-
- {#each _entries as entry}
-
+
-
-
- {/each}
+
+
- {entry.name}
-
-
+
- Prompt Details
- {#if $queueState.runningNodeId || $queueState.progress}
-
- Node: {getNodeInfo($queueState.runningNodeId)}
-
-
-
+ {#if selectedPrompt}
+
+ {/if}
+
+
+
+
+ {#if _entries.length > 0}
+ {#each _entries as entry}
+
diff --git a/src/lib/components/gradio/app/Accordion.svelte b/src/lib/components/gradio/app/Accordion.svelte
index a26f663..1b958f7 100644
--- a/src/lib/components/gradio/app/Accordion.svelte
+++ b/src/lib/components/gradio/app/Accordion.svelte
@@ -13,7 +13,7 @@
}
-
showPrompt(entry, e)}>
+ {#if entry.images.length > 0}
+
+
+ {#each entry.images.slice(0, 4) as image, i}
+
+ {:else}
+
+ {/if}
+
+
showLightbox(entry, i, e)}
+ src={image}
+ alt="thumbnail" />
+
+ {/each}
+
+
+
+ {entry.message}
+
+
+ {entry.submessage}
+
+
+ {#if entry.date != null}
+
+ {entry.date}
+
+ {/if}
+
+ {/each}
+ {:else}
+
+
{/if}
- {#if typeof $queueState.queueRemaining === "number" && $queueState.queueRemaining > 0}
-
+
+
+
+ (No entries)
+
+
-
diff --git a/src/lib/components/LightboxModal.svelte b/src/lib/components/LightboxModal.svelte
index 2c490b3..167999e 100644
--- a/src/lib/components/LightboxModal.svelte
+++ b/src/lib/components/LightboxModal.svelte
@@ -95,12 +95,12 @@
position: absolute;
top: 50%;
width: auto;
- padding: 16px;
+ padding: 60px;
margin-top: -50px;
color: white;
font-weight: bold;
- font-size: 20px;
- transition: 0.6s ease;
+ font-size: 40px;
+ transition: 0.3s ease;
border-radius: 0 3px 3px 0;
user-select: none;
-webkit-user-select: none;
@@ -113,6 +113,6 @@
.modalPrev:hover,
.modalNext:hover {
- background-color: rgba(0, 0, 0, 0.8);
+ background-color: rgba(180, 180, 180, 0.8);
}
diff --git a/src/lib/components/Modal.svelte b/src/lib/components/Modal.svelte
new file mode 100644
index 0000000..856882e
--- /dev/null
+++ b/src/lib/components/Modal.svelte
@@ -0,0 +1,61 @@
+
+
+
+
+
+
diff --git a/src/lib/components/ProgressBar.svelte b/src/lib/components/ProgressBar.svelte
index 61d13bf..7b5682f 100644
--- a/src/lib/components/ProgressBar.svelte
+++ b/src/lib/components/ProgressBar.svelte
@@ -23,17 +23,17 @@
diff --git a/src/lib/components/Spinner.svelte b/src/lib/components/Spinner.svelte
new file mode 100644
index 0000000..8174f2a
--- /dev/null
+++ b/src/lib/components/Spinner.svelte
@@ -0,0 +1,24 @@
+
+
+
diff --git a/src/lib/components/WidgetContainer.svelte b/src/lib/components/WidgetContainer.svelte
index 58d784e..cc13603 100644
--- a/src/lib/components/WidgetContainer.svelte
+++ b/src/lib/components/WidgetContainer.svelte
@@ -50,7 +50,7 @@
$: if ($queueState && widget && widget.node) {
- dragItem.isNodeExecuting = $queueState.runningNodeId === widget.node.id;
+ dragItem.isNodeExecuting = $queueState.runningNodeID === widget.node.id;
}
function getWidgetClass() {
@@ -72,7 +72,7 @@
- Queued prompts: {$queueState.queueRemaining}.
+
+
+
+ switchMode("queue")}
+ class:mode-selected={mode === "queue"}>
+ Queue
+
+ switchMode("history")}
+ class:mode-selected={mode === "history"}>
+ History
+
+
+
+ {#if inProgress}
+
+
- {:else}
-
+ Queued prompts: {$queueState.queueRemaining}
-
+ {:else}
+ {#if queued}
+
Nothing queued.
+ {/if}
+
+ Node: {getNodeInfo($queueState.runningNodeID)}
+
+
+
+
+
+
{/if}
+
{label}
â–¼
diff --git a/src/lib/nodes/ComfyActionNodes.ts b/src/lib/nodes/ComfyActionNodes.ts
index a4489ff..ca6533c 100644
--- a/src/lib/nodes/ComfyActionNodes.ts
+++ b/src/lib/nodes/ComfyActionNodes.ts
@@ -7,6 +7,7 @@ import { get } from "svelte/store";
import ComfyGraphNode, { type ComfyGraphNodeProperties } from "./ComfyGraphNode";
import type { ComfyWidgetNode, GalleryOutput, GalleryOutputEntry } from "./ComfyWidgetNodes";
import type { NotifyOptions } from "$lib/notify";
+import type { FileData as GradioFileData } from "@gradio/upload";
import { convertComfyOutputToGradio, uploadImageToComfyUI, type ComfyUploadImageAPIResponse } from "$lib/utils";
export class ComfyQueueEvents extends ComfyGraphNode {
@@ -659,3 +660,54 @@ LiteGraph.registerNodeType({
desc: "Uploads an image from the specified ComfyUI folder into its input folder",
type: "actions/store_images"
})
+
+export interface ComfySetPromptThumbnailsActionProperties extends ComfyGraphNodeProperties {
+ defaultFolderType: string | null
+}
+
+export class ComfySetPromptThumbnailsAction extends ComfyGraphNode {
+ override properties: ComfySetPromptThumbnailsActionProperties = {
+ tags: [],
+ defaultFolderType: "input",
+ }
+
+ static slotLayout: SlotLayout = {
+ inputs: [
+ { name: "filenames", type: "*" },
+ ]
+ }
+
+ _value: any = null;
+
+ override getPromptThumbnails(): GalleryOutputEntry[] | null {
+ const data = this.getInputData(0)
+
+ const folderType = this.properties.folderType || "input";
+
+ const convertString = (s: string): GalleryOutputEntry => {
+ return { filename: data, subfolder: "", type: folderType }
+ }
+
+ if (typeof data === "string") {
+ return [convertString(data)]
+ }
+ else if (data != null && typeof data === "object") {
+ if ("filename" in data && "type" in data)
+ return [data as GalleryOutputEntry];
+ }
+ else if (Array.isArray(data) && data.length > 0) {
+ if (typeof data[0] === "string")
+ return data.map(convertString)
+ else if (typeof data[0] === "object" && "filename" in data[0] && "type" in data[0])
+ return data as GalleryOutputEntry[]
+ }
+ return null;
+ }
+}
+
+LiteGraph.registerNodeType({
+ class: ComfySetPromptThumbnailsAction,
+ title: "Comfy.SetPromptThumbnailsAction",
+ desc: "When a subgraph containing this node is executed, sets the thumbnails in the queue sidebar to the input filename(s).",
+ type: "actions/set_prompt_thumbnails"
+})
diff --git a/src/lib/nodes/ComfyBackendNode.ts b/src/lib/nodes/ComfyBackendNode.ts
index 4c1e3c3..c6039e6 100644
--- a/src/lib/nodes/ComfyBackendNode.ts
+++ b/src/lib/nodes/ComfyBackendNode.ts
@@ -1,7 +1,7 @@
import LGraphCanvas from "@litegraph-ts/core/src/LGraphCanvas";
import ComfyGraphNode from "./ComfyGraphNode";
import ComfyWidgets from "$lib/widgets"
-import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
+import type { ComfyWidgetNode, GalleryOutput } from "./ComfyWidgetNodes";
import { BuiltInSlotType, type SerializedLGraphNode } from "@litegraph-ts/core";
import type IComfyInputSlot from "$lib/IComfyInputSlot";
import type { ComfyInputConfig } from "$lib/IComfyInputSlot";
@@ -110,7 +110,7 @@ export class ComfyBackendNode extends ComfyGraphNode {
}
}
- override onExecuted(outputData: any) {
+ override onExecuted(outputData: GalleryOutput) {
console.warn("onExecuted outputs", outputData)
this.triggerSlot(0, outputData)
}
diff --git a/src/lib/nodes/ComfyGraphNode.ts b/src/lib/nodes/ComfyGraphNode.ts
index 9d8924b..b91c0ac 100644
--- a/src/lib/nodes/ComfyGraphNode.ts
+++ b/src/lib/nodes/ComfyGraphNode.ts
@@ -3,7 +3,7 @@ import type { SerializedPrompt } from "$lib/components/ComfyApp";
import type ComfyWidget from "$lib/components/widgets/ComfyWidget";
import { LGraph, LGraphNode, LLink, LiteGraph, NodeMode, type INodeInputSlot, type SerializedLGraphNode, type Vector2, type INodeOutputSlot, LConnectionKind, type SlotType, LGraphCanvas, getStaticPropertyOnInstance, type PropertyLayout, type SlotLayout } from "@litegraph-ts/core";
import type { SvelteComponentDev } from "svelte/internal";
-import type { ComfyWidgetNode } from "./ComfyWidgetNodes";
+import type { ComfyWidgetNode, GalleryOutput, GalleryOutputEntry } from "./ComfyWidgetNodes";
import type IComfyInputSlot from "$lib/IComfyInputSlot";
import uiState from "$lib/stores/uiState";
import { get } from "svelte/store";
@@ -48,7 +48,14 @@ export default class ComfyGraphNode extends LGraphNode {
* Triggered when the backend sends a finished output back with this node's ID.
* Valid for output nodes like SaveImage and PreviewImage.
*/
- onExecuted?(output: any): void;
+ onExecuted?(output: GalleryOutput): void;
+
+ /*
+ * When a prompt is queued, this will be called on the node if it can
+ * provide any thumbnails for use with the prompt queue. Useful for HR Fix
+ * or img2img workloads.
+ */
+ getPromptThumbnails?(): GalleryOutputEntry[] | null
/*
* Allows you to manually specify an auto-config for certain input slot
diff --git a/src/lib/nodes/ComfyWidgetNodes.ts b/src/lib/nodes/ComfyWidgetNodes.ts
index b64928e..e3e9667 100644
--- a/src/lib/nodes/ComfyWidgetNodes.ts
+++ b/src/lib/nodes/ComfyWidgetNodes.ts
@@ -161,7 +161,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode {
}
private triggerChangeEvent(value: any) {
- console.debug("[Widget] trigger changed", this, value)
+ // console.debug("[Widget] trigger changed", this, value)
const changedOutput = this.outputs[this.changedIndex]
if (changedOutput.type === BuiltInSlotType.EVENT)
this.triggerSlot(this.changedIndex, value)
@@ -271,7 +271,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode {
}
}
- console.debug("Property copy", input, this.properties)
+ // console.debug("Property copy", input, this.properties)
this.setValue(get(this.value))
@@ -288,7 +288,7 @@ export abstract class ComfyWidgetNode extends ComfyGraphNode {
if (layoutEntry && layoutEntry.parent) {
layoutEntry.parent.attrsChanged.set(get(layoutEntry.parent.attrsChanged) + 1)
}
- console.debug("propsChanged", this)
+ // console.debug("propsChanged", this)
this.propsChanged.set(get(this.propsChanged) + 1)
}
diff --git a/src/lib/notify.ts b/src/lib/notify.ts
index 531c0ba..7808ef7 100644
--- a/src/lib/notify.ts
+++ b/src/lib/notify.ts
@@ -45,6 +45,11 @@ function notifyToast(text: string, options: NotifyOptions) {
'--toastBackground': 'var(--color-blue-500)',
}
}
+ else if (options.type === "warning") {
+ toastOptions.theme = {
+ '--toastBackground': 'var(--color-yellow-500)',
+ }
+ }
else if (options.type === "error") {
toastOptions.theme = {
'--toastBackground': 'var(--color-red-500)',
diff --git a/src/lib/stores/layoutState.ts b/src/lib/stores/layoutState.ts
index a74b18b..19e79c9 100644
--- a/src/lib/stores/layoutState.ts
+++ b/src/lib/stores/layoutState.ts
@@ -1,7 +1,7 @@
import { get, writable } from 'svelte/store';
import type { Readable, Writable } from 'svelte/store';
import type ComfyApp from "$lib/components/ComfyApp"
-import { type LGraphNode, type IWidget, type LGraph, NodeMode } from "@litegraph-ts/core"
+import { type LGraphNode, type IWidget, type LGraph, NodeMode, type LGraphRemoveNodeOptions, type LGraphAddNodeOptions } from "@litegraph-ts/core"
import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
import type { ComfyWidgetNode } from '$lib/nodes';
@@ -79,7 +79,7 @@ export type LayoutState = {
* If true, a saved workflow is being deserialized, so ignore any
* nodeAdded/nodeRemoved events.
*
- * TODO: instead use LGraphAddNodeOptions.addedByDeserialize
+ * TODO: instead use LGraphAddNodeOptions.addedBy
*/
isConfiguring: boolean,
@@ -649,8 +649,8 @@ type LayoutStateOps = {
addWidget: (parent: ContainerLayout, node: ComfyWidgetNode, attrs: Partial, index?: number) => WidgetLayout,
findDefaultContainerForInsertion: () => ContainerLayout | null,
updateChildren: (parent: IDragItem, children: IDragItem[]) => IDragItem[],
- nodeAdded: (node: LGraphNode) => void,
- nodeRemoved: (node: LGraphNode) => void,
+ nodeAdded: (node: LGraphNode, options: LGraphAddNodeOptions) => void,
+ nodeRemoved: (node: LGraphNode, options: LGraphRemoveNodeOptions) => void,
groupItems: (dragItems: IDragItem[], attrs?: Partial) => ContainerLayout,
ungroup: (container: ContainerLayout) => void,
getCurrentSelection: () => IDragItem[],
@@ -757,19 +757,6 @@ function updateChildren(parent: IDragItem, newChildren?: IDragItem[]): IDragItem
return state.allItems[parent.id].children
}
-function nodeAdded(node: LGraphNode) {
- const state = get(store)
- if (state.isConfiguring)
- return;
-
- const parent = findDefaultContainerForInsertion();
-
- console.debug("[layoutState] nodeAdded", node)
- if ("svelteComponentType" in node) {
- addWidget(parent, node as ComfyWidgetNode);
- }
-}
-
function removeEntry(state: LayoutState, id: DragItemID) {
const entry = state.allItems[id]
if (entry.children && entry.children.length > 0) {
@@ -788,7 +775,34 @@ function removeEntry(state: LayoutState, id: DragItemID) {
delete state.allItems[id]
}
-function nodeRemoved(node: LGraphNode) {
+function nodeAdded(node: LGraphNode, options: LGraphAddNodeOptions) {
+ const state = get(store)
+ if (state.isConfiguring)
+ return;
+
+ if (options.addedBy === "moveIntoSubgraph" || options.addedBy === "moveOutOfSubgraph") {
+ // All we need to do is update the nodeID linked to this node.
+ const item = state.allItemsByNode[options.prevNodeId]
+ delete state.allItemsByNode[options.prevNodeId]
+ state.allItemsByNode[node.id] = item
+ return;
+ }
+
+ const parent = findDefaultContainerForInsertion();
+
+ console.debug("[layoutState] nodeAdded", node)
+ if ("svelteComponentType" in node) {
+ addWidget(parent, node as ComfyWidgetNode);
+ }
+}
+
+function nodeRemoved(node: LGraphNode, options: LGraphRemoveNodeOptions) {
+ if (options.removedBy === "moveIntoSubgraph" || options.removedBy === "moveOutOfSubgraph") {
+ // This node is being moved into a subgraph, so it will be readded under
+ // a new node ID shortly.
+ return
+ }
+
const state = get(store)
console.debug("[layoutState] nodeRemoved", node)
diff --git a/src/lib/stores/queueState.ts b/src/lib/stores/queueState.ts
index 8f3076f..13ee1a1 100644
--- a/src/lib/stores/queueState.ts
+++ b/src/lib/stores/queueState.ts
@@ -1,54 +1,287 @@
-import type { ComfyAPIQueueStatus } from "$lib/api";
-import type { Progress } from "$lib/components/ComfyApp";
-import { writable, type Writable } from "svelte/store";
+import type { ComfyAPIHistoryEntry, ComfyAPIHistoryItem, ComfyAPIHistoryResponse, ComfyAPIQueueResponse, ComfyAPIStatusResponse, ComfyPromptExtraData, NodeID, PromptID } from "$lib/api";
+import type { Progress, SerializedPromptInputsAll, SerializedPromptOutputs } from "$lib/components/ComfyApp";
+import type { GalleryOutput } from "$lib/nodes/ComfyWidgetNodes";
+import notify from "$lib/notify";
+import { get, writable, type Writable } from "svelte/store";
export type QueueItem = {
name: string
}
+export type QueueEntryStatus = "success" | "error" | "interrupted" | "all_cached" | "unknown";
+
type QueueStateOps = {
- statusUpdated: (status: ComfyAPIQueueStatus | null) => void,
- executingUpdated: (runningNodeId: string | null) => void,
- progressUpdated: (progress: Progress | null) => void
+ queueUpdated: (resp: ComfyAPIQueueResponse) => void,
+ historyUpdated: (resp: ComfyAPIHistoryResponse) => void,
+ statusUpdated: (status: ComfyAPIStatusResponse | null) => void,
+ executingUpdated: (promptID: PromptID | null, runningNodeID: NodeID | null) => void,
+ executionCached: (promptID: PromptID, nodes: NodeID[]) => void,
+ executionError: (promptID: PromptID, message: string) => void,
+ progressUpdated: (progress: Progress) => void
+ afterQueued: (promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) => void
+ onExecuted: (promptID: PromptID, nodeID: NodeID, output: GalleryOutput) => void
+}
+
+export type QueueEntry = {
+ /* Data preserved on page refresh */
+ number: number,
+ queuedAt?: Date,
+ finishedAt?: Date,
+ promptID: PromptID,
+ prompt: SerializedPromptInputsAll,
+ extraData: ComfyPromptExtraData,
+ goodOutputs: NodeID[],
+
+ /* Data not sent by Comfy's API, lost on page refresh */
+ /* Prompt outputs, collected while the prompt is still executing */
+ outputs: SerializedPromptOutputs,
+
+ /* Nodes in of the workflow that have finished running so far. */
+ nodesRan: Set,
+ cachedNodes: Set
+}
+
+export type CompletedQueueEntry = {
+ entry: QueueEntry,
+ status: QueueEntryStatus,
+ message?: string,
+ error?: string,
}
export type QueueState = {
+ queueRunning: Writable,
+ queuePending: Writable,
+ queueCompleted: Writable,
queueRemaining: number | "X" | null;
- runningNodeId: number | null;
- progress: Progress | null
+ runningNodeID: number | null;
+ progress: Progress | null,
+ isInterrupting: boolean
}
type WritableQueueStateStore = Writable & QueueStateOps;
-const store: Writable = writable({ queueRemaining: null, runningNodeId: null, progress: null })
+const store: Writable = writable({
+ queueRunning: writable([]),
+ queuePending: writable([]),
+ queueCompleted: writable([]),
+ queueRemaining: null,
+ runningNodeID: null,
+ progress: null,
+ isInterrupting: false
+})
-function statusUpdated(status: ComfyAPIQueueStatus | null) {
+function toQueueEntry(resp: ComfyAPIHistoryItem): QueueEntry {
+ const [num, promptID, prompt, extraData, goodOutputs] = resp
+ return {
+ number: num,
+ queuedAt: null, // TODO when ComfyUI passes the date
+ finishedAt: null,
+ promptID,
+ prompt,
+ extraData,
+ goodOutputs,
+ outputs: {},
+ nodesRan: new Set(), // TODO can ComfyUI send this too?
+ cachedNodes: new Set()
+ }
+}
+
+function toCompletedQueueEntry(resp: ComfyAPIHistoryEntry): CompletedQueueEntry {
+ const entry = toQueueEntry(resp.prompt)
+ entry.outputs = resp.outputs;
+ return {
+ entry,
+ status: Object.values(entry.outputs).length > 0 ? "success" : "all_cached",
+ error: null
+ }
+}
+
+function queueUpdated(resp: ComfyAPIQueueResponse) {
+ console.debug("[queueState] queueUpdated", resp.running.length, resp.pending.length)
store.update((s) => {
- if (status !== null)
- s.queueRemaining = status.exec_info.queue_remaining;
+ s.queueRunning.set(resp.running.map(toQueueEntry));
+ s.queuePending.set(resp.pending.map(toQueueEntry));
+ s.queueRemaining = resp.pending.length;
return s
})
}
-function executingUpdated(runningNodeId: string | null) {
+function historyUpdated(resp: ComfyAPIHistoryResponse) {
+ console.debug("[queueState] historyUpdated", Object.values(resp.history).length)
store.update((s) => {
- s.progress = null;
- s.runningNodeId = parseInt(runningNodeId);
+ const values = Object.values(resp.history) // TODO Order by prompt finished date!
+ s.queueCompleted.set(values.map(toCompletedQueueEntry));
return s
})
}
-function progressUpdated(progress: Progress | null) {
+function progressUpdated(progress: Progress) {
+ // console.debug("[queueState] progressUpdated", progress)
store.update((s) => {
s.progress = progress;
return s
})
}
+function statusUpdated(status: ComfyAPIStatusResponse | null) {
+ console.debug("[queueState] statusUpdated", status)
+ store.update((s) => {
+ if (status !== null)
+ s.queueRemaining = status.execInfo.queueRemaining;
+ return s
+ })
+}
+
+function findEntryInPending(promptID: PromptID): [number, QueueEntry | null, Writable | null] {
+ const state = get(store);
+ let index = get(state.queuePending).findIndex(e => e.promptID === promptID)
+ if (index !== -1)
+ return [index, get(state.queuePending)[index], state.queuePending]
+
+ index = get(state.queueRunning).findIndex(e => e.promptID === promptID)
+ if (index !== -1)
+ return [index, get(state.queueRunning)[index], state.queueRunning]
+
+ return [-1, null, null]
+}
+
+function moveToCompleted(index: number, queue: Writable, status: QueueEntryStatus, message?: string, error?: string) {
+ const state = get(store)
+
+ const entry = get(queue)[index];
+ console.debug("[queueState] Move to completed", entry.promptID, index, status, message, error)
+ entry.finishedAt = new Date() // Now
+ queue.update(qp => { qp.splice(index, 1); return qp });
+ state.queueCompleted.update(qc => {
+ const completed: CompletedQueueEntry = { entry, status, message, error }
+ qc.push(completed)
+ return qc
+ })
+
+ store.set(state)
+}
+
+function executingUpdated(promptID: PromptID, runningNodeID: NodeID | null) {
+ console.debug("[queueState] executingUpdated", promptID, runningNodeID)
+ store.update((s) => {
+ s.progress = null;
+
+ const [index, entry, queue] = findEntryInPending(promptID);
+ if (runningNodeID != null) {
+ if (entry != null) {
+ entry.nodesRan.add(runningNodeID)
+ }
+ s.runningNodeID = parseInt(runningNodeID);
+ }
+ else {
+ // Prompt finished executing.
+ if (entry != null) {
+ const totalNodesInPrompt = Object.keys(entry.prompt).length
+ if (entry.cachedNodes.size >= Object.keys(entry.prompt).length) {
+ notify("Prompt was cached, nothing to run.", { type: "warning" })
+ moveToCompleted(index, queue, "all_cached", "(Execution was cached)");
+ }
+ else if (entry.nodesRan.size >= totalNodesInPrompt) {
+ moveToCompleted(index, queue, "success")
+ }
+ else {
+ notify("Interrupted prompt.")
+ moveToCompleted(index, queue, "interrupted", `Interrupted after ${entry.nodesRan.size}/${totalNodesInPrompt} nodes`)
+ }
+ }
+ else {
+ console.debug("[queueState] Could not find in pending! (executingUpdated)", promptID)
+ }
+ s.progress = null;
+ s.runningNodeID = null;
+ }
+ return s
+ })
+}
+
+function executionCached(promptID: PromptID, nodes: NodeID[]) {
+ console.debug("[queueState] executionCached", promptID, nodes)
+ store.update(s => {
+ const [index, entry, queue] = findEntryInPending(promptID);
+ if (entry != null) {
+ for (const nodeID of nodes) {
+ entry.nodesRan.add(nodeID);
+ entry.cachedNodes.add(nodeID);
+ }
+ }
+ else {
+ console.error("[queueState] Could not find in pending! (executionCached)", promptID, "pending", JSON.stringify(get(get(store).queuePending).map(p => p.promptID)), "running", JSON.stringify(get(get(store).queueRunning).map(p => p.promptID)))
+ }
+ s.isInterrupting = false; // TODO move to start
+ s.progress = null;
+ s.runningNodeID = null;
+ return s
+ })
+}
+
+function executionError(promptID: PromptID, message: string) {
+ console.debug("[queueState] executionError", promptID, message)
+ store.update(s => {
+ const [index, entry, queue] = findEntryInPending(promptID);
+ if (entry != null) {
+ moveToCompleted(index, queue, "error", "Error executing", message)
+ }
+ else {
+ console.error("[queueState] Could not find in pending! (executionError)", promptID)
+ }
+ s.progress = null;
+ s.runningNodeID = null;
+ return s
+ })
+}
+
+function afterQueued(promptID: PromptID, number: number, prompt: SerializedPromptInputsAll, extraData: any) {
+ console.debug("[queueState] afterQueued", promptID, Object.keys(prompt))
+ store.update(s => {
+ const entry: QueueEntry = {
+ number,
+ queuedAt: new Date(), // Now
+ finishedAt: null,
+ promptID,
+ prompt,
+ extraData,
+ goodOutputs: [],
+ outputs: {},
+ nodesRan: new Set(),
+ cachedNodes: new Set()
+ }
+ s.queuePending.update(qp => { qp.push(entry); return qp })
+ console.debug("[queueState] ADD PROMPT", promptID)
+ s.isInterrupting = false;
+ return s
+ })
+}
+
+function onExecuted(promptID: PromptID, nodeID: NodeID, output: GalleryOutput) {
+ console.debug("[queueState] onExecuted", promptID, nodeID, output)
+ store.update(s => {
+ const [index, entry, queue] = findEntryInPending(promptID)
+ if (entry != null) {
+ entry.outputs[nodeID] = output;
+ queue.set(get(queue))
+ }
+ else {
+ console.error("[queueState] Could not find in pending! (onExecuted)", promptID)
+ }
+ return s
+ })
+}
+
const queueStateStore: WritableQueueStateStore =
{
...store,
+ queueUpdated,
+ historyUpdated,
statusUpdated,
+ progressUpdated,
executingUpdated,
- progressUpdated
+ executionCached,
+ executionError,
+ afterQueued,
+ onExecuted
}
export default queueStateStore;
diff --git a/src/lib/stores/uiState.ts b/src/lib/stores/uiState.ts
index 332fb10..c827741 100644
--- a/src/lib/stores/uiState.ts
+++ b/src/lib/stores/uiState.ts
@@ -10,11 +10,17 @@ export type UIState = {
uiUnlocked: boolean,
uiEditMode: UIEditMode,
+ reconnecting: boolean,
isSavingToLocalStorage: boolean
}
-export type WritableUIStateStore = Writable;
-const store: WritableUIStateStore = writable(
+type UIStateOps = {
+ reconnecting: () => void,
+ reconnected: () => void,
+}
+
+export type WritableUIStateStore = Writable & UIStateOps;
+const store: Writable = writable(
{
graphLocked: false,
nodesLocked: false,
@@ -22,11 +28,22 @@ const store: WritableUIStateStore = writable(
uiUnlocked: false,
uiEditMode: "widgets",
+ reconnecting: false,
isSavingToLocalStorage: false
})
+function reconnecting() {
+ store.update(s => { s.reconnecting = true; return s; })
+}
+
+function reconnected() {
+ store.update(s => { s.reconnecting = false; return s; })
+}
+
const uiStateStore: WritableUIStateStore =
{
- ...store
+ ...store,
+ reconnecting,
+ reconnected
}
export default uiStateStore;
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 62e72a0..c9378f3 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -13,6 +13,10 @@ export function clamp(n: number, min: number, max: number): number {
return Math.min(Math.max(n, min), max)
}
+export function negmod(n: number, m: number): number {
+ return ((n % m) + m) % m;
+}
+
export function range(size: number, startAt: number = 0): ReadonlyArray {
return [...Array(size).keys()].map(i => i + startAt);
}
@@ -104,7 +108,7 @@ export function promptToGraphVis(prompt: SerializedPrompt): string {
export function getNodeInfo(nodeId: number): string {
let app = (window as any).app;
- if (!app)
+ if (!app || !app.lGraph)
return String(nodeId);
const title = app.lGraph.getNodeById(nodeId)?.title || String(nodeId);
@@ -135,6 +139,12 @@ export function convertComfyOutputToGradio(output: GalleryOutput): GradioFileDat
});
}
+export function convertComfyOutputToComfyURL(output: GalleryOutputEntry): string {
+ const params = new URLSearchParams(output)
+ const url = `http://${location.hostname}:8188` // TODO make configurable
+ return url + "/view?" + params
+}
+
export function convertFilenameToComfyURL(filename: string,
subfolder: string = "",
type: "input" | "output" | "temp" = "output"): string {
diff --git a/src/lib/widgets/ComboWidget.svelte b/src/lib/widgets/ComboWidget.svelte
index 30c4880..999813a 100644
--- a/src/lib/widgets/ComboWidget.svelte
+++ b/src/lib/widgets/ComboWidget.svelte
@@ -246,6 +246,22 @@
width: auto;
--font-size: 13px;
--height: 32px;
+ --background: var(--input-background-fill);
+ --selected-item-color: var(--body-text-color);
+ --input-color: var(--body-text-color);
+ --chevron-color: var(--body-text-color);
+ --border: 1px solid var(--input-border-color);
+ --border-hover: 1px solid var(--input-border-color-hover);
+ --border-focused: 1px solid var(--input-border-color-focus);
+ --border-radius-focused: 0px;
+ --border-radius: 0px;
+ --list-background: var(--comfy-dropdown-list-background);
+ --item-border: var(--comfy-dropdown-border-color);
+ --item-color: var(--body-text-color);
+ --item-color-hover: var(--comfy-dropdown-item-color-hover);
+ --item-background-hover: var(--comfy-dropdown-item-background-hover);
+ --item-color-active: var(--comfy-dropdown-item-color-active);
+ --item-background-active: var(--comfy-dropdown-item-background-active);
}
:global(.svelte-select-list) {
@@ -263,10 +279,11 @@
.comfy-select-list {
width: 30rem;
+ color: var(--item-color);
> :global(.virtual-list-wrapper) {
box-shadow: var(--block-shadow);
- background-color: white;
+ background-color: var(--list-background);
}
.comfy-empty-list {
@@ -279,35 +296,34 @@
}
.comfy-select-item {
- border: 1px solid var(--neutral-300);
+ border: 1px solid var(--item-border);
border-top: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: flex;
align-items: center;
- background-color: white;
+ background-color: var(--list-background);
font-size: 14px;
padding: 0.2rem;
+ .comfy-select-label {
+ }
+
&.mobile {
font-size: 16px;
padding: 1.2rem;
}
&.hover {
- color: white;
- background: var(--neutral-400);
+ color: var(--item-color-hover);
+ background: var(--item-background-hover);
cursor: pointer;
}
&.active {
- color: white;
- background: var(--color-blue-500);
- }
-
- .comfy-select-label {
-
+ color: var(--item-color-active);
+ background: var(--item-background-active);
}
}
diff --git a/src/lib/widgets/GalleryWidget.svelte b/src/lib/widgets/GalleryWidget.svelte
index 8729025..0ffdd3a 100644
--- a/src/lib/widgets/GalleryWidget.svelte
+++ b/src/lib/widgets/GalleryWidget.svelte
@@ -122,14 +122,14 @@
// the event might fire too early
const callback = isMobile ? setupImageForMobileLightbox
- : ImageViewer.instance.setupImageForLightbox.bind(ImageViewer.instance)
+ : ImageViewer.instance.setupGalleryImageForLightbox.bind(ImageViewer.instance)
setTimeout(() => {
const images = element.querySelectorAll('div.block div > img')
if (images != null) {
images.forEach(callback);
}
- ImageViewer.instance.updateOnBackgroundChange();
+ ImageViewer.instance.refreshImages();
}, 200)
// Update index
diff --git a/src/lib/widgets/TextWidget.svelte b/src/lib/widgets/TextWidget.svelte
index bba0dc1..79c8dbc 100644
--- a/src/lib/widgets/TextWidget.svelte
+++ b/src/lib/widgets/TextWidget.svelte
@@ -51,12 +51,7 @@
padding: 2px;
width: 100%;
- :global(input[type=text]:disabled) {
- @include disable-input;
- }
- :global(textarea:disabled) {
- @include disable-input;
- }
+ @include disable-inputs;
}
:global(span.hide) {
diff --git a/src/main-mobile.ts b/src/main-mobile.ts
index c62b15d..50655a4 100644
--- a/src/main-mobile.ts
+++ b/src/main-mobile.ts
@@ -5,6 +5,7 @@ import { f7 } from 'framework7-svelte';
import ComfyApp from '$lib/components/ComfyApp';
import uiState from '$lib/stores/uiState';
import { LiteGraph } from '@litegraph-ts/core';
+import ComfyGraph from '$lib/ComfyGraph';
Framework7.use(Framework7Svelte);
diff --git a/src/mobile/GenToolbar.svelte b/src/mobile/GenToolbar.svelte
index a2be969..fb2c9ac 100644
--- a/src/mobile/GenToolbar.svelte
+++ b/src/mobile/GenToolbar.svelte
@@ -57,9 +57,9 @@
- {#if $queueState.runningNodeId || $queueState.progress}
+ {#if $queueState.runningNodeID || $queueState.progress}
diff --git a/src/mobile/index.html b/src/mobile/index.html
deleted file mode 100644
index c2e2ab8..0000000
--- a/src/mobile/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- Node: {getNodeInfo($queueState.runningNodeId)}
+ Node: {getNodeInfo($queueState.runningNodeID)}
-
-
-
diff --git a/src/scss/global.scss b/src/scss/global.scss
index 2663526..18fd3e8 100644
--- a/src/scss/global.scss
+++ b/src/scss/global.scss
@@ -7,14 +7,154 @@ body {
overscroll-behavior-y: contain;
}
+#app {
+ background: var(--body-background-fill);
+}
+
:root {
--color-blue-500: #3985f5;
+
+ --comfy-accent-soft: var(--neutral-300);
+ --comfy-disabled-label-color: var(--neutral-400);
+ --comfy-disabled-textbox-background-fill: var(--neutral-200);
+ --comfy-disabled-textbox-border-color: var(--neutral-300);
+ --comfy-disabled-textbox-text-color: var(--neutral-500);
+ --comfy-splitpanes-background-fill: var(--secondary-100);
+ --comfy-splitpanes-background-fill-hover: var(--secondary-300);
+ --comfy-splitpanes-background-fill-active: var(--secondary-400);
+ --comfy-dropdown-item-color-hover: white;
+ --comfy-dropdown-item-background-hover: var(--neutral-400);
+ --comfy-dropdown-item-color-active: var(--neutral-100);
+ --comfy-dropdown-item-background-active: var(--secondary-600);
+ --comfy-progress-bar-background: var(--neutral-300);
+ --comfy-progress-bar-foreground: var(--secondary-300);
+ --comfy-node-name-background: var(--color-red-300);
+ --comfy-node-name-foreground: var(--body-text-color);
+ --comfy-spinner-main-color: var(--neutral-400);
+ --comfy-spinner-accent-color: var(--secondary-500);
+}
+
+.dark {
+ color-scheme: dark;
+
+ --comfy-accent-soft: var(--neutral-600);
+ --comfy-disabled-label-color: var(--neutral-500);
+ --comfy-disabled-textbox-background-fill: var(--neutral-800);
+ --comfy-disabled-textbox-border-color: var(--neutral-700);
+ --comfy-disabled-textbox-text-color: var(--neutral-500);
+ --comfy-splitpanes-background-fill: var(--panel-border-color);
+ --comfy-splitpanes-background-fill-hover: var(--secondary-500);
+ --comfy-splitpanes-background-fill-active: var(--secondary-300);
+ --comfy-dropdown-list-background: var(--neutral-800);
+ --comfy-dropdown-item-color-hover: var(--neutral-400);
+ --comfy-dropdown-item-background-hover: var(--neutral-600);
+ --comfy-dropdown-item-background-active: var(--secondary-600);
+ --comfy-dropdown-border-color: var(--neutral-600);
+ --comfy-progress-bar-background: var(--neutral-500);
+ --comfy-progress-bar-foreground: var(--secondary-400);
+ --comfy-node-name-background: var(--neutral-700);
+ --comfy-node-name-foreground: var(--body-text-color);
+ --comfy-spinner-main-color: var(--neutral-600);
+ --comfy-spinner-accent-color: var(--secondary-600);
+}
+
+.mobile {
+ --comfy-progress-bar-background: lightgrey;
+ --comfy-progress-bar-foreground: #B3D8A9
}
@mixin disable-input {
- -webkit-text-fill-color: var(--neutral-500);
- background-color: var(--neutral-200);
- border-color: var(--neutral-300);
+ -webkit-text-fill-color: var(--comfy-disabled-textbox-text-color);
+ background-color: var(--comfy-disabled-textbox-background-fill);
+ border-color: var(--comfy-disabled-textbox-border-color);
box-shadow: 0 0 0 var(--shadow-spread) transparent, rgba(0, 0, 0, 0.08) 0px 2px 4px 0px inset;
cursor: not-allowed;
}
+
+@mixin disable-inputs {
+ :global(input[type=text]:disabled) {
+ @include disable-input;
+ }
+ :global(textarea:disabled) {
+ @include disable-input;
+ }
+ :global(label:has(input:disabled) > span) {
+ color: var(--comfy-disabled-label-color);
+ }
+ :global(label:has(textarea:disabled) > span) {
+ color: var(--comfy-disabled-label-color);
+ }
+}
+
+hr {
+ color: var(--panel-border-color);
+}
+
+select {
+ color: var(--body-text-color);
+ background: var(--block-background-fill);
+}
+
+.container {
+ background: var(--body-background-fill) !important;
+ &.selected {
+ background: var(--ae-primary-color) !important;
+ > .block.padded {
+ background: var(--ae-primary-color) !important;
+ }
+ }
+ > .block {
+ background: var(--body-background-fill) !important;
+ // border-radius: var(--ae-panel-border-radius) !important;
+ }
+ &.z-index0 {
+ > .block {
+ background: var(--panel-background-fill) !important;
+ }
+ }
+
+ // &:not(.edit) {
+ // &.z-index1 > .block {
+ // padding: calc(var(--ae-outside-gap-size) / 2) !important;
+ // border-width: 0px !important;
+ // }
+
+ // > .block {
+ // border: solid var(--ae-panel-border-width) var(--ae-panel-border-color) !important;
+ // }
+ // }
+}
+
+// button {
+// filter: none;
+// &.primary:active {
+// filter: brightness(80%)
+// }
+// &.secondary:active {
+// filter: brightness(80%)
+// }
+// }
+
+
+button {
+ &.primary:active {
+ border-color: var(--button-primary-border-color-active) !important;
+ background: var(--button-primary-background-fill-active) !important;
+ color: var(--button-primary-text-color-active) !important;
+ }
+ &.secondary:active {
+ border-color: var(--button-secondary-border-color-active) !important;
+ background: var(--button-secondary-background-fill-active) !important;
+ color: var(--button-secondary-text-color-active) !important;
+ }
+}
+
+.widget {
+ // padding: var(--ae-outside-gap-size);
+ // border: 1px solid var(--ae-panel-border-color);
+ .block {
+ // background: var(--ae-frame-bg-color) !important;
+ background: var(--block-background-fill) !important;
+ }
+
+}
diff --git a/src/scss/gradio.scss b/src/scss/gradio.scss
index 21d6a27..27df95a 100644
--- a/src/scss/gradio.scss
+++ b/src/scss/gradio.scss
@@ -1,365 +1,427 @@
:root {
- --primary-50: #fff7ed;
- --primary-100: #ffedd5;
- --primary-200: #fed7aa;
- --primary-300: #fdba74;
- --primary-400: #fb923c;
- --primary-500: #f97316;
- --primary-600: #ea580c;
- --primary-700: #c2410c;
- --primary-800: #9a3412;
- --primary-900: #7c2d12;
- --primary-950: #6c2e12;
- --secondary-50: #eff6ff;
- --secondary-100: #dbeafe;
- --secondary-200: #bfdbfe;
- --secondary-300: #93c5fd;
- --secondary-400: #60a5fa;
- --secondary-500: #3b82f6;
- --secondary-600: #2563eb;
- --secondary-700: #1d4ed8;
- --secondary-800: #1e40af;
- --secondary-900: #1e3a8a;
- --secondary-950: #1d3660;
- --neutral-50: #f9fafb;
- --neutral-100: #f3f4f6;
- --neutral-200: #e5e7eb;
- --neutral-300: #d1d5db;
- --neutral-400: #9ca3af;
- --neutral-500: #6b7280;
- --neutral-600: #4b5563;
- --neutral-700: #374151;
- --neutral-800: #1f2937;
- --neutral-900: #111827;
- --neutral-950: #0b0f19;
- --spacing-xxs: 1px;
- --spacing-xs: 2px;
- --spacing-sm: 4px;
- --spacing-md: 6px;
- --spacing-lg: 8px;
- --spacing-xl: 10px;
- --spacing-xxl: 16px;
- --radius-xxs: 1px;
- --radius-xs: 2px;
- --radius-sm: 4px;
- --radius-md: 6px;
- --radius-lg: 8px;
- --radius-xl: 12px;
- --radius-xxl: 22px;
- --text-xxs: 9px;
- --text-xs: 10px;
- --text-sm: 12px;
- --text-md: 14px;
- --text-lg: 16px;
- --text-xl: 22px;
- --text-xxl: 26px;
- --color-accent: var(--primary-500);
- --color-accent-soft: var(--primary-50);
- --background-fill-primary: white;
- --background-fill-secondary: var(--neutral-50);
- --border-color-accent: var(--primary-300);
- --border-color-primary: var(--neutral-200);
- --text-color-code-background-fill: var(--neutral-200);
- --text-color-code-border: var(--border-color-primary);
- --link-text-color: var(--secondary-600);
- --link-text-color-active: var(--secondary-600);
- --link-text-color-hover: var(--secondary-700);
- --link-text-color-visited: var(--secondary-500);
- --body-text-color-subdued: var(--neutral-400);
- --body-background-fill: var(--background-fill-primary);
- --body-text-color: var(--neutral-800);
- --body-text-size: var(--text-md);
- --body-text-weight: 400;
- --embed-radius: var(--radius-lg);
- --shadow-drop: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
- --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1),
- 0 1px 2px -1px rgb(0 0 0 / 0.1);
- --shadow-inset: rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset;
- --shadow-spread: 3px;
- --block-background-fill: var(--background-fill-primary);
- --block-border-color: var(--border-color-primary);
- --block-border-width: 1px;
- --block-info-text-color: var(--body-text-color-subdued);
- --block-info-text-size: var(--text-sm);
- --block-info-text-weight: 400;
- --block-label-background-fill: var(--background-fill-primary);
- --block-label-border-color: var(--border-color-primary);
- --block-label-border-width: 1px;
- --block-label-text-color: var(--neutral-500);
- --block-label-icon-color: var(--block-label-text-color);
- --block-label-margin: 0;
- --block-label-padding: var(--spacing-sm) var(--spacing-lg);
- --block-label-radius: calc(var(--radius-lg) - 1px) 0
- calc(var(--radius-lg) - 1px) 0;
- --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0
- calc(var(--radius-lg) - 1px);
- --block-label-text-size: var(--text-sm);
- --block-label-text-weight: 400;
- --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px);
- --block-radius: var(--radius-lg);
- --block-shadow: var(--shadow-drop);
- --block-title-background-fill: none;
- --block-title-border-color: none;
- --block-title-border-width: 0px;
- --block-title-text-color: var(--neutral-500);
- --block-title-padding: 0;
- --block-title-radius: none;
- --block-title-text-size: var(--text-md);
- --block-title-text-weight: 400;
- --container-radius: var(--radius-lg);
- --form-gap-width: 1px;
- --layout-gap: var(--spacing-xxl);
- --panel-background-fill: var(--background-fill-secondary);
- --panel-border-color: var(--border-color-primary);
- --panel-border-width: 0;
- --section-header-text-size: var(--text-md);
- --section-header-text-weight: 400;
- --checkbox-background-color: var(--background-fill-primary);
- --checkbox-background-color-focus: var(--background-fill-primary);
- --checkbox-background-color-hover: var(--background-fill-primary);
- --checkbox-background-color-selected: var(--secondary-600);
- --checkbox-border-color: var(--neutral-300);
- --checkbox-border-color-focus: var(--secondary-500);
- --checkbox-border-color-hover: var(--neutral-300);
- --checkbox-border-color-selected: var(--secondary-600);
- --checkbox-border-radius: var(--radius-sm);
- --checkbox-border-width: var(--input-border-width);
- --checkbox-label-background-fill: linear-gradient(
- to top,
- var(--neutral-50),
- white
- );
- --checkbox-label-background-fill-hover: linear-gradient(
- to top,
- var(--neutral-100),
- white
- );
- --checkbox-label-background-fill-selected: var(
- --checkbox-label-background-fill
- );
- --checkbox-label-border-color: var(--border-color-primary);
- --checkbox-label-border-color-hover: var(--border-color-primary);
- --checkbox-label-border-width: var(--input-border-width);
- --checkbox-label-gap: var(--spacing-lg);
- --checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md));
- --checkbox-label-shadow: var(--shadow-drop);
- --checkbox-label-text-size: var(--text-md);
- --checkbox-label-text-weight: 400;
- --checkbox-shadow: var(--input-shadow);
- --checkbox-label-text-color: var(--body-text-color);
- --checkbox-label-text-color-selected: var(--checkbox-label-text-color);
- --error-background-fill: linear-gradient(
- to right,
- #fee2e2,
- var(--background-fill-secondary)
- );
- --error-border-color: #fecaca;
- --error-border-width: 1px;
- --error-text-color: #ef4444;
- --prose-header-text-weight: 600;
- --input-background-fill: white;
- --input-background-fill-focus: var(--secondary-500);
- --input-background-fill-hover: var(--input-background-fill);
- --input-border-color: var(--border-color-primary);
- --input-border-color-focus: var(--secondary-300);
- --input-border-color-hover: var(--border-color-primary);
- --input-border-width: 1px;
- --input-padding: var(--spacing-xl);
- --input-placeholder-color: var(--neutral-400);
- --input-radius: var(--radius-lg);
- --input-shadow: 0 0 0 var(--shadow-spread) transparent, var(--shadow-inset);
- --input-shadow-focus: 0 0 0 var(--shadow-spread) var(--secondary-50),
- var(--shadow-inset);
- --input-text-size: var(--text-md);
- --input-text-weight: 400;
- --loader-color: var(--color-accent);
- --prose-text-size: var(--text-md);
- --prose-text-weight: 400;
- --stat-background-fill: linear-gradient(
- to right,
- var(--primary-400),
- var(--primary-200)
- );
- --table-border-color: var(--neutral-300);
- --table-even-background-fill: white;
- --table-odd-background-fill: var(--neutral-50);
- --table-radius: var(--radius-lg);
- --table-row-focus: var(--color-accent-soft);
- --button-border-width: var(--input-border-width);
- --button-cancel-background-fill: linear-gradient(
- to bottom right,
- #fee2e2,
- #fecaca
- );
- --button-cancel-background-fill-hover: linear-gradient(
- to bottom right,
- #fee2e2,
- #fee2e2
- );
- --button-cancel-border-color: #fecaca;
- --button-cancel-border-color-hover: var(--button-cancel-border-color);
- --button-cancel-text-color: #dc2626;
- --button-cancel-text-color-hover: var(--button-cancel-text-color);
- --button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg));
- --button-large-radius: var(--radius-lg);
- --button-large-text-size: var(--text-lg);
- --button-large-text-weight: 600;
- --button-primary-background-fill: linear-gradient(
- to bottom right,
- var(--primary-100),
- var(--primary-300)
- );
- --button-primary-background-fill-hover: linear-gradient(
- to bottom right,
- var(--primary-100),
- var(--primary-200)
- );
- --button-primary-border-color: var(--primary-200);
- --button-primary-border-color-hover: var(--button-primary-border-color);
- --button-primary-text-color: var(--primary-600);
- --button-primary-text-color-hover: var(--button-primary-text-color);
- --button-secondary-background-fill: linear-gradient(
- to bottom right,
- var(--neutral-100),
- var(--neutral-200)
- );
- --button-secondary-background-fill-hover: linear-gradient(
- to bottom right,
- var(--neutral-100),
- var(--neutral-100)
- );
- --button-secondary-border-color: var(--neutral-200);
- --button-secondary-border-color-hover: var(--button-secondary-border-color);
- --button-secondary-text-color: var(--neutral-700);
- --button-secondary-text-color-hover: var(--button-secondary-text-color);
- --button-shadow: var(--shadow-drop);
- --button-shadow-active: var(--shadow-inset);
- --button-shadow-hover: var(--shadow-drop-lg);
- --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm));
- --button-small-radius: var(--radius-lg);
- --button-small-text-size: var(--text-md);
- --button-small-text-weight: 400;
- --button-transition: none;
+ --name: default;
+ --primary-50: #fff7ed;
+ --primary-100: #ffedd5;
+ --primary-200: #fed7aa;
+ --primary-300: #fdba74;
+ --primary-400: #fb923c;
+ --primary-500: #f97316;
+ --primary-600: #ea580c;
+ --primary-700: #c2410c;
+ --primary-800: #9a3412;
+ --primary-900: #7c2d12;
+ --primary-950: #6c2e12;
+ --secondary-50: #eff6ff;
+ --secondary-100: #dbeafe;
+ --secondary-200: #bfdbfe;
+ --secondary-300: #93c5fd;
+ --secondary-400: #60a5fa;
+ --secondary-500: #3b82f6;
+ --secondary-600: #2563eb;
+ --secondary-700: #1d4ed8;
+ --secondary-800: #1e40af;
+ --secondary-900: #1e3a8a;
+ --secondary-950: #1d3660;
+ --neutral-50: #f9fafb;
+ --neutral-100: #f3f4f6;
+ --neutral-200: #e5e7eb;
+ --neutral-300: #d1d5db;
+ --neutral-400: #9ca3af;
+ --neutral-500: #6b7280;
+ --neutral-600: #4b5563;
+ --neutral-700: #374151;
+ --neutral-800: #1f2937;
+ --neutral-900: #111827;
+ --neutral-950: #0b0f19;
+ --spacing-xxs: 1px;
+ --spacing-xs: 2px;
+ --spacing-sm: 4px;
+ --spacing-md: 6px;
+ --spacing-lg: 8px;
+ --spacing-xl: 10px;
+ --spacing-xxl: 16px;
+ --radius-xxs: 1px;
+ --radius-xs: 2px;
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 12px;
+ --radius-xxl: 22px;
+ --text-xxs: 9px;
+ --text-xs: 10px;
+ --text-sm: 12px;
+ --text-md: 14px;
+ --text-lg: 16px;
+ --text-xl: 22px;
+ --text-xxl: 26px;
+ --font: 'Source Sans Pro', 'ui-sans-serif', 'system-ui', sans-serif;
+ --font-mono: 'IBM Plex Mono', 'ui-monospace', 'Consolas', monospace;
+ --body-background-fill: var(--background-fill-primary);
+ --body-text-color: var(--neutral-800);
+ --body-text-size: var(--text-md);
+ --body-text-weight: 400;
+ --embed-radius: var(--radius-lg);
+ --color-accent: var(--primary-500);
+ --color-accent-soft: var(--primary-50);
+ --background-fill-primary: white;
+ --background-fill-secondary: var(--neutral-50);
+ --border-color-accent: var(--primary-300);
+ --border-color-primary: var(--neutral-200);
+ --link-text-color: var(--secondary-600);
+ --link-text-color-active: var(--secondary-600);
+ --link-text-color-hover: var(--secondary-700);
+ --link-text-color-visited: var(--secondary-500);
+ --body-text-color-subdued: var(--neutral-400);
+ --shadow-drop: rgba(0,0,0,0.05) 0px 1px 2px 0px;
+ --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset;
+ --shadow-spread: 3px;
+ --block-background-fill: var(--background-fill-primary);
+ --block-border-color: var(--border-color-primary);
+ --block-border-width: 1px;
+ --block-info-text-color: var(--body-text-color-subdued);
+ --block-info-text-size: var(--text-sm);
+ --block-info-text-weight: 400;
+ --block-label-background-fill: var(--background-fill-primary);
+ --block-label-border-color: var(--border-color-primary);
+ --block-label-border-width: 1px;
+ --block-label-shadow: var(--block-shadow);
+ --block-label-text-color: var(--neutral-500);
+ --block-label-margin: 0;
+ --block-label-padding: var(--spacing-sm) var(--spacing-lg);
+ --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0;
+ --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px);
+ --block-label-text-size: var(--text-sm);
+ --block-label-text-weight: 400;
+ --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px);
+ --block-radius: var(--radius-lg);
+ --block-shadow: var(--shadow-drop);
+ --block-title-background-fill: none;
+ --block-title-border-color: none;
+ --block-title-border-width: 0px;
+ --block-title-text-color: var(--neutral-500);
+ --block-title-padding: 0;
+ --block-title-radius: none;
+ --block-title-text-size: var(--text-md);
+ --block-title-text-weight: 400;
+ --container-radius: var(--radius-lg);
+ --form-gap-width: 1px;
+ --layout-gap: var(--spacing-xxl);
+ --panel-background-fill: var(--background-fill-secondary);
+ --panel-border-color: var(--border-color-primary);
+ --panel-border-width: 0;
+ --section-header-text-size: var(--text-md);
+ --section-header-text-weight: 400;
+ --checkbox-background-color: var(--background-fill-primary);
+ --checkbox-background-color-focus: var(--checkbox-background-color);
+ --checkbox-background-color-hover: var(--checkbox-background-color);
+ --checkbox-background-color-selected: var(--secondary-600);
+ --checkbox-border-color: var(--neutral-300);
+ --checkbox-border-color-focus: var(--secondary-500);
+ --checkbox-border-color-hover: var(--neutral-300);
+ --checkbox-border-color-selected: var(--secondary-600);
+ --checkbox-border-radius: var(--radius-sm);
+ --checkbox-border-width: var(--input-border-width);
+ --checkbox-label-background-fill: linear-gradient(to top, var(--neutral-50), white);
+ --checkbox-label-background-fill-hover: linear-gradient(to top, var(--neutral-100), white);
+ --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill);
+ --checkbox-label-border-color: var(--border-color-primary);
+ --checkbox-label-border-color-hover: var(--checkbox-label-border-color);
+ --checkbox-label-border-width: var(--input-border-width);
+ --checkbox-label-gap: var(--spacing-lg);
+ --checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md));
+ --checkbox-label-shadow: var(--shadow-drop);
+ --checkbox-label-text-size: var(--text-md);
+ --checkbox-label-text-weight: 400;
+ --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
+ --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
+ --checkbox-shadow: var(--input-shadow);
+ --checkbox-label-text-color: var(--body-text-color);
+ --checkbox-label-text-color-selected: var(--checkbox-label-text-color);
+ --error-background-fill: linear-gradient(to right, #fee2e2, var(--background-fill-secondary));
+ --error-border-color: #fecaca;
+ --error-border-width: 1px;
+ --error-text-color: #ef4444;
+ --input-background-fill: white;
+ --input-background-fill-focus: var(--secondary-500);
+ --input-background-fill-hover: var(--input-background-fill);
+ --input-border-color: var(--border-color-primary);
+ --input-border-color-focus: var(--secondary-300);
+ --input-border-color-hover: var(--input-border-color);
+ --input-border-width: 1px;
+ --input-padding: var(--spacing-xl);
+ --input-placeholder-color: var(--neutral-400);
+ --input-radius: var(--radius-lg);
+ --input-shadow: 0 0 0 var(--shadow-spread) transparent, var(--shadow-inset);
+ --input-shadow-focus: 0 0 0 var(--shadow-spread) var(--secondary-50), var(--shadow-inset);
+ --input-text-size: var(--text-md);
+ --input-text-weight: 400;
+ --loader-color: var(--color-accent);
+ --prose-text-size: var(--text-md);
+ --prose-text-weight: 400;
+ --prose-header-text-weight: 600;
+ --slider-color: auto;
+ --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-200));
+ --table-border-color: var(--neutral-300);
+ --table-even-background-fill: white;
+ --table-odd-background-fill: var(--neutral-50);
+ --table-radius: var(--radius-lg);
+ --table-row-focus: var(--color-accent-soft);
+ --button-border-width: var(--input-border-width);
+ --button-cancel-background-fill: linear-gradient(to bottom right, #fee2e2, #fecaca);
+ --button-cancel-background-fill-hover: linear-gradient(to bottom right, #fee2e2, #fee2e2);
+ --button-cancel-border-color: #fecaca;
+ --button-cancel-border-color-hover: var(--button-cancel-border-color);
+ --button-cancel-text-color: #dc2626;
+ --button-cancel-text-color-hover: var(--button-cancel-text-color);
+ --button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg));
+ --button-large-radius: var(--radius-lg);
+ --button-large-text-size: var(--text-lg);
+ --button-large-text-weight: 600;
+ --button-primary-background-fill: linear-gradient(to bottom right, var(--primary-100), var(--primary-300));
+ --button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-100), var(--primary-200));
+ --button-primary-background-fill-active: linear-gradient(to bottom right, var(--primary-200), var(--primary-400));
+ --button-primary-border-color: var(--primary-200);
+ --button-primary-border-color-hover: var(--button-primary-border-color);
+ --button-primary-border-color-active: var(--button-primary-border-color);
+ --button-primary-text-color: var(--primary-600);
+ --button-primary-text-color-hover: var(--button-primary-text-color);
+ --button-primary-text-color-active: var(--button-primary-text-color);
+ --button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-100), var(--neutral-200));
+ --button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-100), var(--neutral-100));
+ --button-secondary-background-fill-active: linear-gradient(to bottom right, var(--neutral-200), var(--neutral-300));
+ --button-secondary-border-color: var(--neutral-200);
+ --button-secondary-border-color-hover: var(--button-secondary-border-color);
+ --button-secondary-border-color-active: var(--button-secondary-border-color);
+ --button-secondary-text-color: var(--neutral-700);
+ --button-secondary-text-color-hover: var(--button-secondary-text-color);
+ --button-secondary-text-color-active: var(--neutral-800);
+ --button-shadow: var(--shadow-drop);
+ --button-shadow-active: var(--shadow-inset);
+ --button-shadow-hover: var(--shadow-drop-lg);
+ --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm));
+ --button-small-radius: var(--radius-lg);
+ --button-small-text-size: var(--text-md);
+ --button-small-text-weight: 400;
+ --button-transition: none;
}
+
.dark {
- --color-accent-soft: var(--neutral-900);
- --background-fill-primary: var(--neutral-950);
- --background-fill-secondary: var(--neutral-900);
- --border-color-accent: var(--neutral-600);
- --border-color-primary: var(--neutral-700);
- --text-color-code-background-fill: var(--neutral-800);
- --link-text-color-active: var(--secondary-500);
- --link-text-color: var(--secondary-500);
- --link-text-color-hover: var(--secondary-400);
- --link-text-color-visited: var(--secondary-600);
- --body-text-color-subdued: var(--neutral-400);
- --body-background-fill: var(--background-fill-primary);
- --body-text-color: var(--neutral-100);
- --shadow-spread: 1px;
- --block-background-fill: var(--neutral-800);
- --block-border-color: var(--border-color-primary);
- --block-border-width: 1px;
- --block-info-text-color: var(--body-text-color-subdued);
- --block-label-background-fill: var(--background-fill-secondary);
- --block-label-border-color: var(--border-color-primary);
- --block-label-border-width: 1px;
- --block-label-text-color: var(--neutral-200);
- --block-shadow: none;
- --block-title-background-fill: none;
- --block-title-border-color: none;
- --block-title-border-width: 0px;
- --block-title-text-color: var(--neutral-200);
- --panel-background-fill: var(--background-fill-secondary);
- --panel-border-color: var(--border-color-primary);
- --checkbox-background-color: var(--neutral-800);
- --checkbox-background-color-focus: var(--checkbox-background-color);
- --checkbox-background-color-hover: var(--checkbox-background-color);
- --checkbox-background-color-selected: var(--secondary-600);
- --checkbox-border-color: var(--neutral-700);
- --checkbox-border-color-focus: var(--secondary-500);
- --checkbox-border-color-hover: var(--neutral-600);
- --checkbox-border-color-selected: var(--secondary-600);
- --checkbox-label-background-fill: linear-gradient(
- to top,
- var(--neutral-900),
- var(--neutral-800)
- );
- --checkbox-label-background-fill-hover: linear-gradient(
- to top,
- var(--neutral-900),
- var(--neutral-800)
- );
- --checkbox-label-background-fill-selected: var(
- --checkbox-label-background-fill
- );
- --checkbox-label-border-color: var(--border-color-primary);
- --checkbox-label-border-color-hover: var(--border-color-primary);
- --checkbox-label-text-color: var(--body-text-color);
- --checkbox-label-text-color-selected: var(--checkbox-label-text-color);
- --error-background-fill: var(--background-fill-primary);
- --error-border-color: var(--border-color-primary);
- --error-border-width: var(--error-border-width);
- --error-text-color: #ef4444;
- --input-background-fill: var(--neutral-800);
- --input-background-fill-focus: var(--secondary-600);
- --input-background-fill-hover: var(--input-background-fill);
- --input-border-color: var(--border-color-primary);
- --input-border-color-focus: var(--neutral-700);
- --input-border-color-hover: var(--border-color-primary);
- --input-placeholder-color: var(--neutral-500);
- --input-shadow: var(--input-shadow);
- --input-shadow-focus: 0 0 0 var(--shadow-spread) var(--neutral-700),
- var(--shadow-inset);
- --loader-color: var(--loader-color);
- --stat-background-fill: linear-gradient(
- to right,
- var(--primary-400),
- var(--primary-600)
- );
- --table-border-color: var(--neutral-700);
- --table-even-background-fill: var(--neutral-950);
- --table-odd-background-fill: var(--neutral-900);
- --table-row-focus: var(--color-accent-soft);
- --button-cancel-background-fill: linear-gradient(
- to bottom right,
- #dc2626,
- #b91c1c
- );
- --button-cancel-background-fill-hover: linear-gradient(
- to bottom right,
- #dc2626,
- #dc2626
- );
- --button-cancel-border-color: #dc2626;
- --button-cancel-border-color-hover: var(--button-cancel-border-color);
- --button-cancel-text-color: white;
- --button-cancel-text-color-hover: var(--button-cancel-text-color);
- --button-primary-background-fill: linear-gradient(
- to bottom right,
- var(--primary-600),
- var(--primary-700)
- );
- --button-primary-background-fill-hover: linear-gradient(
- to bottom right,
- var(--primary-600),
- var(--primary-600)
- );
- --button-primary-border-color: var(--primary-600);
- --button-primary-border-color-hover: var(--button-primary-border-color);
- --button-primary-text-color: white;
- --button-primary-text-color-hover: var(--button-primary-text-color);
- --button-secondary-background-fill: linear-gradient(
- to bottom right,
- var(--neutral-600),
- var(--neutral-700)
- );
- --button-secondary-background-fill-hover: linear-gradient(
- to bottom right,
- var(--neutral-600),
- var(--neutral-600)
- );
- --button-secondary-border-color: var(--neutral-600);
- --button-secondary-border-color-hover: var(--button-secondary-border-color);
- --button-secondary-text-color: white;
- --button-secondary-text-color-hover: var(--button-secondary-text-color);
+ --body-background-fill: var(--background-fill-primary);
+ --body-text-color: var(--neutral-100);
+ --color-accent-soft: var(--neutral-700);
+ --background-fill-primary: var(--neutral-950);
+ --background-fill-secondary: var(--neutral-900);
+ --border-color-accent: var(--neutral-600);
+ --border-color-primary: var(--neutral-700);
+ --link-text-color-active: var(--secondary-500);
+ --link-text-color: var(--secondary-500);
+ --link-text-color-hover: var(--secondary-400);
+ --link-text-color-visited: var(--secondary-600);
+ --body-text-color-subdued: var(--neutral-400);
+ --shadow-spread: 1px;
+ --block-background-fill: var(--neutral-800);
+ --block-border-color: var(--border-color-primary);
+ --block_border_width: None;
+ --block-info-text-color: var(--body-text-color-subdued);
+ --block-label-background-fill: var(--background-fill-secondary);
+ --block-label-border-color: var(--border-color-primary);
+ --block_label_border_width: None;
+ --block-label-text-color: var(--neutral-200);
+ --block_shadow: None;
+ --block_title_background_fill: None;
+ --block_title_border_color: None;
+ --block_title_border_width: None;
+ --block-title-text-color: var(--neutral-200);
+ --panel-background-fill: var(--background-fill-secondary);
+ --panel-border-color: var(--border-color-primary);
+ --panel_border_width: None;
+ --checkbox-background-color: var(--neutral-800);
+ --checkbox-background-color-focus: var(--checkbox-background-color);
+ --checkbox-background-color-hover: var(--checkbox-background-color);
+ --checkbox-background-color-selected: var(--secondary-600);
+ --checkbox-border-color: var(--neutral-700);
+ --checkbox-border-color-focus: var(--secondary-500);
+ --checkbox-border-color-hover: var(--neutral-600);
+ --checkbox-border-color-selected: var(--secondary-600);
+ --checkbox-border-width: var(--input-border-width);
+ --checkbox-label-background-fill: linear-gradient(to top, var(--neutral-900), var(--neutral-800));
+ --checkbox-label-background-fill-hover: linear-gradient(to top, var(--neutral-900), var(--neutral-800));
+ --checkbox-label-background-fill-selected: var(--checkbox-label-background-fill);
+ --checkbox-label-border-color: var(--border-color-primary);
+ --checkbox-label-border-color-hover: var(--checkbox-label-border-color);
+ --checkbox-label-border-width: var(--input-border-width);
+ --checkbox-label-text-color: var(--body-text-color);
+ --checkbox-label-text-color-selected: var(--checkbox-label-text-color);
+ --error-background-fill: var(--background-fill-primary);
+ --error-border-color: var(--border-color-primary);
+ --error_border_width: None;
+ --error-text-color: #ef4444;
+ --input-background-fill: var(--neutral-800);
+ --input-background-fill-focus: var(--secondary-600);
+ --input-background-fill-hover: var(--input-background-fill);
+ --input-border-color: var(--border-color-primary);
+ --input-border-color-focus: var(--neutral-700);
+ --input-border-color-hover: var(--input-border-color);
+ --input_border_width: None;
+ --input-placeholder-color: var(--neutral-500);
+ --input_shadow: None;
+ --input-shadow-focus: 0 0 0 var(--shadow-spread) var(--neutral-700), var(--shadow-inset);
+ --loader_color: None;
+ --slider_color: None;
+ --stat-background-fill: linear-gradient(to right, var(--primary-400), var(--primary-600));
+ --table-border-color: var(--neutral-700);
+ --table-even-background-fill: var(--neutral-950);
+ --table-odd-background-fill: var(--neutral-900);
+ --table-row-focus: var(--color-accent-soft);
+ --button-border-width: var(--input-border-width);
+ --button-cancel-background-fill: linear-gradient(to bottom right, #dc2626, #b91c1c);
+ --button-cancel-background-fill-hover: linear-gradient(to bottom right, #dc2626, #dc2626);
+ --button-cancel-border-color: #dc2626;
+ --button-cancel-border-color-hover: var(--button-cancel-border-color);
+ --button-cancel-text-color: white;
+ --button-cancel-text-color-hover: var(--button-cancel-text-color);
+ --button-primary-background-fill: linear-gradient(to bottom right, var(--primary-500), var(--primary-600));
+ --button-primary-background-fill-hover: linear-gradient(to bottom right, var(--primary-500), var(--primary-500));
+ --button-primary-background-fill-active: linear-gradient(to bottom right, var(--primary-600), var(--primary-700));
+ --button-primary-border-color: var(--primary-500);
+ --button-primary-border-color-hover: var(--button-primary-border-color);
+ --button-primary-border-color-active: var(--button-primary-border-color);
+ --button-primary-text-color: white;
+ --button-primary-text-color-hover: var(--button-primary-text-color);
+ --button-primary-text-color-active: var(--neutral-300);
+ --button-secondary-background-fill: linear-gradient(to bottom right, var(--neutral-600), var(--neutral-700));
+ --button-secondary-background-fill-hover: linear-gradient(to bottom right, var(--neutral-600), var(--neutral-600));
+ --button-secondary-background-fill-active: linear-gradient(to bottom right, var(--neutral-700), var(--neutral-800));
+ --button-secondary-border-color: var(--neutral-600);
+ --button-secondary-border-color-hover: var(--button-secondary-border-color);
+ --button-secondary-border-color-active: var(--button-secondary-border-color);
+ --button-secondary-text-color: white;
+ --button-secondary-text-color-hover: var(--button-secondary-text-color);
+ --button-secondary-text-color-active: var(--neutral-300);
+ --name: default;
+ --primary-50: #fff7ed;
+ --primary-100: #ffedd5;
+ --primary-200: #fed7aa;
+ --primary-300: #fdba74;
+ --primary-400: #fb923c;
+ --primary-500: #f97316;
+ --primary-600: #ea580c;
+ --primary-700: #c2410c;
+ --primary-800: #9a3412;
+ --primary-900: #7c2d12;
+ --primary-950: #6c2e12;
+ --secondary-50: #eff6ff;
+ --secondary-100: #dbeafe;
+ --secondary-200: #bfdbfe;
+ --secondary-300: #93c5fd;
+ --secondary-400: #60a5fa;
+ --secondary-500: #3b82f6;
+ --secondary-600: #2563eb;
+ --secondary-700: #1d4ed8;
+ --secondary-800: #1e40af;
+ --secondary-900: #1e3a8a;
+ --secondary-950: #1d3660;
+ --neutral-50: #f9fafb;
+ --neutral-100: #f3f4f6;
+ --neutral-200: #e5e7eb;
+ --neutral-300: #d1d5db;
+ --neutral-400: #9ca3af;
+ --neutral-500: #6b7280;
+ --neutral-600: #4b5563;
+ --neutral-700: #374151;
+ --neutral-800: #1f2937;
+ --neutral-900: #111827;
+ --neutral-950: #0b0f19;
+ --spacing-xxs: 1px;
+ --spacing-xs: 2px;
+ --spacing-sm: 4px;
+ --spacing-md: 6px;
+ --spacing-lg: 8px;
+ --spacing-xl: 10px;
+ --spacing-xxl: 16px;
+ --radius-xxs: 1px;
+ --radius-xs: 2px;
+ --radius-sm: 4px;
+ --radius-md: 6px;
+ --radius-lg: 8px;
+ --radius-xl: 12px;
+ --radius-xxl: 22px;
+ --text-xxs: 9px;
+ --text-xs: 10px;
+ --text-sm: 12px;
+ --text-md: 14px;
+ --text-lg: 16px;
+ --text-xl: 22px;
+ --text-xxl: 26px;
+ --font: 'Source Sans Pro', 'ui-sans-serif', 'system-ui', sans-serif;
+ --font-mono: 'IBM Plex Mono', 'ui-monospace', 'Consolas', monospace;
+ --body-text-size: var(--text-md);
+ --body-text-weight: 400;
+ --embed-radius: var(--radius-lg);
+ --color-accent: var(--primary-500);
+ --shadow-drop: rgba(0,0,0,0.05) 0px 1px 2px 0px;
+ --shadow-drop-lg: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --shadow-inset: rgba(0,0,0,0.05) 0px 2px 4px 0px inset;
+ --block-border-width: 1px;
+ --block-info-text-size: var(--text-sm);
+ --block-info-text-weight: 400;
+ --block-label-border-width: 1px;
+ --block-label-shadow: var(--block-shadow);
+ --block-label-margin: 0;
+ --block-label-padding: var(--spacing-sm) var(--spacing-lg);
+ --block-label-radius: calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px) 0;
+ --block-label-right-radius: 0 calc(var(--radius-lg) - 1px) 0 calc(var(--radius-lg) - 1px);
+ --block-label-text-size: var(--text-sm);
+ --block-label-text-weight: 400;
+ --block-padding: var(--spacing-xl) calc(var(--spacing-xl) + 2px);
+ --block-radius: var(--radius-lg);
+ --block-shadow: var(--shadow-drop);
+ --block-title-background-fill: none;
+ --block-title-border-color: none;
+ --block-title-border-width: 0px;
+ --block-title-padding: 0;
+ --block-title-radius: none;
+ --block-title-text-size: var(--text-md);
+ --block-title-text-weight: 400;
+ --container-radius: var(--radius-lg);
+ --form-gap-width: 1px;
+ --layout-gap: var(--spacing-xxl);
+ --panel-border-width: 0;
+ --section-header-text-size: var(--text-md);
+ --section-header-text-weight: 400;
+ --checkbox-border-radius: var(--radius-sm);
+ --checkbox-label-gap: var(--spacing-lg);
+ --checkbox-label-padding: var(--spacing-md) calc(2 * var(--spacing-md));
+ --checkbox-label-shadow: var(--shadow-drop);
+ --checkbox-label-text-size: var(--text-md);
+ --checkbox-label-text-weight: 400;
+ --checkbox-check: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
+ --radio-circle: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");
+ --checkbox-shadow: var(--input-shadow);
+ --error-border-width: 1px;
+ --input-border-width: 1px;
+ --input-padding: var(--spacing-xl);
+ --input-radius: var(--radius-lg);
+ --input-shadow: 0 0 0 var(--shadow-spread) transparent, var(--shadow-inset);
+ --input-text-size: var(--text-md);
+ --input-text-weight: 400;
+ --loader-color: var(--color-accent);
+ --prose-text-size: var(--text-md);
+ --prose-text-weight: 400;
+ --prose-header-text-weight: 600;
+ --slider-color: auto;
+ --table-radius: var(--radius-lg);
+ --button-large-padding: var(--spacing-lg) calc(2 * var(--spacing-lg));
+ --button-large-radius: var(--radius-lg);
+ --button-large-text-size: var(--text-lg);
+ --button-large-text-weight: 600;
+ --button-shadow: var(--shadow-drop);
+ --button-shadow-active: var(--shadow-inset);
+ --button-shadow-hover: var(--shadow-drop-lg);
+ --button-small-padding: var(--spacing-sm) calc(2 * var(--spacing-sm));
+ --button-small-radius: var(--radius-lg);
+ --button-small-text-size: var(--text-md);
+ --button-small-text-weight: 400;
+ --button-transition: none;
}
diff --git a/static/screenshot.png b/static/screenshot.png
index f675e63..8fbfd1b 100644
Binary files a/static/screenshot.png and b/static/screenshot.png differ
diff --git a/static/screenshot2.png b/static/screenshot2.png
index 7f0127b..4b3a255 100644
Binary files a/static/screenshot2.png and b/static/screenshot2.png differ
diff --git a/vite.config.ts b/vite.config.ts
index 6d0f9de..287947b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -70,6 +70,6 @@ export default defineConfig({
// }
},
test: {
- include: ['src/**/*.{test,spec}.{js,ts}']
+ include: ['litegraph/packages/tests/src/main.ts']
}
});