Reshuffle GUI workspace layout
This commit is contained in:
parent
977aa5cb37
commit
8de96a86d2
4 changed files with 128 additions and 176 deletions
|
|
@ -49,7 +49,6 @@ type AppState = {
|
|||
inputPreviewImage: ImageAsset | null;
|
||||
sourceImage: ImageAsset | null;
|
||||
preprocessedImage: ImageAsset | null;
|
||||
edgeMapImage: ImageAsset | null;
|
||||
latticeImage: ImageAsset | null;
|
||||
guidanceImage: ImageAsset | null;
|
||||
inference: InferencePayload | null;
|
||||
|
|
@ -67,7 +66,6 @@ type AppState = {
|
|||
altHeld: boolean;
|
||||
zoom: number;
|
||||
showGrid: boolean;
|
||||
currentStage: string;
|
||||
};
|
||||
|
||||
type ViewportAnchor = {
|
||||
|
|
@ -98,10 +96,8 @@ const stepSlider = byId<HTMLInputElement>("stepSlider");
|
|||
const stepValue = byId<HTMLSpanElement>("stepValue");
|
||||
const lossCanvas = byId<HTMLCanvasElement>("lossCanvas");
|
||||
const leftCanvas = byId<HTMLCanvasElement>("leftCanvas");
|
||||
const middleCanvas = byId<HTMLCanvasElement>("middleCanvas");
|
||||
const rightCanvas = byId<HTMLCanvasElement>("rightCanvas");
|
||||
const leftVizLabel = byId<HTMLDivElement>("leftVizLabel");
|
||||
const middleVizLabel = byId<HTMLDivElement>("middleVizLabel");
|
||||
const rightVizLabel = byId<HTMLDivElement>("rightVizLabel");
|
||||
const metricsPanel = byId<HTMLDivElement>("metricsPanel");
|
||||
const paintSwatch = byId<HTMLDivElement>("paintSwatch");
|
||||
|
|
@ -161,7 +157,6 @@ const state: AppState = {
|
|||
inputPreviewImage: null,
|
||||
sourceImage: null,
|
||||
preprocessedImage: null,
|
||||
edgeMapImage: null,
|
||||
latticeImage: null,
|
||||
guidanceImage: null,
|
||||
inference: null,
|
||||
|
|
@ -179,7 +174,6 @@ const state: AppState = {
|
|||
altHeld: false,
|
||||
zoom: Number(zoomInput.value),
|
||||
showGrid: gridToggle.checked,
|
||||
currentStage: "Idle",
|
||||
};
|
||||
|
||||
let currentEventSource: EventSource | null = null;
|
||||
|
|
@ -249,7 +243,6 @@ function resetRunArtifacts(): void {
|
|||
state.jobId = null;
|
||||
state.sourceImage = null;
|
||||
state.preprocessedImage = null;
|
||||
state.edgeMapImage = null;
|
||||
state.latticeImage = null;
|
||||
state.guidanceImage = null;
|
||||
state.inference = null;
|
||||
|
|
@ -263,7 +256,6 @@ function resetRunArtifacts(): void {
|
|||
state.eventLog = [];
|
||||
state.editorBaseAsset = null;
|
||||
state.editorDirty = false;
|
||||
state.currentStage = "Waiting";
|
||||
}
|
||||
|
||||
function parseOptionalInteger(input: HTMLInputElement): number | null {
|
||||
|
|
@ -479,10 +471,8 @@ function renderSummary(): void {
|
|||
async function renderViewer(): Promise<void> {
|
||||
const frame = getSelectedFrame();
|
||||
let leftAsset = state.preprocessedImage ?? state.sourceImage;
|
||||
let middleAsset = state.edgeMapImage;
|
||||
let rightAsset = state.finalOutputImage ?? state.cleanupImage ?? state.sourceImage;
|
||||
let leftLabel = "Source";
|
||||
let middleLabel = "Edge Scout";
|
||||
let rightLabel = "Output";
|
||||
|
||||
if (state.latticeImage) {
|
||||
|
|
@ -490,27 +480,26 @@ async function renderViewer(): Promise<void> {
|
|||
leftLabel = "Lattice Prep";
|
||||
}
|
||||
if (state.guidanceImage) {
|
||||
middleAsset = state.guidanceImage;
|
||||
middleLabel = "Guidance";
|
||||
leftAsset = state.guidanceImage;
|
||||
leftLabel = "Guidance";
|
||||
}
|
||||
if (frame) {
|
||||
leftAsset = frame.samplingOverlayImage;
|
||||
middleAsset = frame.displacementImage;
|
||||
rightAsset = frame.outputImage;
|
||||
leftLabel = "Sampling Overlay";
|
||||
middleLabel = "Displacement";
|
||||
rightLabel = "Current Output";
|
||||
} else if (state.cleanupImage) {
|
||||
middleAsset = state.heatmapImage ?? middleAsset;
|
||||
middleLabel = "Cleanup Heatmap";
|
||||
if (state.heatmapImage) {
|
||||
leftAsset = state.heatmapImage;
|
||||
leftLabel = "Cleanup Heatmap";
|
||||
}
|
||||
rightAsset = state.cleanupImage;
|
||||
rightLabel = "Cleaned Output";
|
||||
}
|
||||
|
||||
leftVizLabel.textContent = leftLabel;
|
||||
middleVizLabel.textContent = middleLabel;
|
||||
rightVizLabel.textContent = rightLabel;
|
||||
await Promise.all([drawAsset(leftCanvas, leftAsset), drawAsset(middleCanvas, middleAsset), drawAsset(rightCanvas, rightAsset)]);
|
||||
await Promise.all([drawAsset(leftCanvas, leftAsset), drawAsset(rightCanvas, rightAsset)]);
|
||||
renderMetrics();
|
||||
}
|
||||
|
||||
|
|
@ -826,22 +815,18 @@ async function handleEvent(eventName: string, payload: JsonRecord): Promise<void
|
|||
break;
|
||||
case "source_loaded":
|
||||
state.sourceImage = payload.sourceImage as ImageAsset;
|
||||
state.currentStage = "Loaded source";
|
||||
setStatus("running", "Source loaded. Survey crew is measuring the mess.");
|
||||
addLog("Source", "Loaded the input image.");
|
||||
break;
|
||||
case "preprocess_completed":
|
||||
state.preprocessedImage = payload.sourceImage as ImageAsset;
|
||||
state.currentStage = "Preprocessed source";
|
||||
addLog("Preprocess", "Stripped edge-connected background noise.");
|
||||
break;
|
||||
case "inference_candidates_ready":
|
||||
state.inference = payload.inference as InferencePayload;
|
||||
state.currentStage = "Scoring lattice candidates";
|
||||
addLog("Inference", "Scored candidate grids and phase offsets.");
|
||||
break;
|
||||
case "phase_rerank_started":
|
||||
state.currentStage = "Phase rerank preview";
|
||||
addLog(
|
||||
"Rerank",
|
||||
`Running ${String(payload.previewSteps)} preview steps across ${String(payload.candidateCount)} low-confidence candidates.`,
|
||||
|
|
@ -849,18 +834,14 @@ async function handleEvent(eventName: string, payload: JsonRecord): Promise<void
|
|||
break;
|
||||
case "phase_selection_completed":
|
||||
state.inference = payload.inference as InferencePayload;
|
||||
state.currentStage = "Selected lattice";
|
||||
addLog("Selection", "Committed to a ruler and phase.");
|
||||
break;
|
||||
case "analysis_completed":
|
||||
state.edgeMapImage = payload.edgeMapImage as ImageAsset;
|
||||
state.currentStage = "Edge scout";
|
||||
addLog("Scout", "Built the edge map that tells the solver where the floorboards creak.");
|
||||
break;
|
||||
case "phase_field_prepared":
|
||||
state.latticeImage = payload.latticeImage as ImageAsset;
|
||||
state.guidanceImage = payload.guidanceImage as ImageAsset;
|
||||
state.currentStage = "Prepared phase field";
|
||||
addLog("Prep", `Locked ${String(payload.targetWidth)} x ${String(payload.targetHeight)} lattice centers.`);
|
||||
break;
|
||||
case "phase_field_initial":
|
||||
|
|
@ -874,30 +855,26 @@ async function handleEvent(eventName: string, payload: JsonRecord): Promise<void
|
|||
state.frames.push(frame);
|
||||
state.frames.sort((a, b) => a.step - b.step);
|
||||
}
|
||||
if (state.autoFollow) {
|
||||
state.selectedFrameIndex = Math.max(0, state.frames.findIndex((candidate) => candidate.step === frame.step));
|
||||
if (state.autoFollow) {
|
||||
state.selectedFrameIndex = Math.max(0, state.frames.findIndex((candidate) => candidate.step === frame.step));
|
||||
}
|
||||
if (eventName === "phase_field_final") {
|
||||
addLog("Solver", "Final nearest-source sample committed.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
state.currentStage = frame.step === 0 ? "Initial placement" : `Solver step ${frame.step}/${frame.totalSteps}`;
|
||||
if (eventName === "phase_field_final") {
|
||||
addLog("Solver", "Final nearest-source sample committed.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "cleanup_completed":
|
||||
state.cleanupImage = payload.cleanedImage as ImageAsset;
|
||||
state.heatmapImage = payload.heatmapImage as ImageAsset;
|
||||
state.currentStage = "Cleanup";
|
||||
addLog("Cleanup", "Ran the local cleanup sweep.");
|
||||
break;
|
||||
case "palette_completed":
|
||||
state.finalOutputImage = payload.outputImage as ImageAsset;
|
||||
state.currentStage = "Final output";
|
||||
addLog("Output", "Final output image is ready.");
|
||||
break;
|
||||
case "pipeline_completed":
|
||||
state.finalOutputImage = payload.outputImage as ImageAsset;
|
||||
state.runSummary = (payload.runSummary as JsonRecord | null) ?? null;
|
||||
state.currentStage = "Completed";
|
||||
setStatus("completed", "Run finished. If the machine still did something stupid, fix it pixel by pixel.");
|
||||
addLog("Done", "Full pipeline completed.");
|
||||
runButton.disabled = false;
|
||||
|
|
|
|||
|
|
@ -21,10 +21,8 @@ const stepSlider = byId("stepSlider");
|
|||
const stepValue = byId("stepValue");
|
||||
const lossCanvas = byId("lossCanvas");
|
||||
const leftCanvas = byId("leftCanvas");
|
||||
const middleCanvas = byId("middleCanvas");
|
||||
const rightCanvas = byId("rightCanvas");
|
||||
const leftVizLabel = byId("leftVizLabel");
|
||||
const middleVizLabel = byId("middleVizLabel");
|
||||
const rightVizLabel = byId("rightVizLabel");
|
||||
const metricsPanel = byId("metricsPanel");
|
||||
const paintSwatch = byId("paintSwatch");
|
||||
|
|
@ -80,7 +78,6 @@ const state = {
|
|||
inputPreviewImage: null,
|
||||
sourceImage: null,
|
||||
preprocessedImage: null,
|
||||
edgeMapImage: null,
|
||||
latticeImage: null,
|
||||
guidanceImage: null,
|
||||
inference: null,
|
||||
|
|
@ -98,7 +95,6 @@ const state = {
|
|||
altHeld: false,
|
||||
zoom: Number(zoomInput.value),
|
||||
showGrid: gridToggle.checked,
|
||||
currentStage: "Idle",
|
||||
};
|
||||
let currentEventSource = null;
|
||||
let painting = false;
|
||||
|
|
@ -159,7 +155,6 @@ function resetRunArtifacts() {
|
|||
state.jobId = null;
|
||||
state.sourceImage = null;
|
||||
state.preprocessedImage = null;
|
||||
state.edgeMapImage = null;
|
||||
state.latticeImage = null;
|
||||
state.guidanceImage = null;
|
||||
state.inference = null;
|
||||
|
|
@ -173,7 +168,6 @@ function resetRunArtifacts() {
|
|||
state.eventLog = [];
|
||||
state.editorBaseAsset = null;
|
||||
state.editorDirty = false;
|
||||
state.currentStage = "Waiting";
|
||||
}
|
||||
function parseOptionalInteger(input) {
|
||||
if (input.value.trim() === "") {
|
||||
|
|
@ -377,37 +371,34 @@ function renderSummary() {
|
|||
async function renderViewer() {
|
||||
const frame = getSelectedFrame();
|
||||
let leftAsset = state.preprocessedImage ?? state.sourceImage;
|
||||
let middleAsset = state.edgeMapImage;
|
||||
let rightAsset = state.finalOutputImage ?? state.cleanupImage ?? state.sourceImage;
|
||||
let leftLabel = "Source";
|
||||
let middleLabel = "Edge Scout";
|
||||
let rightLabel = "Output";
|
||||
if (state.latticeImage) {
|
||||
leftAsset = state.latticeImage;
|
||||
leftLabel = "Lattice Prep";
|
||||
}
|
||||
if (state.guidanceImage) {
|
||||
middleAsset = state.guidanceImage;
|
||||
middleLabel = "Guidance";
|
||||
leftAsset = state.guidanceImage;
|
||||
leftLabel = "Guidance";
|
||||
}
|
||||
if (frame) {
|
||||
leftAsset = frame.samplingOverlayImage;
|
||||
middleAsset = frame.displacementImage;
|
||||
rightAsset = frame.outputImage;
|
||||
leftLabel = "Sampling Overlay";
|
||||
middleLabel = "Displacement";
|
||||
rightLabel = "Current Output";
|
||||
}
|
||||
else if (state.cleanupImage) {
|
||||
middleAsset = state.heatmapImage ?? middleAsset;
|
||||
middleLabel = "Cleanup Heatmap";
|
||||
if (state.heatmapImage) {
|
||||
leftAsset = state.heatmapImage;
|
||||
leftLabel = "Cleanup Heatmap";
|
||||
}
|
||||
rightAsset = state.cleanupImage;
|
||||
rightLabel = "Cleaned Output";
|
||||
}
|
||||
leftVizLabel.textContent = leftLabel;
|
||||
middleVizLabel.textContent = middleLabel;
|
||||
rightVizLabel.textContent = rightLabel;
|
||||
await Promise.all([drawAsset(leftCanvas, leftAsset), drawAsset(middleCanvas, middleAsset), drawAsset(rightCanvas, rightAsset)]);
|
||||
await Promise.all([drawAsset(leftCanvas, leftAsset), drawAsset(rightCanvas, rightAsset)]);
|
||||
renderMetrics();
|
||||
}
|
||||
function renderSlider() {
|
||||
|
|
@ -704,38 +695,30 @@ async function handleEvent(eventName, payload) {
|
|||
break;
|
||||
case "source_loaded":
|
||||
state.sourceImage = payload.sourceImage;
|
||||
state.currentStage = "Loaded source";
|
||||
setStatus("running", "Source loaded. Survey crew is measuring the mess.");
|
||||
addLog("Source", "Loaded the input image.");
|
||||
break;
|
||||
case "preprocess_completed":
|
||||
state.preprocessedImage = payload.sourceImage;
|
||||
state.currentStage = "Preprocessed source";
|
||||
addLog("Preprocess", "Stripped edge-connected background noise.");
|
||||
break;
|
||||
case "inference_candidates_ready":
|
||||
state.inference = payload.inference;
|
||||
state.currentStage = "Scoring lattice candidates";
|
||||
addLog("Inference", "Scored candidate grids and phase offsets.");
|
||||
break;
|
||||
case "phase_rerank_started":
|
||||
state.currentStage = "Phase rerank preview";
|
||||
addLog("Rerank", `Running ${String(payload.previewSteps)} preview steps across ${String(payload.candidateCount)} low-confidence candidates.`);
|
||||
break;
|
||||
case "phase_selection_completed":
|
||||
state.inference = payload.inference;
|
||||
state.currentStage = "Selected lattice";
|
||||
addLog("Selection", "Committed to a ruler and phase.");
|
||||
break;
|
||||
case "analysis_completed":
|
||||
state.edgeMapImage = payload.edgeMapImage;
|
||||
state.currentStage = "Edge scout";
|
||||
addLog("Scout", "Built the edge map that tells the solver where the floorboards creak.");
|
||||
break;
|
||||
case "phase_field_prepared":
|
||||
state.latticeImage = payload.latticeImage;
|
||||
state.guidanceImage = payload.guidanceImage;
|
||||
state.currentStage = "Prepared phase field";
|
||||
addLog("Prep", `Locked ${String(payload.targetWidth)} x ${String(payload.targetHeight)} lattice centers.`);
|
||||
break;
|
||||
case "phase_field_initial":
|
||||
|
|
@ -753,7 +736,6 @@ async function handleEvent(eventName, payload) {
|
|||
if (state.autoFollow) {
|
||||
state.selectedFrameIndex = Math.max(0, state.frames.findIndex((candidate) => candidate.step === frame.step));
|
||||
}
|
||||
state.currentStage = frame.step === 0 ? "Initial placement" : `Solver step ${frame.step}/${frame.totalSteps}`;
|
||||
if (eventName === "phase_field_final") {
|
||||
addLog("Solver", "Final nearest-source sample committed.");
|
||||
}
|
||||
|
|
@ -762,18 +744,15 @@ async function handleEvent(eventName, payload) {
|
|||
case "cleanup_completed":
|
||||
state.cleanupImage = payload.cleanedImage;
|
||||
state.heatmapImage = payload.heatmapImage;
|
||||
state.currentStage = "Cleanup";
|
||||
addLog("Cleanup", "Ran the local cleanup sweep.");
|
||||
break;
|
||||
case "palette_completed":
|
||||
state.finalOutputImage = payload.outputImage;
|
||||
state.currentStage = "Final output";
|
||||
addLog("Output", "Final output image is ready.");
|
||||
break;
|
||||
case "pipeline_completed":
|
||||
state.finalOutputImage = payload.outputImage;
|
||||
state.runSummary = payload.runSummary ?? null;
|
||||
state.currentStage = "Completed";
|
||||
setStatus("completed", "Run finished. If the machine still did something stupid, fix it pixel by pixel.");
|
||||
addLog("Done", "Full pipeline completed.");
|
||||
runButton.disabled = false;
|
||||
|
|
|
|||
|
|
@ -8,22 +8,114 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="shell app-shell">
|
||||
<header class="app-shell-header">
|
||||
<div class="brand">
|
||||
<div class="brand-lockup">
|
||||
<div class="brand-emblem" aria-hidden="true">
|
||||
<span class="brand-emblem-core"></span>
|
||||
</div>
|
||||
<div class="copy-stack brand-copy">
|
||||
<p class="eyebrow">Live Pipeline</p>
|
||||
<h1>Repixelizer</h1>
|
||||
<p class="copy-tag brand-tag">Take fake pixel art, force it onto a real lattice, then inspect the crime scene frame by frame.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="app-grid">
|
||||
<main class="workspace page-content">
|
||||
<header class="app-shell-header">
|
||||
<div class="brand">
|
||||
<div class="brand-lockup">
|
||||
<div class="brand-emblem" aria-hidden="true">
|
||||
<span class="brand-emblem-core"></span>
|
||||
</div>
|
||||
<div class="copy-stack brand-copy">
|
||||
<p class="eyebrow">Live Pipeline</p>
|
||||
<h1>Repixelizer</h1>
|
||||
<p class="copy-tag brand-tag">Take fake pixel art, force it onto a real lattice, then inspect the crime scene frame by frame.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="hero viewer-panel">
|
||||
<div class="card-head viewer-head">
|
||||
<div class="copy-stack card-copy viewer-heading">
|
||||
<h2 id="viewerTitle">Pipeline Stages</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div id="metricsPanel" class="metrics-panel stats-grid">
|
||||
<div class="metric-chip metric-status-block">
|
||||
<span id="statusBadge" class="status-badge">Idle</span>
|
||||
<p id="statusText" class="status-text metrics-status-text">Waiting for input.</p>
|
||||
</div>
|
||||
<div class="metric-chip scrubber metric-scrubber">
|
||||
<label for="stepSlider">Solver Step</label>
|
||||
<input id="stepSlider" type="range" min="0" max="0" step="1" value="0" disabled />
|
||||
<span id="stepValue" class="step-value">No frames yet</span>
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="lossCanvas" class="loss-canvas" width="800" height="120"></canvas>
|
||||
<div class="viewer-grid viewer-grid-dual">
|
||||
<figure class="viz-card">
|
||||
<div class="viz-label" id="leftVizLabel">Source</div>
|
||||
<canvas id="leftCanvas"></canvas>
|
||||
</figure>
|
||||
<figure class="viz-card">
|
||||
<div class="viz-label" id="rightVizLabel">Output</div>
|
||||
<canvas id="rightCanvas"></canvas>
|
||||
</figure>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card editor-panel">
|
||||
<div class="card-head editor-head">
|
||||
<div class="copy-stack card-copy">
|
||||
<p class="eyebrow">Pixel Fixes</p>
|
||||
<h2>Output Editor</h2>
|
||||
<p class="copy-tag">Pencil the stragglers. Wheel zooms to cursor, middle mouse pans, Alt steals color.</p>
|
||||
</div>
|
||||
<div class="editor-actions">
|
||||
<button id="resetEditorButton" class="button button-small button-secondary" type="button">Reset Output</button>
|
||||
<button id="downloadButton" class="button button-small" type="button">Export PNG</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-toolbar">
|
||||
<div class="paint-block">
|
||||
<div id="paintSwatch" class="paint-swatch"></div>
|
||||
<div class="rgba-inputs">
|
||||
<label class="field">
|
||||
<span>R</span>
|
||||
<input id="paintR" type="number" min="0" max="255" value="255" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>G</span>
|
||||
<input id="paintG" type="number" min="0" max="255" value="255" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>B</span>
|
||||
<input id="paintB" type="number" min="0" max="255" value="255" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>A</span>
|
||||
<input id="paintA" type="number" min="0" max="255" value="255" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zoom-block field">
|
||||
<label for="zoomInput">Zoom</label>
|
||||
<input id="zoomInput" type="range" min="4" max="32" step="1" value="12" />
|
||||
<span id="zoomValue">12x</span>
|
||||
</div>
|
||||
<label class="toggle-row compact">
|
||||
<input id="gridToggle" type="checkbox" checked />
|
||||
<span>Grid</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="editor-stage">
|
||||
<div class="checkerboard editor-surface" id="editorSurface">
|
||||
<canvas id="editorCanvas"></canvas>
|
||||
<canvas id="editorGridCanvas"></canvas>
|
||||
</div>
|
||||
<div class="editor-meta">
|
||||
<div id="editorMeta" class="info-stack">
|
||||
<p class="muted">Final output lands here once the solver finishes its little pilgrimage.</p>
|
||||
</div>
|
||||
<div id="summaryPanel" class="summary-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<aside class="sidebar">
|
||||
<section class="card section-block">
|
||||
<div class="card-head">
|
||||
|
|
@ -123,102 +215,6 @@
|
|||
<div id="eventLog" class="event-log"></div>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<main class="workspace page-content">
|
||||
<section class="hero viewer-panel">
|
||||
<div class="card-head viewer-head">
|
||||
<div class="copy-stack card-copy viewer-heading">
|
||||
<h2 id="viewerTitle">Pipeline Stages</h2>
|
||||
</div>
|
||||
</div>
|
||||
<canvas id="lossCanvas" class="loss-canvas" width="800" height="120"></canvas>
|
||||
<div class="viewer-grid">
|
||||
<figure class="viz-card">
|
||||
<div class="viz-label" id="leftVizLabel">Source</div>
|
||||
<canvas id="leftCanvas"></canvas>
|
||||
</figure>
|
||||
<figure class="viz-card">
|
||||
<div class="viz-label" id="middleVizLabel">Scout</div>
|
||||
<canvas id="middleCanvas"></canvas>
|
||||
</figure>
|
||||
<figure class="viz-card">
|
||||
<div class="viz-label" id="rightVizLabel">Output</div>
|
||||
<canvas id="rightCanvas"></canvas>
|
||||
</figure>
|
||||
</div>
|
||||
<div id="metricsPanel" class="metrics-panel stats-grid">
|
||||
<div class="metric-chip metric-status-block">
|
||||
<span id="statusBadge" class="status-badge">Idle</span>
|
||||
<p id="statusText" class="status-text metrics-status-text">Waiting for input.</p>
|
||||
</div>
|
||||
<div class="metric-chip scrubber metric-scrubber">
|
||||
<label for="stepSlider">Solver Step</label>
|
||||
<input id="stepSlider" type="range" min="0" max="0" step="1" value="0" disabled />
|
||||
<span id="stepValue" class="step-value">No frames yet</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card editor-panel">
|
||||
<div class="card-head editor-head">
|
||||
<div class="copy-stack card-copy">
|
||||
<p class="eyebrow">Pixel Fixes</p>
|
||||
<h2>Output Editor</h2>
|
||||
<p class="copy-tag">Pencil the stragglers. Wheel zooms to cursor, middle mouse pans, Alt steals color.</p>
|
||||
</div>
|
||||
<div class="editor-actions">
|
||||
<button id="resetEditorButton" class="button button-small button-secondary" type="button">Reset Output</button>
|
||||
<button id="downloadButton" class="button button-small" type="button">Export PNG</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor-toolbar">
|
||||
<div class="paint-block">
|
||||
<div id="paintSwatch" class="paint-swatch"></div>
|
||||
<div class="rgba-inputs">
|
||||
<label class="field">
|
||||
<span>R</span>
|
||||
<input id="paintR" type="number" min="0" max="255" value="255" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>G</span>
|
||||
<input id="paintG" type="number" min="0" max="255" value="255" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>B</span>
|
||||
<input id="paintB" type="number" min="0" max="255" value="255" />
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>A</span>
|
||||
<input id="paintA" type="number" min="0" max="255" value="255" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zoom-block field">
|
||||
<label for="zoomInput">Zoom</label>
|
||||
<input id="zoomInput" type="range" min="4" max="32" step="1" value="12" />
|
||||
<span id="zoomValue">12x</span>
|
||||
</div>
|
||||
<label class="toggle-row compact">
|
||||
<input id="gridToggle" type="checkbox" checked />
|
||||
<span>Grid</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="editor-stage">
|
||||
<div class="checkerboard editor-surface" id="editorSurface">
|
||||
<canvas id="editorCanvas"></canvas>
|
||||
<canvas id="editorGridCanvas"></canvas>
|
||||
</div>
|
||||
<div class="editor-meta">
|
||||
<div id="editorMeta" class="info-stack">
|
||||
<p class="muted">Final output lands here once the solver finishes its little pilgrimage.</p>
|
||||
</div>
|
||||
<div id="summaryPanel" class="summary-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ p {
|
|||
.app-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: minmax(320px, 390px) minmax(0, 1fr);
|
||||
grid-template-columns: minmax(0, 1fr) minmax(320px, 390px);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
|
|
@ -569,7 +569,7 @@ p {
|
|||
|
||||
.viewer-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue