ima2-gen 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,7 +51,7 @@ Both indicators shown live in the left panel (green dot = ready, red dot = disab
51
51
  | **Quality** | Low (fast) · Medium (balanced) · High (best) |
52
52
  | **Size** | `1024²` `1536×1024` `1024×1536` `1360×1024` `1024×1360` `1824×1024` `1024×1824` `2048²` `2048×1152` `1152×2048` `3824×2160` `2160×3824` · `auto` · custom |
53
53
  | **Format** | PNG · JPEG · WebP |
54
- | **Moderation** | Low (less restrictive) · Auto (standard) |
54
+ | **Moderation** | Low (relaxed filter, default) · Auto (standard filter) |
55
55
  | **Count** | 1 · 2 · 4 parallel |
56
56
 
57
57
  All sizes respect gpt-image-2 constraints: every side is a multiple of 16, long:short ratio ≤ 3:1, 655,360–8,294,400 total pixels.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ima2-gen",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "GPT Image 2 generator with OAuth & API key support",
5
5
  "type": "module",
6
6
  "bin": {
package/server.js CHANGED
@@ -62,6 +62,7 @@ app.use("/generated", express.static(join(__dirname, "generated"), {
62
62
  // ── Reference validation ──
63
63
  const MAX_REF_B64_BYTES = 7 * 1024 * 1024; // ~5.2MB binary after base64 decode
64
64
  const BASE64_RE = /^[A-Za-z0-9+/]+=*$/;
65
+ const VALID_MODERATION = new Set(["auto", "low"]);
65
66
  function validateAndNormalizeRefs(references) {
66
67
  if (!Array.isArray(references)) return { error: "references must be an array" };
67
68
  if (references.length > 5) return { error: "references may not exceed 5 items" };
@@ -82,6 +83,13 @@ function validateAndNormalizeRefs(references) {
82
83
  return { refs: out };
83
84
  }
84
85
 
86
+ function validateModeration(moderation) {
87
+ if (typeof moderation !== "string" || !VALID_MODERATION.has(moderation)) {
88
+ return { error: "moderation must be one of: auto, low" };
89
+ }
90
+ return { moderation };
91
+ }
92
+
85
93
  // ── OAuth proxy: generate via Responses API (stream mode) ──
86
94
  // Research mode is ALWAYS ON for OAuth — web_search is included in tools, GPT
87
95
  // decides per-prompt whether to actually invoke it. Simple prompts skip web_search
@@ -89,10 +97,10 @@ function validateAndNormalizeRefs(references) {
89
97
  const RESEARCH_SUFFIX =
90
98
  "\n\n필요하면 먼저 웹에서 이 주제의 정확한 레퍼런스(얼굴/제품/장소/최신 정보)를 검색한 뒤 그걸 토대로 이미지를 생성해. 단순한 주제는 곧바로 생성해도 돼.";
91
99
 
92
- async function generateViaOAuth(prompt, quality, size, references = [], requestId = null) {
100
+ async function generateViaOAuth(prompt, quality, size, moderation = "low", references = [], requestId = null) {
93
101
  const tools = [
94
102
  { type: "web_search" },
95
- { type: "image_generation", quality, size },
103
+ { type: "image_generation", quality, size, moderation },
96
104
  ];
97
105
 
98
106
  const textPrompt = `Generate an image: ${prompt}${RESEARCH_SUFFIX}`;
@@ -220,7 +228,7 @@ async function generateViaOAuth(prompt, quality, size, references = [], requestI
220
228
  body: JSON.stringify({
221
229
  model: "gpt-5.4",
222
230
  input: [{ role: "user", content: prompt }],
223
- tools: [{ type: "image_generation", quality, size }],
231
+ tools: [{ type: "image_generation", quality, size, moderation }],
224
232
  stream: false,
225
233
  }),
226
234
  });
@@ -456,6 +464,8 @@ app.post("/api/generate", async (req, res) => {
456
464
  req.body;
457
465
 
458
466
  if (!prompt) return res.status(400).json({ error: "Prompt is required" });
467
+ const moderationCheck = validateModeration(moderation);
468
+ if (moderationCheck.error) return res.status(400).json({ error: moderationCheck.error });
459
469
  const count = Math.min(Math.max(parseInt(n) || 1, 1), 8);
460
470
  startJob({
461
471
  requestId,
@@ -484,7 +494,7 @@ app.post("/api/generate", async (req, res) => {
484
494
  }
485
495
  const useOAuth = true;
486
496
  const __client = req.get("x-ima2-client") || "ui";
487
- console.log(`[generate][${__client}] provider=oauth quality=${quality} size=${size} n=${count} refs=${refB64s.length}`);
497
+ console.log(`[generate][${__client}] provider=oauth quality=${quality} size=${size} moderation=${moderation} n=${count} refs=${refB64s.length}`);
488
498
  const startTime = Date.now();
489
499
 
490
500
  const mimeMap = { png: "image/png", jpeg: "image/jpeg", webp: "image/webp" };
@@ -496,7 +506,7 @@ app.post("/api/generate", async (req, res) => {
496
506
  let lastErr;
497
507
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
498
508
  try {
499
- const r = await generateViaOAuth(prompt, quality, size, refB64s, requestId);
509
+ const r = await generateViaOAuth(prompt, quality, size, moderation, refB64s, requestId);
500
510
  if (r.b64) return r;
501
511
  lastErr = new Error("Empty response (safety refusal)");
502
512
  } catch (e) {
@@ -527,6 +537,7 @@ app.post("/api/generate", async (req, res) => {
527
537
  quality,
528
538
  size,
529
539
  format,
540
+ moderation,
530
541
  provider: "oauth",
531
542
  createdAt: Date.now(),
532
543
  usage: r.value.usage || null,
@@ -562,6 +573,7 @@ app.post("/api/generate", async (req, res) => {
562
573
  webSearchCalls: totalWebSearchCalls,
563
574
  quality,
564
575
  size,
576
+ moderation,
565
577
  };
566
578
 
567
579
  if (count === 1) {
@@ -578,7 +590,7 @@ app.post("/api/generate", async (req, res) => {
578
590
  });
579
591
 
580
592
  // ── OAuth edit: send image as input to Responses API ──
581
- async function editViaOAuth(prompt, imageB64, quality, size) {
593
+ async function editViaOAuth(prompt, imageB64, quality, size, moderation = "low") {
582
594
  const res = await fetch(`${OAUTH_URL}/v1/responses`, {
583
595
  method: "POST",
584
596
  headers: { "Content-Type": "application/json", Accept: "text/event-stream" },
@@ -594,7 +606,7 @@ async function editViaOAuth(prompt, imageB64, quality, size) {
594
606
  ],
595
607
  },
596
608
  ],
597
- tools: [{ type: "image_generation", quality, size }],
609
+ tools: [{ type: "image_generation", quality, size, moderation }],
598
610
  tool_choice: "required",
599
611
  stream: true,
600
612
  }),
@@ -650,19 +662,21 @@ async function editViaOAuth(prompt, imageB64, quality, size) {
650
662
  // ── Edit image (inpainting) ──
651
663
  app.post("/api/edit", async (req, res) => {
652
664
  try {
653
- const { prompt, image: imageB64, mask: maskB64, quality = "low", size = "1024x1024", provider = "oauth" } =
665
+ const { prompt, image: imageB64, mask: maskB64, quality = "low", size = "1024x1024", moderation = "low", provider = "oauth" } =
654
666
  req.body;
655
667
 
656
668
  if (!prompt || !imageB64)
657
669
  return res.status(400).json({ error: "Prompt and image are required" });
670
+ const moderationCheck = validateModeration(moderation);
671
+ if (moderationCheck.error) return res.status(400).json({ error: moderationCheck.error });
658
672
 
659
673
  if (provider === "api") {
660
674
  return res.status(403).json({ error: "API key provider is disabled. Use OAuth (Codex login).", code: "APIKEY_DISABLED" });
661
675
  }
662
- console.log(`[edit][${req.get("x-ima2-client") || "ui"}] provider=oauth quality=${quality} size=${size}`);
676
+ console.log(`[edit][${req.get("x-ima2-client") || "ui"}] provider=oauth quality=${quality} size=${size} moderation=${moderation}`);
663
677
  const startTime = Date.now();
664
678
 
665
- const { b64: resultB64, usage } = await editViaOAuth(prompt, imageB64, quality, size);
679
+ const { b64: resultB64, usage } = await editViaOAuth(prompt, imageB64, quality, size, moderation);
666
680
 
667
681
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
668
682
 
@@ -673,6 +687,7 @@ app.post("/api/edit", async (req, res) => {
673
687
  prompt,
674
688
  quality,
675
689
  size,
690
+ moderation,
676
691
  format: "png",
677
692
  provider: "oauth",
678
693
  kind: "edit",
@@ -688,6 +703,7 @@ app.post("/api/edit", async (req, res) => {
688
703
  filename,
689
704
  usage,
690
705
  provider: "oauth",
706
+ moderation,
691
707
  });
692
708
  } catch (err) {
693
709
  console.error("Edit error:", err.message);
@@ -720,6 +736,7 @@ app.post("/api/node/generate", async (req, res) => {
720
736
  quality = "low",
721
737
  size = "1024x1024",
722
738
  format = "png",
739
+ moderation = "low",
723
740
  references = [],
724
741
  externalSrc = null,
725
742
  } = body;
@@ -750,6 +767,13 @@ app.post("/api/node/generate", async (req, res) => {
750
767
  parentNodeId,
751
768
  });
752
769
  }
770
+ const moderationCheck = validateModeration(moderation);
771
+ if (moderationCheck.error) {
772
+ return res.status(400).json({
773
+ error: { code: "INVALID_MODERATION", message: moderationCheck.error },
774
+ parentNodeId,
775
+ });
776
+ }
753
777
  const refB64s = refCheck.refs;
754
778
 
755
779
  const startTime = Date.now();
@@ -769,8 +793,8 @@ app.post("/api/node/generate", async (req, res) => {
769
793
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
770
794
  try {
771
795
  const r = parentB64
772
- ? await editViaOAuth(prompt, parentB64, quality, size)
773
- : await generateViaOAuth(prompt, quality, size, refB64s, requestId);
796
+ ? await editViaOAuth(prompt, parentB64, quality, size, moderation)
797
+ : await generateViaOAuth(prompt, quality, size, moderation, refB64s, requestId);
774
798
  if (r.b64) {
775
799
  b64 = r.b64;
776
800
  usage = r.usage;
@@ -801,7 +825,7 @@ app.post("/api/node/generate", async (req, res) => {
801
825
  sessionId,
802
826
  clientNodeId,
803
827
  prompt,
804
- options: { quality, size, format },
828
+ options: { quality, size, format, moderation },
805
829
  createdAt: Date.now(),
806
830
  createdAtIso: new Date().toISOString(),
807
831
  elapsed,
@@ -810,7 +834,7 @@ app.post("/api/node/generate", async (req, res) => {
810
834
  provider: "oauth",
811
835
  kind: parentB64 ? "edit" : "generate",
812
836
  // Fields consumed by /api/history flat scan (so node images appear in history too)
813
- quality, size, format,
837
+ quality, size, format, moderation,
814
838
  };
815
839
  await mkdir(join(__dirname, "generated"), { recursive: true });
816
840
  const { filename } = await saveNode(__dirname, { nodeId, b64, meta, ext: format });
@@ -826,6 +850,7 @@ app.post("/api/node/generate", async (req, res) => {
826
850
  usage,
827
851
  webSearchCalls,
828
852
  provider: "oauth",
853
+ moderation,
829
854
  });
830
855
  } catch (err) {
831
856
  console.error("[node/generate] error:", err.message);
@@ -1077,7 +1102,7 @@ onShutdown(() => {
1077
1102
  });
1078
1103
  process.on("exit", __unadvertise);
1079
1104
 
1080
- app.listen(PORT, () => {
1105
+ const server = app.listen(PORT, () => {
1081
1106
  console.log(`Image Gen running at http://localhost:${PORT}`);
1082
1107
  console.log(`Provider policy: OAuth only (API key hard-disabled). OAuth proxy port ${OAUTH_PORT}.`);
1083
1108
  __advertise();
@@ -1088,3 +1113,12 @@ app.listen(PORT, () => {
1088
1113
  console.error("[db] bootstrap failed:", err.message);
1089
1114
  }
1090
1115
  });
1116
+
1117
+ server.on("error", (err) => {
1118
+ if (err?.code === "EADDRINUSE") {
1119
+ console.error(`[server] Port ${PORT} is already in use. Stop the existing image_gen server before starting another dev server.`);
1120
+ process.exit(1);
1121
+ }
1122
+ console.error("[server] Failed to start:", err?.message || err);
1123
+ process.exit(1);
1124
+ });
@@ -0,0 +1 @@
1
+ @layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-border-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.hidden{display:none}.border{border-style:var(--tw-border-style);border-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}}*,:before,:after{box-sizing:border-box;margin:0;padding:0}:root{--bg:#0a0a0a;--surface:#141414;--surface-2:#1c1c1c;--border:#2a2a2a;--text:#e8e8e8;--text-dim:#888;--accent:#f0f0f0;--accent-bright:#fff;--accent-soft:#ffffff1a;--accent-ink:#0a0a0a;--green:#22c55e;--amber:#f59e0b;--red:#ef4444;--radius:10px;--font:"Outfit", sans-serif;--mono:"Geist Mono", monospace}body{font-family:var(--font);background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased;min-height:100dvh}.app{grid-template-columns:260px 1fr auto;height:100dvh;display:grid;overflow:hidden}.sidebar{background:var(--surface);border-right:1px solid var(--border);flex-direction:column;height:100dvh;min-height:0;display:flex;overflow:hidden}.sidebar__scroll{flex-direction:column;flex:auto;gap:14px;min-height:0;padding:20px 16px 12px;display:flex;overflow-y:auto}.logo{align-items:center;gap:9px;min-width:0;display:flex}.logo-copy{flex:auto;min-width:0}.logo-title{color:var(--text);letter-spacing:-.04em;font-size:18px;font-weight:650;line-height:1.05}.logo-subtitle{color:var(--text-dim);font-family:var(--mono);letter-spacing:.02em;white-space:nowrap;text-overflow:ellipsis;margin-top:4px;font-size:10px;font-weight:500;overflow:hidden}.lang-toggle{background:#ffffff08;border:1px solid #ffffff29;border-radius:6px;flex:none;gap:2px;margin-left:auto;display:inline-flex;overflow:hidden}.lang-toggle__btn{letter-spacing:.08em;color:var(--text-dim,#888);cursor:pointer;background:0 0;border:none;align-items:center;gap:4px;padding:3px 8px;font-size:11px;font-weight:500;transition:background .12s,color .12s,box-shadow .12s;display:inline-flex}.lang-toggle__btn:hover{color:var(--accent-bright);background:#ffffff14}.lang-toggle__btn.is-active{color:var(--accent-bright);background:var(--accent-soft);box-shadow:inset 0 0 0 1px #ffffff29}.lang-toggle__label{font-family:var(--mono)}.logo-mark{opacity:.9;border:1px solid #ffffff57;border-radius:999px;flex:none;width:12px;height:18px;position:relative}.logo-mark:after{content:"";background:var(--accent-bright);opacity:.72;border-radius:999px;width:4px;height:4px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.billing-bar{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);font-family:var(--mono);flex-direction:column;gap:4px;padding:10px 12px;font-size:12px;display:flex}.billing-bar .label{color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;font-size:10px}.billing-bar .value{font-size:13px;font-weight:600}.section-title{text-transform:uppercase;letter-spacing:1px;color:var(--text-dim);margin-top:4px;font-size:11px;font-weight:500}.prompt-area{resize:vertical;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);width:100%;min-height:80px;max-height:200px;color:var(--text);font-family:var(--font);outline:none;padding:14px;font-size:14px;transition:border-color .2s}.prompt-area:focus-visible{border-color:var(--accent);box-shadow:0 0 0 2px #ffffff14}.prompt-area:focus:not(:focus-visible){border-color:#444}.prompt-area::placeholder{color:#555}.option-group{flex-direction:column;gap:4px;display:flex}.option-help{color:var(--text-muted,#888);margin:2px 0 8px;font-size:11px;line-height:1.45}.option-row{gap:4px;display:flex}.option-btn{background:var(--bg);border:1px solid var(--border);min-width:0;color:var(--text-dim);font-family:var(--mono);cursor:pointer;text-align:center;text-overflow:ellipsis;border-radius:6px;flex:1;padding:6px 4px;font-size:11px;line-height:1.3;transition:all .15s;overflow:hidden}.option-btn:hover{color:var(--text);border-color:#444}.option-btn.active{border-color:var(--accent);color:var(--accent-bright);background:var(--accent-soft)}.cost-estimate{font-family:var(--mono);color:var(--text-dim);justify-content:space-between;padding:8px 0;font-size:12px;display:flex}.cost-estimate .price{color:var(--green);font-weight:500}.generate-btn{background:var(--accent-bright);width:100%;color:var(--accent-ink);border-radius:var(--radius);font-family:var(--font);cursor:pointer;letter-spacing:-.3px;border:none;padding:14px;font-size:15px;font-weight:600;transition:all .15s;box-shadow:0 10px 28px #ffffff14}.generate-btn:hover{background:#fff;transform:translateY(-1px);box-shadow:0 14px 36px #ffffff1f}.generate-btn:active{transform:translateY(1px)}.generate-btn:disabled{opacity:.3;cursor:not-allowed;transform:none}.generate-btn.loading{background:var(--surface-2);color:var(--text-dim);border:1px solid var(--border);box-shadow:none}.generate-btn.loading:before{content:none}@keyframes spin{to{transform:rotate(360deg)}}.canvas{justify-content:center;align-items:center;height:100dvh;padding:24px 24px 16px;display:flex;position:relative;overflow:auto}.right-panel{background:var(--surface);border-left:1px solid var(--border);flex-direction:column;width:280px;height:100dvh;transition:width .18s;display:flex;position:relative}.right-panel.collapsed{width:20px}.right-panel-body{flex-direction:column;flex:1;gap:10px;padding:16px 12px;display:flex;overflow-y:auto}.right-panel.collapsed .right-panel-body{display:none}.right-panel-toggle{background:var(--surface-2);border:1px solid var(--border);width:20px;height:40px;color:var(--text-dim);cursor:pointer;z-index:2;border-right:none;border-radius:6px 0 0 6px;justify-content:center;align-items:center;font-size:11px;display:flex;position:absolute;top:50%;left:-14px;transform:translateY(-50%)}.right-panel-toggle:hover{color:var(--text)}@media(prefers-reduced-motion:reduce){.right-panel{transition:none}}.canvas-empty{text-align:center;color:#333;letter-spacing:-2px;-webkit-user-select:none;user-select:none;font-size:48px;font-weight:700}.canvas-empty span{color:#333;letter-spacing:0;margin-top:12px;font-size:14px;font-weight:400;display:block}.result-container{flex-direction:column;align-items:center;gap:10px;max-width:100%;max-height:100%;display:none}.result-container.visible{display:flex}.result-img{object-fit:contain;border-radius:12px;max-width:100%;max-height:calc(100dvh - 260px);box-shadow:0 20px 60px #00000080}.result-prompt{background:var(--surface);border:1px solid var(--border);max-width:600px;font-family:var(--mono);color:var(--text-dim);cursor:pointer;word-break:break-word;text-align:center;border-radius:8px;padding:10px 16px;font-size:12px;line-height:1.5;transition:all .15s}.result-prompt:hover{color:var(--text);border-color:#444}.result-meta{font-family:var(--mono);color:var(--text-dim);gap:20px;font-size:12px;display:flex}.result-actions{gap:8px;display:flex}.action-btn{background:var(--surface);border:1px solid var(--border);color:var(--text);font-family:var(--mono);cursor:pointer;border-radius:8px;padding:8px 16px;font-size:12px;transition:all .15s}.action-btn:hover{background:var(--surface-2);border-color:#444}.progress-bar{background:var(--border);opacity:0;height:2px;transition:opacity .3s;position:absolute;top:0;left:0;right:0;overflow:hidden}.progress-bar.active{opacity:1}.progress-bar:after{content:"";background:var(--accent-bright);width:40%;height:100%;animation:1.2s ease-in-out infinite progress-slide;position:absolute;top:0;left:-40%}@keyframes progress-slide{0%{left:-40%}to{left:100%}}.history-strip{border-top:1px solid var(--border);background:var(--surface);flex:none;gap:6px;padding:10px 16px;display:flex;overflow:auto hidden}.history-strip::-webkit-scrollbar{height:4px}.history-strip::-webkit-scrollbar-track{background:0 0}.history-strip::-webkit-scrollbar-thumb{background:var(--border);border-radius:2px}.history-thumb{object-fit:cover;cursor:pointer;opacity:.6;border:2px solid #0000;border-radius:6px;flex-shrink:0;width:48px;height:48px;transition:all .15s}.history-thumb:hover{opacity:1}.history-thumb.active{border-color:var(--accent);opacity:1}.history-thumb--add{background:var(--surface-2);color:var(--text-dim);border:1px solid var(--border);opacity:1;z-index:2;flex:none;justify-content:center;align-items:center;display:flex;position:sticky;left:0;box-shadow:6px 0 8px -6px #0009}.history-thumb--add:hover{color:var(--accent);border-color:var(--accent)}.composer{border-radius:var(--radius);border:1px solid var(--border);background:var(--surface);flex-direction:column;gap:8px;padding:10px;transition:border-color .15s,background .15s;display:flex;position:relative}.composer--drag{border-color:var(--accent);background:#ffffff0a}.composer__header{justify-content:space-between;align-items:center;display:flex}.composer__label{margin:0}.composer__count{font-family:var(--mono);color:var(--text-dim);font-size:11px}.composer__chips{flex-wrap:wrap;gap:6px;display:flex}.composer__chip{border:1px solid var(--border);background:var(--bg);border-radius:8px;flex-shrink:0;width:44px;height:44px;position:relative;overflow:hidden}.composer__chip img{object-fit:cover;width:100%;height:100%;display:block}.composer__chip-remove{color:#fff;cursor:pointer;opacity:0;background:#000000bf;border:none;border-radius:50%;justify-content:center;align-items:center;width:16px;height:16px;padding:0;font-size:9px;line-height:1;transition:opacity .12s;display:flex;position:absolute;top:2px;right:2px}.composer__chip:hover .composer__chip-remove{opacity:1}.composer__chip-remove:hover,.composer__chip-remove:focus-visible{background:var(--red);opacity:1}.composer__textarea{background:0 0;border:none;min-height:72px;padding:4px 2px}.composer__textarea:focus,.composer__textarea:focus-visible{box-shadow:none;border:none}.composer__toolbar{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.composer__tool{font-family:var(--mono);color:var(--text-dim);background:var(--surface-2);border:1px solid var(--border);cursor:pointer;border-radius:6px;align-items:center;gap:6px;padding:5px 10px;font-size:11px;transition:border-color .12s,color .12s;display:inline-flex}.composer__tool:hover:not(:disabled),.composer__tool:focus-visible{border-color:var(--accent);color:var(--text)}.composer__tool:disabled{opacity:.4;cursor:not-allowed}.composer__tool svg{flex-shrink:0}.composer__hint{font-family:var(--mono);color:var(--text-dim);opacity:.7;margin-left:auto;font-size:10px}.composer__dropzone{border-radius:var(--radius);border:2px dashed var(--accent);font-family:var(--mono);color:var(--accent-bright);pointer-events:none;z-index:5;background:#ffffff0a;justify-content:center;align-items:center;font-size:12px;display:flex;position:absolute;inset:0}.gallery-backdrop{z-index:110;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#000000c7;justify-content:center;align-items:center;animation:.15s ease-out fadeIn;display:flex;position:fixed;inset:0}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.gallery{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);flex-direction:column;width:min(1280px,94vw);height:min(860px,90vh);display:flex;overflow:hidden;box-shadow:0 20px 60px #00000080}.gallery__header{border-bottom:1px solid var(--border);background:var(--surface);grid-template-columns:1fr minmax(200px,360px) auto;align-items:center;gap:12px;padding:14px 18px;display:grid}.gallery__title-row{align-items:baseline;gap:10px;display:flex}.gallery__title{letter-spacing:-.01em;font-size:16px;font-weight:600}.gallery__meta{font-family:var(--mono);color:var(--text-dim);font-size:11px}.gallery__search{background:var(--surface-2);border:1px solid var(--border);color:var(--text);border-radius:6px;outline:none;padding:7px 10px;font-size:13px;transition:border-color .12s}.gallery__search:focus-visible{border-color:var(--accent);box-shadow:0 0 0 2px #ffffff14}.gallery__search:focus:not(:focus-visible){border-color:var(--accent)}.gallery__close{border:1px solid var(--border);color:var(--text);cursor:pointer;background:0 0;border-radius:6px;justify-content:center;align-items:center;width:32px;height:32px;font-size:14px;display:flex}.gallery__close:hover{border-color:var(--accent);color:var(--accent)}.gallery__scroll{flex:1;padding:8px 0 16px;overflow-y:auto}.gallery__group{padding:8px 18px 4px}.gallery__group-header{background:var(--surface);z-index:1;border-bottom:1px solid #ffffff0a;align-items:baseline;gap:8px;padding:10px 0 8px;display:flex;position:sticky;top:0}.gallery__group-label{color:var(--text);text-transform:uppercase;letter-spacing:.06em;font-size:12px;font-weight:600}.gallery__group-count{font-family:var(--mono);color:var(--text-dim);font-size:11px}.gallery__grid{grid-template-columns:repeat(auto-fill,minmax(170px,1fr));align-content:start;gap:10px;padding:10px 0 4px;display:grid}.gallery__tile{background:var(--surface-2);cursor:pointer;aspect-ratio:1;border:2px solid #0000;border-radius:8px;padding:0;transition:transform .12s,border-color .12s;position:relative;overflow:hidden}.gallery__tile:hover{border-color:#3a3a3a;transform:translateY(-1px)}.gallery__tile img{object-fit:cover;width:100%;height:100%;display:block}.gallery__tile--active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.gallery__caption{color:#fff;text-align:left;opacity:0;pointer-events:none;background:linear-gradient(#0000,#000000d9 70%);padding:18px 8px 6px;font-size:11px;line-height:1.3;transition:opacity .15s;position:absolute;bottom:0;left:0;right:0}.gallery__tile:hover .gallery__caption{opacity:1}.gallery__caption-text{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.gallery__empty{text-align:center;color:var(--text-dim);padding:80px 20px;font-size:14px}.gallery__tile-wrap{position:relative}.gallery__delete{color:#fff;cursor:pointer;opacity:0;z-index:2;background:#000000b8;border:none;border-radius:50%;width:24px;height:24px;font-size:13px;line-height:1;transition:opacity .15s,background .15s;position:absolute;top:6px;right:6px}.gallery__tile-wrap:hover .gallery__delete{opacity:1}.gallery__delete:hover{background:var(--red,#e54040)}.gallery__group-toggle{background:var(--surface);border:1px solid var(--border);border-radius:8px;gap:4px;margin-left:auto;padding:2px;display:inline-flex}.gallery__group-toggle button{color:var(--text-dim);cursor:pointer;background:0 0;border:none;border-radius:6px;padding:4px 10px;font-size:12px}.gallery__group-toggle button.active{background:var(--accent);color:var(--accent-ink)}.gallery__undo{background:var(--surface);border:1px solid var(--border);z-index:10;border-radius:8px;align-items:center;gap:12px;padding:8px 14px;font-size:13px;display:flex;position:absolute;bottom:16px;left:50%;transform:translate(-50%);box-shadow:0 4px 16px #0006}.gallery__undo button{background:var(--accent);color:var(--accent-ink);cursor:pointer;border:none;border-radius:4px;padding:4px 10px;font-size:12px;font-weight:600}.gallery__undo-timer{color:var(--text-dim);font-variant-numeric:tabular-nums;font-size:11px}.toast{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);font-family:var(--mono);color:var(--text);opacity:0;z-index:100;padding:12px 20px;font-size:13px;transition:all .3s;position:fixed;bottom:24px;right:24px;transform:translateY(100px)}.toast.visible{opacity:1;transform:translateY(0)}.toast.error{border-color:var(--red);color:var(--red)}.custom-size-input{background:var(--bg);border:1px solid var(--border);color:var(--text);font-family:var(--mono);text-align:center;border-radius:8px;outline:none;flex:1;padding:8px;font-size:12px}.size-hint{font-family:var(--mono);color:var(--text-dim);font-size:10px}.oauth-status{font-family:var(--mono);color:var(--text-dim);padding:4px 0;font-size:11px}.provider-row{gap:8px;display:flex}.provider-pill{background:var(--surface-2);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);cursor:pointer;flex:1;align-items:center;gap:8px;padding:8px 10px;font-size:12px;transition:border-color .15s,background .15s;display:flex}.provider-pill:hover{border-color:#3a3a3a}.provider-pill.selected{border-color:var(--accent);background:#1a1a1a}.status-dot{border-radius:50%;flex-shrink:0;width:8px;height:8px;display:inline-block}.sr-only{clip:rect(0,0,0,0);white-space:nowrap;border:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.status-dot--ok{background:var(--green);box-shadow:0 0 6px var(--green)}.status-dot--bad{background:var(--red);box-shadow:0 0 6px var(--red)}.modal-backdrop{z-index:100;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);background:#0009;justify-content:center;align-items:center;display:flex;position:fixed;inset:0}.modal{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);width:min(440px,90vw);padding:20px 22px;box-shadow:0 20px 40px #00000080}.modal__title{color:var(--text);margin-bottom:10px;font-size:15px;font-weight:600}.modal__body p{color:var(--text);margin:0 0 8px;font-size:13px;line-height:1.5}.modal__hint{color:var(--text-dim);font-family:var(--mono);font-size:12px}.modal__actions{justify-content:flex-end;margin-top:14px;display:flex}.modal__btn{background:var(--accent);color:var(--accent-ink);cursor:pointer;border:none;border-radius:6px;padding:8px 16px;font-size:13px;font-weight:600}.modal__btn:hover{background:var(--accent-bright)}.option-sub{color:var(--text-dim);font-size:10px;line-height:1.1}@media(max-width:800px){.app{grid-template-rows:auto 1fr;grid-template-columns:1fr;height:auto;overflow:visible}.sidebar{border-right:none;border-bottom:1px solid var(--border);height:auto;max-height:50dvh}.sidebar__scroll{padding:12px}.canvas{height:auto;min-height:50dvh;padding:20px}.right-panel{z-index:50;width:min(320px,85vw);height:100dvh;transition:transform .22s;position:fixed;top:0;right:0;transform:translate(100%);box-shadow:-4px 0 24px #0006}.right-panel.drawer-open{transform:translate(0)}.right-panel.collapsed{width:min(320px,85vw)}.right-panel-toggle{border:1px solid var(--border);z-index:60;border-radius:8px;width:40px;height:40px;position:fixed;top:12px;left:auto;right:12px;transform:none}.right-panel-backdrop{z-index:40;background:#00000080;animation:.22s drawer-fade;position:fixed;inset:0}@keyframes drawer-fade{0%{opacity:0}to{opacity:1}}}.ui-mode-switch{background:var(--surface-2,#1a1a1a);border-radius:8px;gap:4px;margin:8px 0;padding:4px;display:flex}.ui-mode-switch__tab{color:var(--text-muted,#888);cursor:pointer;background:0 0;border:none;border-radius:6px;flex:1;padding:6px 10px;font-size:12px}.ui-mode-switch__tab.active{background:var(--accent-bright,#fff);color:var(--accent-ink,#171006);font-weight:600}.sidebar__node-hint{background:var(--surface-2,#1a1a1a);color:var(--text-muted,#888);border-radius:8px;margin:8px 0;padding:12px;font-size:12px;line-height:1.5}.node-canvas{background:var(--surface,#0d0d0d);flex:1;justify-content:center;align-items:center;display:flex;position:relative;overflow:hidden}.node-canvas__plus{background:var(--accent,#4a9eff);color:#fff;cursor:pointer;border:none;border-radius:12px;padding:18px 28px;font-size:15px}.node-canvas__add-root{z-index:10;background:var(--accent,#4a9eff);color:#fff;cursor:pointer;border:none;border-radius:50%;width:36px;height:36px;font-size:18px;position:absolute;top:16px;right:16px;box-shadow:0 4px 12px #0000004d}.image-node{background:var(--surface-2,#1a1a1a);border:1px solid var(--border,#2a2a2a);width:260px;color:var(--text,#ddd);border-radius:12px;flex-direction:column;gap:8px;padding:10px;font-size:12px;display:flex}.image-node--pending{border-color:#f0c040}.image-node--ready{border-color:#4a9eff}.image-node--error{border-color:#e05050}.image-node__preview{aspect-ratio:1;background:#0a0a0a;border-radius:8px;justify-content:center;align-items:center;width:100%;display:flex;overflow:hidden}.image-node__preview img{object-fit:cover;width:100%;height:100%}.image-node__placeholder,.image-node__skeleton{color:#555;font-size:20px}.image-node__prompt{border:1px solid var(--border,#2a2a2a);width:100%;color:var(--text,#ddd);resize:vertical;background:#0a0a0a;border-radius:6px;padding:6px 8px;font-family:inherit;font-size:12px}.image-node__footer{justify-content:space-between;align-items:center;gap:6px;display:flex}.image-node__status{color:var(--text-muted,#888);flex:1;font-size:10px}.image-node__actions{gap:4px;display:flex}.image-node__actions button{background:var(--surface-3,#2a2a2a);color:var(--text,#ddd);cursor:pointer;border:none;border-radius:4px;padding:4px 8px;font-size:11px}.image-node__actions button:disabled{opacity:.5;cursor:not-allowed}.image-node__del{color:#e05050!important}.in-flight-list{flex-direction:column;gap:4px;margin:8px 0 0;padding:0;list-style:none;display:flex}.in-flight-item{background:var(--surface-2);border:1px solid var(--border);color:var(--text-dim);border-radius:6px;justify-content:space-between;align-items:center;gap:8px;padding:6px 10px;font-size:12px;display:flex}.in-flight-prompt{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.in-flight-phase{letter-spacing:.04em;text-transform:uppercase;color:var(--text-dim);opacity:.7;font-variant-numeric:tabular-nums;flex-shrink:0;font-size:10px}.in-flight-item[data-phase=streaming] .in-flight-phase{color:var(--accent,#7aa2ff);opacity:1}.in-flight-item[data-phase=decoding] .in-flight-phase{color:#7fd17f;opacity:1}.in-flight-spinner{border:2px solid var(--text-dim);border-top-color:#0000;border-radius:50%;flex-shrink:0;width:12px;height:12px;animation:.8s linear infinite spin}.image-node--selected{box-shadow:0 0 0 2px #ffffff26,0 8px 24px #00000080;border-color:var(--accent-bright,#fff)!important}.image-node__handle{background:var(--text-dim,#888)!important;border:2px solid var(--surface-2,#1c1c1c)!important;width:10px!important;height:10px!important}.image-node__handle--source,.image-node:hover .image-node__handle{background:var(--accent-bright,#fff)!important}.node-canvas__hint{font-family:var(--mono,monospace);color:var(--text-dim,#888);border:1px solid var(--border,#2a2a2a);pointer-events:none;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);background:#0a0a0ab3;border-radius:6px;padding:4px 10px;font-size:11px;position:absolute;bottom:12px;left:50%;transform:translate(-50%)}.react-flow__controls{border-radius:6px;overflow:hidden;background:var(--surface-2,#1c1c1c)!important;border:1px solid var(--border,#2a2a2a)!important}.react-flow__controls-button{border-bottom:1px solid var(--border,#2a2a2a)!important;color:var(--text,#e8e8e8)!important;fill:var(--text,#e8e8e8)!important;background:0 0!important}.react-flow__controls-button:hover{background:var(--surface,#141414)!important}.react-flow__controls-button svg{fill:currentColor!important}.react-flow__edge-path{stroke:var(--text-dim,#888)!important;stroke-width:1.5px!important}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge:hover .react-flow__edge-path{stroke:var(--accent-bright,#fff)!important;stroke-width:2px!important}.react-flow__connectionline{stroke-dasharray:4 4;stroke:var(--accent-bright,#fff)!important;stroke-width:2px!important}.session-picker{flex-direction:column;gap:4px;margin:10px 0 6px;display:flex}.session-picker-row{align-items:stretch;gap:4px;display:flex}.session-current{background:var(--surface,#141414);color:var(--text,#e8e8e8);border:1px solid var(--border,#2a2a2a);border-radius:var(--radius,6px);font:inherit;cursor:pointer;text-align:left;flex:1;justify-content:space-between;align-items:center;min-width:0;padding:7px 10px;font-size:12px;display:flex}.session-current:hover{border-color:#3a3a3a}.session-title{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.session-caret{color:var(--text-dim,#888);margin-left:6px}.session-btn{background:var(--surface,#141414);color:var(--text,#e8e8e8);border:1px solid var(--border,#2a2a2a);border-radius:var(--radius,6px);font:inherit;cursor:pointer;min-width:28px;padding:0 10px;font-size:13px}.session-btn:hover:not(:disabled){border-color:#3a3a3a}.session-btn:disabled{opacity:.4;cursor:not-allowed}.session-list{background:var(--surface,#141414);border:1px solid var(--border,#2a2a2a);border-radius:var(--radius,6px);max-height:220px;margin:0;padding:4px;list-style:none;overflow-y:auto}.session-list li{border-radius:4px;align-items:stretch;gap:2px;display:flex}.session-list li:hover{background:#ffffff08}.session-list li.is-active{background:#ffffff0f}.session-item{color:var(--text,#e8e8e8);font:inherit;cursor:pointer;text-align:left;background:0 0;border:0;flex:1;justify-content:space-between;align-items:center;min-width:0;padding:6px 8px;font-size:12px;display:flex}.session-item-title{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.session-item-count{color:var(--text-dim,#888);font-size:10px;font-family:var(--mono,monospace);margin-left:8px}.session-del{color:var(--text-dim,#888);cursor:pointer;background:0 0;border:0;padding:0 8px;font-size:14px}.session-del:hover{color:#f66}.session-empty{color:var(--text-dim,#888);text-align:center;padding:8px;font-size:12px}.node-canvas__loading{z-index:20;color:var(--text-dim,#aaa);font-size:13px;font-family:var(--mono,monospace);pointer-events:auto;background:#0a0a0ab3;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #777;--xy-background-pattern-lines-color-default: #777;--xy-background-pattern-cross-color-default: #777;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}