feat: graphical inventory with generated pixel art icons
- 11 artifact icons generated via Z-Image Turbo (128x128) - Inventory displays as icon grid with hover label strip - Artifacts in rooms show icon thumbnails alongside name/type - Icon generation script with skip-existing logic - Prompt reference JSON for all artifact icons - Fix inventory not resetting on Play Again - Add user-select: none to presentational elements Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
65
docs/architecture/adventure-icon-prompts.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"meta": {
|
||||
"style": "Pixel art icon on transparent black background, 128x128, clean edges, glowing accent color, game inventory item style",
|
||||
"usage": "Each key is an artifact ID used in adventure.html. Generate one icon per artifact.",
|
||||
"model": "Z-Image Turbo (no LoRA)",
|
||||
"resolution": "128x128"
|
||||
},
|
||||
"artifacts": {
|
||||
"graphview": {
|
||||
"name": "GraphView.vue",
|
||||
"type": "Component",
|
||||
"prompt": "Pixel art icon of a glowing canvas frame with connected nodes and wires inside, blue accent glow, dark background, game inventory item"
|
||||
},
|
||||
"widgetvaluestore": {
|
||||
"name": "widgetValueStore.ts",
|
||||
"type": "Proto-ECS Store",
|
||||
"prompt": "Pixel art icon of a vault door with a glowing slider widget embossed on it, purple and gold accents, dark background, game inventory item"
|
||||
},
|
||||
"layoutstore": {
|
||||
"name": "layoutStore.ts",
|
||||
"type": "Proto-ECS Store",
|
||||
"prompt": "Pixel art icon of a grid blueprint with glowing position markers, purple accent lines, dark background, game inventory item"
|
||||
},
|
||||
"litegraphservice": {
|
||||
"name": "litegraphService.ts",
|
||||
"type": "Service",
|
||||
"prompt": "Pixel art icon of a gear with a graph node symbol in the center, copper and blue metallic glow, dark background, game inventory item"
|
||||
},
|
||||
"lgraphcanvas": {
|
||||
"name": "LGraphCanvas.ts",
|
||||
"type": "God Object",
|
||||
"prompt": "Pixel art icon of a massive cracked monolith radiating red warning light, labeled 9100, ominous dark background, game inventory item"
|
||||
},
|
||||
"lgraphnode": {
|
||||
"name": "LGraphNode.ts",
|
||||
"type": "God Object",
|
||||
"prompt": "Pixel art icon of an oversized cube with tangled wires bursting from every face, red and amber glow, dark background, game inventory item"
|
||||
},
|
||||
"world-registry": {
|
||||
"name": "World Registry",
|
||||
"type": "ECS Core",
|
||||
"prompt": "Pixel art icon of a glowing crystalline orb containing tiny entity symbols, bright blue and white aura, dark background, game inventory item"
|
||||
},
|
||||
"branded-ids": {
|
||||
"name": "Branded Entity IDs",
|
||||
"type": "Type Safety",
|
||||
"prompt": "Pixel art icon of a set of ID cards with distinct colored borders and brand stamps, green checkmark glow, dark background, game inventory item"
|
||||
},
|
||||
"quadtree": {
|
||||
"name": "QuadTree Spatial Index",
|
||||
"type": "Data Structure",
|
||||
"prompt": "Pixel art icon of a square recursively divided into four quadrants with glowing dots at intersections, teal accent, dark background, game inventory item"
|
||||
},
|
||||
"yjs-crdt": {
|
||||
"name": "Y.js CRDT Layout",
|
||||
"type": "Collaboration",
|
||||
"prompt": "Pixel art icon of two overlapping document layers merging with sync arrows, purple and green glow, dark background, game inventory item"
|
||||
},
|
||||
"usecorecommands": {
|
||||
"name": "useCoreCommands.ts",
|
||||
"type": "Composable",
|
||||
"prompt": "Pixel art icon of a hook tool with keyboard key symbols orbiting it, yellow and blue glow, dark background, game inventory item"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,15 @@
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
#hud, .stat-bar-group, .choice-key, .challenge-badge, .artifact-type,
|
||||
.sidebar-header, #room-layer, .ending-label, .scorecard-header,
|
||||
#challenge-header, .map-room .room-layer, #challenge-progress,
|
||||
.stat-delta, .ending-stat .label, #play-again-btn, #toggle-map,
|
||||
.choice-btn .choice-hint, .challenge-choice-btn .choice-hint,
|
||||
.inv-slot, .artifact-icon {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
|
||||
background: var(--bg);
|
||||
@@ -389,14 +398,36 @@
|
||||
padding: 10px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.artifact:last-child { border-bottom: none; }
|
||||
.artifact-name { color: var(--yellow); }
|
||||
.artifact-type { font-size: 10px; color: var(--muted); text-transform: uppercase; }
|
||||
|
||||
.artifact-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 6px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.artifact-icon img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
|
||||
.artifact-icon.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.artifact-info { flex: 1; }
|
||||
.artifact-name { color: var(--yellow); display: block; }
|
||||
.artifact-type { font-size: 10px; color: var(--muted); text-transform: uppercase; display: block; }
|
||||
|
||||
/* --- Navigation Choices --- */
|
||||
#choices {
|
||||
@@ -490,15 +521,53 @@
|
||||
.log-entry.error { color: var(--red); }
|
||||
.log-entry.ending { color: var(--purple); }
|
||||
|
||||
.inv-item {
|
||||
padding: 6px 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
#inv-label {
|
||||
padding: 4px 12px;
|
||||
font-size: 11px;
|
||||
color: var(--yellow);
|
||||
min-height: 22px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
user-select: none;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
#inv-label .inv-label-type {
|
||||
color: var(--muted);
|
||||
font-size: 9px;
|
||||
text-transform: uppercase;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.inv-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
|
||||
gap: 6px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.inv-slot {
|
||||
aspect-ratio: 1;
|
||||
border-radius: 6px;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
position: relative;
|
||||
transition: border-color 0.15s;
|
||||
}
|
||||
|
||||
.inv-slot:hover { border-color: var(--yellow); }
|
||||
|
||||
.inv-slot img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
|
||||
.inv-slot.placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.inv-item-name { color: var(--yellow); font-size: 12px; }
|
||||
.inv-item-desc { color: var(--muted); font-size: 10px; }
|
||||
|
||||
/* --- Ending Screen --- */
|
||||
#ending-overlay {
|
||||
@@ -765,6 +834,7 @@
|
||||
<span>Inventory</span>
|
||||
<span id="inv-count">0</span>
|
||||
</div>
|
||||
<div id="inv-label"> </div>
|
||||
<div class="sidebar-body" id="inventory">
|
||||
<div class="log-entry" style="color:var(--muted)">Empty — explore to find artifacts.</div>
|
||||
</div>
|
||||
@@ -862,7 +932,7 @@
|
||||
the bottom panel shows queues and console output.
|
||||
`,
|
||||
artifacts: [
|
||||
{ name: 'GraphView.vue', type: 'Component' },
|
||||
{ name: 'GraphView.vue', type: 'Component', icon: 'graphview' },
|
||||
],
|
||||
challenge: {
|
||||
title: 'The Circular Dependency',
|
||||
@@ -917,8 +987,8 @@
|
||||
<a href="${GH}/src/stores/nodeDefStore.ts" target="_blank">nodeDefStore</a> catalogs every node type known to the system.
|
||||
`,
|
||||
artifacts: [
|
||||
{ name: 'widgetValueStore.ts', type: 'Proto-ECS Store' },
|
||||
{ name: 'layoutStore.ts', type: 'Proto-ECS Store' },
|
||||
{ name: 'widgetValueStore.ts', type: 'Proto-ECS Store', icon: 'widgetvaluestore' },
|
||||
{ name: 'layoutStore.ts', type: 'Proto-ECS Store', icon: 'layoutstore' },
|
||||
],
|
||||
challenge: {
|
||||
title: 'The Scattered Mutations',
|
||||
@@ -974,7 +1044,7 @@
|
||||
here bridges the presentation layer above with the stores below.
|
||||
`,
|
||||
artifacts: [
|
||||
{ name: 'litegraphService.ts', type: 'Service' },
|
||||
{ name: 'litegraphService.ts', type: 'Service', icon: 'litegraphservice' },
|
||||
],
|
||||
challenge: {
|
||||
title: 'The Migration Question',
|
||||
@@ -1028,8 +1098,8 @@
|
||||
and scattered mutation sites. The ECS migration aims to tame this complexity.
|
||||
`,
|
||||
artifacts: [
|
||||
{ name: 'LGraphCanvas.ts', type: 'God Object' },
|
||||
{ name: 'LGraphNode.ts', type: 'God Object' },
|
||||
{ name: 'LGraphCanvas.ts', type: 'God Object', icon: 'lgraphcanvas' },
|
||||
{ name: 'LGraphNode.ts', type: 'God Object', icon: 'lgraphnode' },
|
||||
],
|
||||
challenge: {
|
||||
title: 'The God Object Dilemma',
|
||||
@@ -1085,8 +1155,8 @@
|
||||
to operate on these components in clean, decoupled passes.
|
||||
`,
|
||||
artifacts: [
|
||||
{ name: 'World Registry', type: 'ECS Core' },
|
||||
{ name: 'Branded Entity IDs', type: 'Type Safety' },
|
||||
{ name: 'World Registry', type: 'ECS Core', icon: 'world-registry' },
|
||||
{ name: 'Branded Entity IDs', type: 'Type Safety', icon: 'branded-ids' },
|
||||
],
|
||||
challenge: {
|
||||
title: 'The ID Crossroads',
|
||||
@@ -1143,8 +1213,8 @@
|
||||
paints nodes, links, and reroutes onto the HTML canvas.
|
||||
`,
|
||||
artifacts: [
|
||||
{ name: 'QuadTree Spatial Index', type: 'Data Structure' },
|
||||
{ name: 'Y.js CRDT Layout', type: 'Collaboration' },
|
||||
{ name: 'QuadTree Spatial Index', type: 'Data Structure', icon: 'quadtree' },
|
||||
{ name: 'Y.js CRDT Layout', type: 'Collaboration', icon: 'yjs-crdt' },
|
||||
],
|
||||
challenge: {
|
||||
title: 'The Render-Time Mutation',
|
||||
@@ -1192,7 +1262,7 @@
|
||||
encapsulates logic that would otherwise clutter components.
|
||||
`,
|
||||
artifacts: [
|
||||
{ name: 'useCoreCommands.ts', type: 'Composable' },
|
||||
{ name: 'useCoreCommands.ts', type: 'Composable', icon: 'usecorecommands' },
|
||||
],
|
||||
challenge: {
|
||||
title: 'The Collaboration Protocol',
|
||||
@@ -1321,6 +1391,7 @@
|
||||
artifactsList: $('#artifacts-list'),
|
||||
inventory: $('#inventory'),
|
||||
invCount: $('#inv-count'),
|
||||
invLabel: $('#inv-label'),
|
||||
log: $('#log'),
|
||||
logCount: $('#log-count'),
|
||||
barDebt: $('#bar-debt'),
|
||||
@@ -1385,6 +1456,24 @@
|
||||
.join(' ')
|
||||
}
|
||||
|
||||
// --- Icon helper ---
|
||||
const iconFallbacks = {
|
||||
'Component': '▭', 'Proto-ECS Store': '◇', 'Service': '⚙',
|
||||
'God Object': '☠', 'ECS Core': '◈', 'Type Safety': '☑',
|
||||
'Data Structure': '▦', 'Collaboration': '⇄', 'Composable': '✶',
|
||||
}
|
||||
|
||||
function artifactIconHtml(a, size) {
|
||||
const sz = size || 36
|
||||
if (a.icon) {
|
||||
return `<div class="artifact-icon" style="width:${sz}px;height:${sz}px">
|
||||
<img src="icons/${a.icon}.png" alt="${a.name}"
|
||||
onerror="this.parentElement.classList.add('placeholder');this.parentElement.innerHTML='${iconFallbacks[a.type] || '◆'}'">
|
||||
</div>`
|
||||
}
|
||||
return `<div class="artifact-icon placeholder" style="width:${sz}px;height:${sz}px">${iconFallbacks[a.type] || '◆'}</div>`
|
||||
}
|
||||
|
||||
// --- Rendering ---
|
||||
function render(roomId) {
|
||||
const room = rooms[roomId]
|
||||
@@ -1430,8 +1519,11 @@
|
||||
els.artifacts.classList.add('has-items')
|
||||
els.artifactsList.innerHTML = room.artifacts.map(a => `
|
||||
<div class="artifact">
|
||||
<span class="artifact-name">${a.name}</span>
|
||||
<span class="artifact-type">${a.type}</span>
|
||||
${artifactIconHtml(a, 36)}
|
||||
<div class="artifact-info">
|
||||
<span class="artifact-name">${a.name}</span>
|
||||
<span class="artifact-type">${a.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')
|
||||
|
||||
@@ -1566,13 +1658,29 @@
|
||||
|
||||
function renderInventory() {
|
||||
if (state.inventory.length > 0) {
|
||||
els.inventory.innerHTML = state.inventory.map(i => `
|
||||
<div class="inv-item">
|
||||
<span class="inv-item-name">${i.name}</span>
|
||||
<span class="inv-item-desc">${i.type}</span>
|
||||
</div>
|
||||
`).join('')
|
||||
els.inventory.innerHTML = '<div class="inv-grid">' + state.inventory.map((item, idx) => {
|
||||
const fallback = iconFallbacks[item.type] || '◆'
|
||||
const inner = item.icon
|
||||
? `<img src="icons/${item.icon}.png" alt="${item.name}"
|
||||
onerror="this.parentElement.classList.add('placeholder');this.parentElement.innerHTML='${fallback}'">`
|
||||
: fallback
|
||||
const cls = item.icon ? 'inv-slot' : 'inv-slot placeholder'
|
||||
return `<div class="${cls}" data-inv-idx="${idx}">${inner}</div>`
|
||||
}).join('') + '</div>'
|
||||
|
||||
els.inventory.querySelectorAll('.inv-slot').forEach(slot => {
|
||||
slot.addEventListener('mouseenter', () => {
|
||||
const item = state.inventory[slot.dataset.invIdx]
|
||||
if (item) els.invLabel.innerHTML = `${item.name}<span class="inv-label-type">${item.type}</span>`
|
||||
})
|
||||
slot.addEventListener('mouseleave', () => {
|
||||
els.invLabel.innerHTML = ' '
|
||||
})
|
||||
})
|
||||
} else {
|
||||
els.inventory.innerHTML = '<div class="log-entry" style="color:var(--muted)">Empty — explore to find artifacts.</div>'
|
||||
}
|
||||
els.invLabel.innerHTML = ' '
|
||||
els.invCount.textContent = state.inventory.length
|
||||
}
|
||||
|
||||
|
||||
178
docs/architecture/generate-icons.py
Normal file
@@ -0,0 +1,178 @@
|
||||
"""
|
||||
Generate pixel art inventory icons for the Architecture Adventure game.
|
||||
Uses Z-Image Turbo pipeline via local ComfyUI server (no LoRA).
|
||||
Skips icons that already exist on disk.
|
||||
|
||||
Usage: python docs/architecture/generate-icons.py
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
COMFY_URL = "http://localhost:8188"
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
PROMPTS_FILE = os.path.join(SCRIPT_DIR, "adventure-icon-prompts.json")
|
||||
OUTPUT_DIR = os.path.join(SCRIPT_DIR, "icons")
|
||||
BASE_SEED = 7777
|
||||
WIDTH = 128
|
||||
HEIGHT = 128
|
||||
|
||||
|
||||
def build_workflow(prompt_text, seed, prefix):
|
||||
return {
|
||||
"1": {
|
||||
"class_type": "UNETLoader",
|
||||
"inputs": {
|
||||
"unet_name": "ZIT\\z_image_turbo_bf16.safetensors",
|
||||
"weight_dtype": "default",
|
||||
},
|
||||
},
|
||||
"2": {
|
||||
"class_type": "CLIPLoader",
|
||||
"inputs": {
|
||||
"clip_name": "qwen_3_4b.safetensors",
|
||||
"type": "lumina2",
|
||||
"device": "default",
|
||||
},
|
||||
},
|
||||
"3": {
|
||||
"class_type": "VAELoader",
|
||||
"inputs": {"vae_name": "ae.safetensors"},
|
||||
},
|
||||
"4": {
|
||||
"class_type": "ModelSamplingAuraFlow",
|
||||
"inputs": {"shift": 3, "model": ["1", 0]},
|
||||
},
|
||||
"6": {
|
||||
"class_type": "CLIPTextEncode",
|
||||
"inputs": {"text": prompt_text, "clip": ["2", 0]},
|
||||
},
|
||||
"7": {
|
||||
"class_type": "ConditioningZeroOut",
|
||||
"inputs": {"conditioning": ["6", 0]},
|
||||
},
|
||||
"8": {
|
||||
"class_type": "EmptySD3LatentImage",
|
||||
"inputs": {"width": WIDTH, "height": HEIGHT, "batch_size": 1},
|
||||
},
|
||||
"9": {
|
||||
"class_type": "KSampler",
|
||||
"inputs": {
|
||||
"seed": seed,
|
||||
"control_after_generate": "fixed",
|
||||
"steps": 8,
|
||||
"cfg": 1,
|
||||
"sampler_name": "res_multistep",
|
||||
"scheduler": "simple",
|
||||
"denoise": 1,
|
||||
"model": ["4", 0],
|
||||
"positive": ["6", 0],
|
||||
"negative": ["7", 0],
|
||||
"latent_image": ["8", 0],
|
||||
},
|
||||
},
|
||||
"10": {
|
||||
"class_type": "VAEDecode",
|
||||
"inputs": {"samples": ["9", 0], "vae": ["3", 0]},
|
||||
},
|
||||
"11": {
|
||||
"class_type": "SaveImage",
|
||||
"inputs": {"filename_prefix": prefix, "images": ["10", 0]},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def submit_prompt(workflow):
|
||||
payload = json.dumps({"prompt": workflow}).encode("utf-8")
|
||||
req = urllib.request.Request(
|
||||
f"{COMFY_URL}/prompt",
|
||||
data=payload,
|
||||
headers={"Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
resp = urllib.request.urlopen(req)
|
||||
return json.loads(resp.read())
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode()
|
||||
raise RuntimeError(f"HTTP {e.code}: {body}")
|
||||
|
||||
|
||||
def poll_history(prompt_id, timeout=120):
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
resp = urllib.request.urlopen(f"{COMFY_URL}/history/{prompt_id}")
|
||||
data = json.loads(resp.read())
|
||||
if prompt_id in data:
|
||||
return data[prompt_id]
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(2)
|
||||
return None
|
||||
|
||||
|
||||
def download_image(filename, subfolder, dest_path):
|
||||
url = f"{COMFY_URL}/view?filename={urllib.request.quote(filename)}&subfolder={urllib.request.quote(subfolder)}&type=output"
|
||||
urllib.request.urlretrieve(url, dest_path)
|
||||
|
||||
|
||||
def main():
|
||||
with open(PROMPTS_FILE) as f:
|
||||
data = json.load(f)
|
||||
|
||||
artifacts = data["artifacts"]
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
# Filter out already-generated icons
|
||||
to_generate = {}
|
||||
for artifact_id, artifact in artifacts.items():
|
||||
dest = os.path.join(OUTPUT_DIR, f"{artifact_id}.png")
|
||||
if os.path.exists(dest):
|
||||
print(f" Skipping {artifact_id}.png (already exists)")
|
||||
else:
|
||||
to_generate[artifact_id] = artifact
|
||||
|
||||
if not to_generate:
|
||||
print("All icons already generated. Nothing to do.")
|
||||
return
|
||||
|
||||
# Submit jobs
|
||||
jobs = []
|
||||
for i, (artifact_id, artifact) in enumerate(to_generate.items()):
|
||||
prefix = f"adventure-icons/{artifact_id}"
|
||||
wf = build_workflow(artifact["prompt"], BASE_SEED + i, prefix)
|
||||
result = submit_prompt(wf)
|
||||
prompt_id = result["prompt_id"]
|
||||
jobs.append((artifact_id, prompt_id))
|
||||
print(f" Submitted: {artifact_id} -> {prompt_id}")
|
||||
|
||||
print(f"\n{len(jobs)} jobs queued. Polling for completion...\n")
|
||||
|
||||
# Poll for completion
|
||||
completed = set()
|
||||
while len(completed) < len(jobs):
|
||||
for artifact_id, prompt_id in jobs:
|
||||
if prompt_id in completed:
|
||||
continue
|
||||
history = poll_history(prompt_id, timeout=5)
|
||||
if history:
|
||||
completed.add(prompt_id)
|
||||
outputs = history.get("outputs", {})
|
||||
for node_out in outputs.values():
|
||||
for img in node_out.get("images", []):
|
||||
src_filename = img["filename"]
|
||||
subfolder = img.get("subfolder", "")
|
||||
dest = os.path.join(OUTPUT_DIR, f"{artifact_id}.png")
|
||||
download_image(src_filename, subfolder, dest)
|
||||
print(f" [{len(completed)}/{len(jobs)}] {artifact_id}.png downloaded")
|
||||
if len(completed) < len(jobs):
|
||||
time.sleep(2)
|
||||
|
||||
print(f"\nDone! {len(completed)} icons saved to {OUTPUT_DIR}/")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
docs/architecture/icons/branded-ids.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/architecture/icons/graphview.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/architecture/icons/layoutstore.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/architecture/icons/lgraphcanvas.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
docs/architecture/icons/lgraphnode.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/architecture/icons/litegraphservice.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
docs/architecture/icons/quadtree.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/architecture/icons/usecorecommands.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/architecture/icons/widgetvaluestore.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
docs/architecture/icons/world-registry.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/architecture/icons/yjs-crdt.png
Normal file
|
After Width: | Height: | Size: 14 KiB |