Add FxNode render graph playground

Add persistent render graph loadouts, grouped node authoring, code-appending apply behavior, explicit playground lighting, and resilient worker startup.

Amp-Thread-ID: https://ampcode.com/threads/T-01a033d4-0e44-708e-af0e-6166283416ff
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-08-24 17:45:48 +00:00
co-authored by heaust
parent 951449db0b
commit 6153223fd5
27 changed files with 4672 additions and 207 deletions
+62 -1
View File
@@ -42,6 +42,8 @@ button { color: inherit; }
.tool-group button { display: inline-flex; height: 29px; align-items: center; gap: 6px; padding: 0 9px; color: #586d80; border: 1px solid transparent; border-radius: 5px; background: transparent; cursor: pointer; font-size: 10px; }
.tool-group button:hover, .tool-group button[aria-pressed="true"] { color: #187fc2; border-color: #d3e3ed; background: #fff; }
.braces { color: #2498df; font: 700 10px/1 ui-monospace, monospace; }
.graph-tool-hint { display: inline-flex; height: 27px; align-items: center; gap: 5px; padding: 0 9px; color: #6b8193; font-size: 9px; }
.graph-tool-hint b { color: #258dcf; font-weight: 700; }
.tool-context { display: flex; min-width: 0; flex: 1; align-items: center; justify-content: center; gap: 7px; color: #748698; font-size: 9px; }
.language-dot { width: 6px; height: 6px; border-radius: 2px; background: #2d9de3; }
.context-divider { width: 1px; height: 12px; margin-inline: 4px; background: #cfdae3; }
@@ -55,6 +57,10 @@ button { color: inherit; }
.pane-tab { display: flex; min-width: 145px; align-items: center; gap: 7px; padding: 0 14px; color: #9db1c2; border: 0; border-right: 1px solid #22384b; background: #0d1d2d; font-size: 10px; }
.pane-tab.active { color: #d4e2ed; border-top: 2px solid #2ba5ea; background: #0b1927; }
.ts-icon { display: grid; width: 18px; height: 16px; place-items: center; color: #fff; border-radius: 2px; background: #278dcd; font: 700 7px/1 ui-monospace, monospace; }
.rg-tab-icon { display: grid; width: 18px; height: 16px; place-items: center; color: #b8e6fb; border: 1px solid #277da9; border-radius: 3px; background: #16425c; font-size: 10px; }
#graph-tab-dirty { display: none; width: 6px; height: 6px; border-radius: 50%; background: #4fbceb; }
#graph-tab-dirty.visible { display: inline-block; }
.coding-panel { min-height: 0; }
.editor { position: relative; min-height: 0; overflow: hidden; background: #0b1927; }
.editor-loading { display: grid; height: 100%; place-items: center; color: #668097; font: 10px/1 ui-monospace, monospace; }
.monaco-editor .margin { background-color: #0b1927 !important; }
@@ -64,6 +70,57 @@ button { color: inherit; }
#editor-diagnostics i { width: 5px; height: 5px; border-radius: 50%; background: #4aa975; }
#editor-diagnostics.has-errors { color: #e8a4a4; }
#editor-diagnostics.has-errors i { background: #e26f6f; }
.render-graph-editor { display: grid; min-height: 0; grid-template-rows: 46px minmax(0, 1fr); overflow: hidden; background: #081522; }
.render-graph-toolbar { position: relative; z-index: 2; display: flex; min-width: 0; align-items: center; gap: 8px; padding: 6px 8px; border-bottom: 1px solid #1d3548; background: #0d2030; box-shadow: 0 5px 18px rgb(0 0 0 / 10%); }
.render-graph-toolbar label { position: relative; display: flex; min-width: 0; height: 31px; align-items: center; }
.render-graph-toolbar label > span { position: absolute; z-index: 1; top: 4px; left: 9px; color: #54758c; font: 700 6px/1 ui-monospace, monospace; letter-spacing: .08em; text-transform: uppercase; pointer-events: none; }
.render-graph-toolbar select, .render-graph-toolbar input { height: 31px; padding: 11px 28px 3px 9px; color: #c2d7e5; border: 1px solid #29465c; border-radius: 5px; outline: 0; background: #10283a; font-size: 9px; }
.render-graph-toolbar select { width: 138px; cursor: pointer; }
.render-graph-toolbar input { width: 120px; padding-right: 8px; }
.render-graph-toolbar select:focus, .render-graph-toolbar input:focus { border-color: #318fc8; box-shadow: 0 0 0 2px rgb(49 155 215 / 12%); }
.render-graph-toolbar button { display: inline-flex; height: 31px; flex: 0 0 auto; align-items: center; gap: 5px; padding: 0 10px; color: #dff4ff; border: 1px solid #238ac8; border-radius: 5px; background: #187cb7; cursor: pointer; font-size: 8px; font-weight: 700; }
.render-graph-toolbar button:hover { background: #208dca; }
.render-graph-toolbar button.secondary { color: #a9c7d9; border-color: #31536a; background: #132d3f; }
.render-graph-toolbar button.secondary:hover { color: #e0f4ff; border-color: #39779c; background: #18394f; }
.render-graph-toolbar button:disabled { cursor: wait; opacity: .55; }
#loadout-status { display: inline-flex; min-width: 0; align-items: center; gap: 6px; margin-left: auto; overflow: hidden; color: #7590a3; font: 7px/1 ui-monospace, monospace; text-overflow: ellipsis; white-space: nowrap; }
#loadout-status i { width: 5px; height: 5px; flex: 0 0 auto; border-radius: 50%; background: #44ad78; box-shadow: 0 0 0 3px rgb(68 173 120 / 10%); }
#loadout-status.busy i { background: #3aa9e5; animation: status-pulse 1s infinite; }
#loadout-status.failed { color: #df9696; }
#loadout-status.failed i { background: #df6c6c; }
.render-graph-canvas-wrap { position: relative; min-width: 0; min-height: 0; overflow: hidden; }
#render-graph-canvas { display: block; width: 100%; height: 100%; outline: 0; touch-action: none; }
#render-graph-canvas:focus-visible { box-shadow: inset 0 0 0 1px #3ba8e4; }
.render-graph-empty-hint { position: absolute; right: 12px; bottom: 12px; display: grid; grid-template-columns: 24px auto; gap: 2px 8px; align-items: center; padding: 8px 10px; color: #718b9f; border: 1px solid #203e53; border-radius: 7px; background: rgb(9 27 42 / 86%); box-shadow: 0 8px 25px rgb(0 0 0 / 16%); pointer-events: none; backdrop-filter: blur(6px); }
.render-graph-empty-hint > span { display: grid; width: 24px; height: 24px; grid-row: 1 / 3; place-items: center; color: #50b9ed; border: 1px solid #295b77; border-radius: 5px; background: #0f3046; }
.render-graph-empty-hint strong { color: #9bb4c6; font-size: 8px; }
.render-graph-empty-hint small { color: #577085; font-size: 7px; }
.rg-node-menu { position: fixed; z-index: 250; width: 226px; padding: 7px; visibility: hidden; color: #bcd0de; border: 1px solid #2d4c61; border-radius: 8px; background: #0d2030; box-shadow: 0 18px 50px rgb(0 0 0 / 40%); font-size: 9px; }
.rg-node-menu::before { position: absolute; inset: 0; border-radius: inherit; box-shadow: inset 0 1px rgb(255 255 255 / 4%); content: ""; pointer-events: none; }
.rg-menu-search { display: flex; height: 31px; align-items: center; gap: 7px; margin-bottom: 6px; padding: 0 8px; color: #5d8098; border: 1px solid #28465b; border-radius: 5px; background: #091927; }
.rg-menu-search input { min-width: 0; flex: 1; color: #cce1ed; border: 0; outline: 0; background: transparent; font-size: 9px; }
.rg-menu-search input::placeholder { color: #557186; }
.rg-menu-groups { display: grid; gap: 2px; }
.rg-menu-group { position: relative; }
.rg-menu-group-trigger { display: grid; width: 100%; min-height: 34px; grid-template-columns: 24px 1fr auto; align-items: center; gap: 7px; padding: 0 7px; color: #a6bfce; text-align: left; border: 1px solid transparent; border-radius: 5px; background: transparent; cursor: default; }
.rg-menu-group-trigger:hover, .rg-menu-group.open > .rg-menu-group-trigger { color: #d9effb; border-color: #294b62; background: #153246; }
.rg-menu-group-icon { display: grid; width: 21px; height: 21px; place-items: center; color: #60c3f2; border: 1px solid #2a5a75; border-radius: 4px; background: #0e2a3e; font-size: 10px; }
.rg-menu-group-trigger small { color: #557287; font-size: 7px; }
.rg-menu-chevron { color: #56788f; }
.rg-menu-submenu { position: absolute; top: -7px; left: calc(100% + 7px); display: none; width: 252px; max-height: min(390px, calc(100vh - 20px)); padding: 7px; overflow-y: auto; border: 1px solid #2d4c61; border-radius: 8px; background: #0d2030; box-shadow: 0 18px 50px rgb(0 0 0 / 38%); }
.rg-node-menu.open-left .rg-menu-submenu { right: calc(100% + 7px); left: auto; }
.rg-menu-group.open > .rg-menu-submenu { display: grid; gap: 2px; }
.rg-menu-group:nth-child(n + 4) .rg-menu-submenu { top: auto; bottom: -7px; }
.rg-menu-submenu::before { position: absolute; top: 0; right: 100%; width: 8px; height: 42px; content: ""; }
.rg-menu-submenu > strong { padding: 5px 7px 7px; color: #5f8198; font: 700 7px/1 ui-monospace, monospace; letter-spacing: .1em; text-transform: uppercase; }
.rg-menu-option { display: grid; min-height: 42px; grid-template-columns: 1fr auto; gap: 3px 8px; padding: 6px 8px; color: #a9c0cf; text-align: left; border: 1px solid transparent; border-radius: 5px; background: transparent; cursor: pointer; }
.rg-menu-option:hover, .rg-menu-option.active { color: #def3ff; border-color: #2b526a; background: #15364c; }
.rg-menu-option span { font-weight: 650; }
.rg-menu-option small { grid-column: 1 / 3; color: #607b8f; font-size: 7px; line-height: 1.3; }
.rg-menu-option b { color: #48b6eb; font-size: 11px; font-weight: 400; }
.rg-menu-results { display: grid; gap: 2px; max-height: min(390px, calc(100vh - 75px)); overflow-y: auto; }
.rg-menu-empty { padding: 17px 9px; color: #657e91; text-align: center; font-size: 8px; }
.resize-handle { position: relative; z-index: 3; cursor: col-resize; background: #172a3b; }
.resize-handle::after { position: absolute; top: 50%; left: 1px; width: 3px; height: 40px; transform: translateY(-50%); border-radius: 3px; background: #355067; content: ""; opacity: .65; }
.resize-handle:hover, .resize-handle.dragging { background: #218ed2; }
@@ -153,9 +210,13 @@ button { color: inherit; }
.run-button kbd { display: none; }
.tool-context { display: none; }
.tool-group-right { margin-left: auto; }
.workbench { display: grid; overflow: auto; grid-template-columns: 1fr; grid-template-rows: minmax(400px, 55vh) minmax(450px, 1fr); }
.workbench { display: grid; overflow: auto; grid-template-columns: 1fr; grid-template-rows: max(560px, 55vh) minmax(500px, 1fr); }
.resize-handle { display: none; }
.stage-pane { min-height: 500px; }
.render-graph-toolbar { overflow-x: auto; }
.loadout-name-field { display: none !important; }
.render-graph-empty-hint { display: none; }
.rg-menu-submenu { position: fixed; top: auto; right: 8px; bottom: 8px; left: 8px; width: auto; max-height: 42vh; }
}
@media (prefers-reduced-motion: reduce) {