Expand Repixelizer parity product surface

This commit is contained in:
Meta Vens 2026-06-05 22:44:18 +01:00
commit be84cd539f
14 changed files with 1102 additions and 3 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ tools/deps/
android/app/src/main/assets/current-surface.json
flutter/eve_parity/assets/current-surface.json
flutter/eve_parity/assets/fonts/
flutter/eve_parity/assets/repixelizer/
*.deb
*.ipa
*.dSYM/

View file

@ -193,6 +193,45 @@ class MainActivity : Activity(), SensorEventListener {
cornerRadius = dp(6).toFloat()
}
}
"image.preview" -> previewBox(props.optString("label", "Image"), tokens, square = true)
"canvas.preview" -> previewBox(props.optString("label", "Canvas"), tokens, square = false)
"canvas.editor" -> previewBox(props.optString("label", "Editable canvas"), tokens, square = false, tall = true)
"status.stage" -> LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
setPadding(dp(12), dp(12), dp(12), dp(12))
background = panelBackground(tokens, 4)
addView(label(props.optString("label", "Status"), 12f, tokenColor(tokens, "colorAccent", 0xffff8a2a.toInt()), true))
addView(label(props.optString("stage", ""), 15f, tokenColor(tokens, "colorText", 0xfff6f1e2.toInt()), true))
addView(label(props.optString("detail", ""), 12f, tokenColor(tokens, "colorMuted", 0xffd3bb7f.toInt()), false))
}
"input.file", "dropzone" -> label(props.optString("label", "Choose File"), 14f, tokenColor(tokens, "colorText", 0xfff6f1e2.toInt()), true).apply {
gravity = Gravity.CENTER
setPadding(dp(14), dp(12), dp(14), dp(12))
background = panelBackground(tokens, 6)
}
"input.number", "input.select", "control.range" -> LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(label(props.optString("label", ""), 11f, tokenColor(tokens, "colorAccent", 0xffff8a2a.toInt()), true))
addView(label(props.optString("value", ""), 13f, tokenColor(tokens, "colorText", 0xfff6f1e2.toInt()), false).apply {
setPadding(dp(8), dp(6), dp(8), dp(6))
background = panelBackground(tokens, 2)
})
}
"control.toggle" -> label("${props.optString("label", "Toggle")}: ${if (props.optBoolean("value", false)) "on" else "off"}", 12f, tokenColor(tokens, "colorText", 0xfff6f1e2.toInt()), false).apply {
setPadding(dp(8), dp(6), dp(8), dp(6))
background = panelBackground(tokens, 2)
}
"color.swatch" -> View(this).apply {
background = android.graphics.drawable.GradientDrawable().apply {
setColor(tokenColor(JSONObject().put("value", props.optString("value", "#ffffffff")), "value", Color.WHITE))
setStroke(dp(2), tokenColor(tokens, "colorAccent", 0xffff8a2a.toInt()))
}
layoutParams = ViewGroup.LayoutParams(dp(38), dp(38))
}
"metric" -> LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(label("${props.optString("label", "Metric")}: ${props.opt("value") ?: ""}", 13f, tokenColor(tokens, "colorText", 0xfff6f1e2.toInt()), true))
}
"surface" -> LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
forEachChild(children) { addView(renderCultUiNode(it, values, tokens)) }
@ -301,6 +340,17 @@ class MainActivity : Activity(), SensorEventListener {
}
}
private fun previewBox(labelText: String, tokens: JSONObject, square: Boolean, tall: Boolean = false): View {
return LinearLayout(this).apply {
orientation = LinearLayout.VERTICAL
addView(label(labelText, 12f, tokenColor(tokens, "colorAccent", 0xffff8a2a.toInt()), true))
addView(StageBackgroundView(this@MainActivity, "waiting for pixels", tokenColor(tokens, "colorAccent", 0xffff8a2a.toInt())), LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
if (square) dp(180) else if (tall) dp(280) else dp(160)
))
}
}
private fun forEachChild(children: org.json.JSONArray?, block: (JSONObject) -> Unit) {
if (children == null) return
for (index in 0 until children.length()) block(children.getJSONObject(index))

View file

@ -46,7 +46,7 @@ class _EveParityAppState extends State<EveParityApp> {
)
: state == null
? const SizedBox.shrink()
: EveSurfaceView(state: state),
: SingleChildScrollView(child: EveSurfaceView(state: state)),
),
),
);
@ -220,6 +220,24 @@ class EveNodeView extends StatelessWidget {
return EveSlider(state: state, node: node);
case 'control.button':
return _button();
case 'image.preview':
return _imagePreview();
case 'canvas.preview':
case 'canvas.editor':
return _canvasPlaceholder(editor: node.kind == 'canvas.editor');
case 'status.stage':
return _statusStage();
case 'input.file':
case 'dropzone':
return _button();
case 'input.number':
case 'input.select':
case 'control.range':
return _field();
case 'control.toggle':
return _toggle();
case 'color.swatch':
return _swatch();
case 'vn.stage':
return _vnStage();
case 'image.background':
@ -449,12 +467,159 @@ class EveNodeView extends StatelessWidget {
);
}
Widget _imagePreview() {
final label = node.props['label']?.toString() ?? 'Image';
final assetPath = _previewAssetPath(node.props['src']);
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: 1,
child: DecoratedBox(
decoration: BoxDecoration(
color: state.tokens.panelInset,
border: Border.all(color: state.tokens.border, width: 2),
),
child: Stack(
children: [
Positioned.fill(child: CustomPaint(painter: EveGridPainter(color: state.tokens.accent.withValues(alpha: 0.10)))),
if (assetPath != null)
Positioned.fill(
child: Image.asset(assetPath, fit: BoxFit.cover, filterQuality: FilterQuality.none),
)
else
Center(
child: Padding(
padding: const EdgeInsets.all(10),
child: Text(
label,
textAlign: TextAlign.center,
style: TextStyle(color: state.tokens.muted, fontFamily: 'EveParity', fontSize: 12),
),
),
),
],
),
),
),
const SizedBox(height: 8),
Text(label, style: TextStyle(color: state.tokens.muted, fontFamily: 'EveParity', fontSize: 12)),
],
);
}
Widget _canvasPlaceholder({required bool editor}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
node.props['label']?.toString() ?? 'Canvas',
style: TextStyle(color: state.tokens.accent, fontFamily: 'EveParity', fontSize: 12, fontWeight: FontWeight.w700),
),
const SizedBox(height: 8),
SizedBox(
height: editor ? 280 : 170,
width: double.infinity,
child: DecoratedBox(
decoration: BoxDecoration(
color: editor ? const Color(0xFF142033) : state.tokens.panelInset,
border: Border.all(color: state.tokens.border, width: 2),
),
child: Stack(
children: [
Positioned.fill(child: CustomPaint(painter: EveGridPainter(color: state.tokens.accent.withValues(alpha: editor ? 0.18 : 0.08)))),
Center(
child: Text(
node.props['state']?.toString() ?? '',
style: TextStyle(color: state.tokens.muted, fontFamily: 'EveParity', fontSize: 12),
),
),
],
),
),
),
],
);
}
Widget _statusStage() {
return DecoratedBox(
decoration: BoxDecoration(
color: state.tokens.panelAlt,
border: Border.all(color: state.tokens.border, width: 2),
borderRadius: BorderRadius.circular(4),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(node.props['label']?.toString() ?? 'Status', style: TextStyle(color: state.tokens.accent, fontFamily: 'EveParity', fontSize: 12, fontWeight: FontWeight.w700)),
const SizedBox(height: 6),
Text(node.props['stage']?.toString() ?? '', style: TextStyle(color: state.tokens.text, fontFamily: 'EveParity', fontSize: 15, fontWeight: FontWeight.w700)),
const SizedBox(height: 4),
Text(node.props['detail']?.toString() ?? '', style: TextStyle(color: state.tokens.muted, fontFamily: 'EveParity', fontSize: 12)),
],
),
),
);
}
Widget _field() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(node.props['label']?.toString() ?? '', style: TextStyle(color: state.tokens.accent, fontFamily: 'EveParity', fontSize: 11, fontWeight: FontWeight.w700)),
const SizedBox(height: 4),
DecoratedBox(
decoration: BoxDecoration(color: state.tokens.panelInset, border: Border.all(color: state.tokens.border)),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
child: Text(node.props['value']?.toString() ?? '', style: TextStyle(color: state.tokens.text, fontFamily: 'EveParity', fontSize: 13)),
),
),
],
);
}
Widget _toggle() {
return DecoratedBox(
decoration: BoxDecoration(color: state.tokens.panelInset, border: Border.all(color: state.tokens.border)),
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
'${node.props['label'] ?? 'Toggle'}: ${node.props['value'] == true ? 'on' : 'off'}',
style: TextStyle(color: state.tokens.text, fontFamily: 'EveParity', fontSize: 12),
),
),
);
}
Widget _swatch() {
return Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: _parseColor(node.props['value']) ?? state.tokens.text,
border: Border.all(color: state.tokens.accent, width: 2),
),
);
}
Widget _surface() {
final padding = _number(node.layout['padding'] ?? node.props['padding'], 0);
final direction = node.layout['direction']?.toString();
return Padding(
padding: EdgeInsets.all(padding),
child: LayoutBuilder(
builder: (context, constraints) {
if (direction == 'vertical') {
return _children(axis: Axis.vertical, maxWidth: 1280);
}
if (direction == 'horizontal') {
return _children(axis: Axis.horizontal, maxWidth: 1280);
}
if (constraints.maxWidth >= 900 && node.children.length > 1) {
return _children(axis: Axis.horizontal, maxWidth: 1280);
}
@ -625,6 +790,21 @@ class EveNodeView extends StatelessWidget {
}
Map<String, dynamic> _map(Object? value) => value is Map<String, dynamic> ? value : const {};
Color? _parseColor(Object? value) {
if (value is! String || !value.startsWith('#')) return null;
final body = value.substring(1);
if (body.length == 8) return Color(int.parse(body, radix: 16));
if (body.length == 6) return Color(int.parse('ff$body', radix: 16));
return null;
}
String? _previewAssetPath(Object? source) {
final src = source?.toString() ?? '';
if (src.endsWith('/character-input.png')) return 'assets/repixelizer/character-input.png';
if (src.endsWith('/character-repixelized.png')) return 'assets/repixelizer/character-repixelized.png';
return null;
}
}
class EveGridPainter extends CustomPainter {

View file

@ -53,6 +53,8 @@ flutter:
assets:
- assets/current-surface.json
- assets/repixelizer/character-input.png
- assets/repixelizer/character-repixelized.png
fonts:
- family: EveParity

Binary file not shown.

Before

Width:  |  Height:  |  Size: 251 KiB

After

Width:  |  Height:  |  Size: 2.2 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

After

Width:  |  Height:  |  Size: 370 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before After
Before After

View file

@ -21,6 +21,10 @@ void main() {
await tester.pump(const Duration(milliseconds: 100));
await tester.pumpAndSettle();
await tester.pumpAndSettle();
await tester.runAsync(() async {
await Future<void>.delayed(const Duration(milliseconds: 250));
});
await tester.pumpAndSettle();
expect(find.byType(EveSurfaceView), findsOneWidget);
if (fixtureId == 'cultui-inspector') {
expect(find.text('AETHERIC FIELD TESTER'), findsOneWidget);

View file

@ -14,10 +14,12 @@ $flutterRoot = Join-Path $projectRoot "flutter\eve_parity"
$flutter = Join-Path $projectRoot "tools\deps\flutter\bin\flutter.bat"
$assetPath = Join-Path $flutterRoot "assets\current-surface.json"
$fontDir = Join-Path $flutterRoot "assets\fonts"
$repixelizerAssetDir = Join-Path $flutterRoot "assets\repixelizer"
$absoluteOutput = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path $projectRoot $OutputPath }
New-Item -ItemType Directory -Force (Split-Path -Parent $absoluteOutput) | Out-Null
New-Item -ItemType Directory -Force (Split-Path -Parent $assetPath) | Out-Null
New-Item -ItemType Directory -Force $fontDir | Out-Null
New-Item -ItemType Directory -Force $repixelizerAssetDir | Out-Null
if (-not (Test-Path $flutter)) {
throw "Flutter is not installed at $flutter"
@ -29,6 +31,8 @@ if ($Target -eq "linux" -and $env:OS -like "Windows*") {
Copy-Item -LiteralPath (Join-Path $projectRoot "tools\deps\flutter\bin\cache\artifacts\material_fonts\roboto-regular.ttf") -Destination (Join-Path $fontDir "Roboto-Regular.ttf") -Force
Copy-Item -LiteralPath (Join-Path $projectRoot "tools\deps\flutter\bin\cache\artifacts\material_fonts\roboto-bold.ttf") -Destination (Join-Path $fontDir "Roboto-Bold.ttf") -Force
curl.exe -L "https://repixelizer.gamecult.org/app/landing-assets/character-input.png" -o (Join-Path $repixelizerAssetDir "character-input.png") | Out-Host
curl.exe -L "https://repixelizer.gamecult.org/app/landing-assets/character-repixelized.png" -o (Join-Path $repixelizerAssetDir "character-repixelized.png") | Out-Host
node .\tools\parity\export-fixture.mjs $FixtureId $assetPath | Out-Host
if ($LASTEXITCODE -ne 0) {

View file

@ -14,11 +14,13 @@ $projectRoot = Split-Path -Parent $PSScriptRoot
$sourceRoot = Join-Path $projectRoot "flutter\eve_parity"
$assetPath = Join-Path $sourceRoot "assets\current-surface.json"
$fontDir = Join-Path $sourceRoot "assets\fonts"
$repixelizerAssetDir = Join-Path $sourceRoot "assets\repixelizer"
$absoluteOutput = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path $projectRoot $OutputPath }
$archivePath = Join-Path ([System.IO.Path]::GetTempPath()) "eve-parity-linux-$([guid]::NewGuid()).tar"
New-Item -ItemType Directory -Force (Split-Path -Parent $absoluteOutput) | Out-Null
New-Item -ItemType Directory -Force (Split-Path -Parent $assetPath) | Out-Null
New-Item -ItemType Directory -Force $fontDir | Out-Null
New-Item -ItemType Directory -Force $repixelizerAssetDir | Out-Null
if (-not (Test-Path $sourceRoot)) {
throw "Flutter parity source not found: $sourceRoot"
@ -30,6 +32,8 @@ if ($LASTEXITCODE -ne 0) {
}
Copy-Item -LiteralPath (Join-Path $projectRoot "tools\deps\flutter\bin\cache\artifacts\material_fonts\roboto-regular.ttf") -Destination (Join-Path $fontDir "Roboto-Regular.ttf") -Force
Copy-Item -LiteralPath (Join-Path $projectRoot "tools\deps\flutter\bin\cache\artifacts\material_fonts\roboto-bold.ttf") -Destination (Join-Path $fontDir "Roboto-Bold.ttf") -Force
curl.exe -L "https://repixelizer.gamecult.org/app/landing-assets/character-input.png" -o (Join-Path $repixelizerAssetDir "character-input.png") | Out-Host
curl.exe -L "https://repixelizer.gamecult.org/app/landing-assets/character-repixelized.png" -o (Join-Path $repixelizerAssetDir "character-repixelized.png") | Out-Host
try {
Push-Location $sourceRoot

View file

@ -63,7 +63,20 @@
},
"expect": {
"providerId": "repixelizer",
"componentKinds": ["surface", "pane", "card", "text"],
"componentKinds": [
"surface",
"pane",
"card",
"text",
"image.preview",
"canvas.preview",
"canvas.editor",
"input.file",
"dropzone",
"input.number",
"control.button",
"status.stage"
],
"styleTokens": [
"colorBackgroundTop",
"colorPanel",

View file

@ -165,6 +165,697 @@
},
"text": "Repixelizer hosted demo"
},
{
"children": [
{
"children": [],
"id": "repixelizer.product.pitch",
"kind": "text",
"props": {
"role": "strong",
"text": "You need pixel art for a project, but pixel art is a craft and your favorite AI is mostly vibes in a trench coat."
},
"role": "strong",
"text": "You need pixel art for a project, but pixel art is a craft and your favorite AI is mostly vibes in a trench coat."
},
{
"children": [
{
"children": [
{
"children": [],
"id": "repixelizer.product.input.image",
"kind": "image.preview",
"props": {
"aspectRatio": "1/1",
"imageRendering": "pixelated",
"label": "AI-generated hazmat character before repixelizing",
"role": "body",
"src": "https://repixelizer.gamecult.org/app/landing-assets/character-input.png"
},
"role": "body",
"text": "AI-generated hazmat character before repixelizing"
},
{
"children": [],
"id": "repixelizer.product.input.copy",
"kind": "text",
"props": {
"role": "body",
"text": "Looks charming from orbit. Suspiciously charming."
},
"role": "body",
"text": "Looks charming from orbit. Suspiciously charming."
}
],
"id": "repixelizer.product.input",
"kind": "card",
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "The AI gives you this"
},
"role": "group",
"style": {
"tone": "cool",
"variant": "repixelizer-retro-card"
},
"text": "The AI gives you this"
},
{
"children": [
{
"children": [],
"id": "repixelizer.product.closeup.image",
"kind": "image.preview",
"props": {
"aspectRatio": "1/1",
"crop": "42% 63%",
"imageRendering": "pixelated",
"label": "Zoomed close-up showing fake pixel-art artifacts",
"role": "body",
"src": "https://repixelizer.gamecult.org/app/landing-assets/character-input.png",
"zoom": 3.45
},
"role": "body",
"text": "Zoomed close-up showing fake pixel-art artifacts"
},
{
"children": [],
"id": "repixelizer.product.closeup.copy",
"kind": "text",
"props": {
"role": "body",
"text": "Smears, half-cells, anti-grid nonsense. The crime scene has opinions."
},
"role": "body",
"text": "Smears, half-cells, anti-grid nonsense. The crime scene has opinions."
}
],
"id": "repixelizer.product.closeup",
"kind": "card",
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Then you zoom in"
},
"role": "group",
"style": {
"tone": "cool",
"variant": "repixelizer-retro-card"
},
"text": "Then you zoom in"
},
{
"children": [
{
"children": [],
"id": "repixelizer.product.output.image",
"kind": "image.preview",
"props": {
"aspectRatio": "1/1",
"imageRendering": "pixelated",
"label": "Repixelized hazmat character output",
"role": "body",
"src": "https://repixelizer.gamecult.org/app/landing-assets/character-repixelized.png"
},
"role": "body",
"text": "Repixelized hazmat character output"
},
{
"children": [],
"id": "repixelizer.product.output.copy",
"kind": "text",
"props": {
"role": "body",
"text": "Real cells on a real lattice, ready for cleanup or export."
},
"role": "body",
"text": "Real cells on a real lattice, ready for cleanup or export."
}
],
"id": "repixelizer.product.output",
"kind": "card",
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Repixelizer spits out this"
},
"role": "group",
"style": {
"tone": "cool",
"variant": "repixelizer-retro-card"
},
"text": "Repixelizer spits out this"
}
],
"id": "repixelizer.product.gallery",
"kind": "partition",
"layout": {
"direction": "horizontal",
"gap": 12
},
"props": {
"gap": 12,
"role": "product.gallery",
"split": "x"
},
"role": "gallery"
}
],
"id": "repixelizer.product.landing",
"kind": "pane",
"layout": {
"direction": "vertical",
"padding": 10
},
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Hosted demo"
},
"role": "section",
"style": {
"tone": "warm",
"variant": "repixelizer-retro-pane"
},
"text": "Hosted demo"
},
{
"children": [
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.status",
"kind": "status.stage",
"props": {
"detail": "Choose a file, then run the machine.",
"label": "Idle",
"role": "body",
"stage": "Waiting for input"
},
"role": "body",
"text": "Idle"
},
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.input.canvas",
"kind": "canvas.preview",
"props": {
"label": "Input",
"role": "body",
"state": "empty"
},
"role": "body",
"text": "Input"
},
{
"children": [],
"id": "repixelizer.workspace.output.canvas",
"kind": "canvas.preview",
"props": {
"label": "Output",
"role": "body",
"state": "empty"
},
"role": "body",
"text": "Output"
}
],
"id": "repixelizer.workspace.compare",
"kind": "partition",
"layout": {
"direction": "horizontal",
"gap": 10
},
"props": {
"gap": 10,
"role": "comparison",
"split": "x"
},
"role": "comparison"
},
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.inspect.copy",
"kind": "text",
"props": {
"role": "body",
"text": "Hold either preview to inspect both panels together."
},
"role": "body",
"text": "Hold either preview to inspect both panels together."
},
{
"children": [],
"id": "repixelizer.workspace.inspect.zoom",
"kind": "input.select",
"props": {
"label": "Inspect Zoom",
"role": "field",
"value": "8x"
},
"role": "field",
"text": "Inspect Zoom"
}
],
"id": "repixelizer.workspace.inspect",
"kind": "card",
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Zoom"
},
"role": "group",
"style": {
"tone": "panel",
"variant": "repixelizer-retro-card"
},
"text": "Zoom"
}
],
"id": "repixelizer.workspace.viewer",
"kind": "pane",
"layout": {
"direction": "vertical",
"padding": 10
},
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Pipeline Stages"
},
"role": "section",
"style": {
"tone": "warm",
"variant": "repixelizer-retro-pane"
},
"text": "Pipeline Stages"
},
{
"children": [
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.reset",
"kind": "control.button",
"props": {
"commandId": "repixelizer.editor.reset",
"label": "Reset Output",
"role": "command"
},
"role": "command",
"text": "Reset Output"
},
{
"children": [],
"id": "repixelizer.workspace.export",
"kind": "control.button",
"props": {
"commandId": "repixelizer.editor.export",
"label": "Export PNG",
"role": "command"
},
"role": "command",
"text": "Export PNG"
}
],
"id": "repixelizer.workspace.editor.actions",
"kind": "partition",
"layout": {
"direction": "horizontal",
"gap": 8
},
"props": {
"gap": 8,
"role": "toolbar",
"split": "x"
},
"role": "toolbar"
},
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.swatch",
"kind": "color.swatch",
"props": {
"label": "Paint",
"role": "body",
"value": "#ffffffff"
},
"role": "body",
"text": "Paint"
},
{
"children": [],
"id": "repixelizer.workspace.paint.r",
"kind": "input.number",
"props": {
"label": "R",
"role": "field",
"value": "255"
},
"role": "field",
"text": "R"
},
{
"children": [],
"id": "repixelizer.workspace.paint.g",
"kind": "input.number",
"props": {
"label": "G",
"role": "field",
"value": "255"
},
"role": "field",
"text": "G"
},
{
"children": [],
"id": "repixelizer.workspace.paint.b",
"kind": "input.number",
"props": {
"label": "B",
"role": "field",
"value": "255"
},
"role": "field",
"text": "B"
},
{
"children": [],
"id": "repixelizer.workspace.paint.a",
"kind": "input.number",
"props": {
"label": "A",
"role": "field",
"value": "255"
},
"role": "field",
"text": "A"
},
{
"children": [],
"id": "repixelizer.workspace.zoom",
"kind": "control.range",
"props": {
"label": "Zoom",
"max": 32,
"min": 4,
"role": "field",
"value": "12x"
},
"role": "field",
"text": "Zoom"
},
{
"children": [],
"id": "repixelizer.workspace.grid",
"kind": "control.toggle",
"props": {
"label": "Grid",
"role": "body",
"value": true
},
"role": "body",
"text": "Grid"
}
],
"id": "repixelizer.workspace.paint",
"kind": "partition",
"layout": {
"direction": "horizontal",
"gap": 6
},
"props": {
"gap": 6,
"role": "paint-controls",
"split": "x"
},
"role": "paint-controls"
},
{
"children": [],
"id": "repixelizer.workspace.editor.canvas",
"kind": "canvas.editor",
"props": {
"label": "Editable output canvas",
"role": "body",
"state": "empty"
},
"role": "body",
"text": "Editable output canvas"
}
],
"id": "repixelizer.workspace.editor",
"kind": "pane",
"layout": {
"direction": "vertical",
"padding": 10
},
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Pixel Fixes"
},
"role": "section",
"style": {
"tone": "warm",
"variant": "repixelizer-retro-pane"
},
"text": "Pixel Fixes"
},
{
"children": [
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.input.copy",
"kind": "text",
"props": {
"role": "body",
"text": "Start with the ugly input."
},
"role": "body",
"text": "Start with the ugly input."
},
{
"children": [],
"id": "repixelizer.workspace.file",
"kind": "input.file",
"props": {
"accept": "image/png,image/*",
"label": "Choose File",
"role": "body"
},
"role": "body",
"text": "Choose File"
},
{
"children": [],
"id": "repixelizer.workspace.dropzone",
"kind": "dropzone",
"props": {
"label": "PNG, sprite scrap, cursed logo. Whatever started this.",
"role": "body"
},
"role": "body",
"text": "PNG, sprite scrap, cursed logo. Whatever started this."
}
],
"id": "repixelizer.workspace.input",
"kind": "card",
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Input"
},
"role": "group",
"style": {
"tone": "panel",
"variant": "repixelizer-retro-card"
},
"text": "Input"
},
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.target.size",
"kind": "input.number",
"props": {
"label": "Target Size",
"role": "field",
"value": "auto"
},
"role": "field",
"text": "Target Size"
},
{
"children": [],
"id": "repixelizer.workspace.target.width",
"kind": "input.number",
"props": {
"label": "Target Width",
"role": "field",
"value": "auto"
},
"role": "field",
"text": "Target Width"
},
{
"children": [],
"id": "repixelizer.workspace.target.height",
"kind": "input.number",
"props": {
"label": "Target Height",
"role": "field",
"value": "auto"
},
"role": "field",
"text": "Target Height"
},
{
"children": [],
"id": "repixelizer.workspace.steps",
"kind": "input.number",
"props": {
"label": "Steps",
"role": "field",
"value": "48"
},
"role": "field",
"text": "Steps"
},
{
"children": [],
"id": "repixelizer.workspace.run",
"kind": "control.button",
"props": {
"commandId": "repixelizer.job.submit",
"label": "Run The Machine",
"method": "POST",
"role": "command",
"route": "/api/jobs"
},
"role": "command",
"text": "Run The Machine"
}
],
"id": "repixelizer.workspace.controls",
"kind": "card",
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Controls"
},
"role": "group",
"style": {
"tone": "panel",
"variant": "repixelizer-retro-card"
},
"text": "Controls"
},
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.queue.depth",
"kind": "metric",
"props": {
"label": "Queue",
"max": 32,
"role": "body",
"value": 0
},
"role": "body",
"text": "Queue"
},
{
"children": [],
"id": "repixelizer.workspace.queue.worker",
"kind": "metric",
"props": {
"label": "Worker",
"max": 1,
"role": "body",
"value": 0
},
"role": "body",
"text": "Worker"
}
],
"id": "repixelizer.workspace.queue",
"kind": "card",
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Queue"
},
"role": "group",
"style": {
"tone": "panel",
"variant": "repixelizer-retro-card"
},
"text": "Queue"
},
{
"children": [
{
"children": [],
"id": "repixelizer.workspace.lattice.copy",
"kind": "text",
"props": {
"role": "body",
"text": "No lattice picked yet."
},
"role": "body",
"text": "No lattice picked yet."
}
],
"id": "repixelizer.workspace.lattice",
"kind": "card",
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Lattice"
},
"role": "group",
"style": {
"tone": "panel",
"variant": "repixelizer-retro-card"
},
"text": "Lattice"
}
],
"id": "repixelizer.workspace.sidebar",
"kind": "pane",
"layout": {
"direction": "vertical",
"padding": 10
},
"props": {
"styleProfile": "repixelizer.retro.pixel",
"title": "Run Controls"
},
"role": "section",
"style": {
"tone": "warm",
"variant": "repixelizer-retro-pane"
},
"text": "Run Controls"
}
],
"id": "repixelizer.workspace",
"kind": "partition",
"layout": {
"direction": "horizontal",
"gap": 12
},
"props": {
"gap": 12,
"role": "repixelizer.workspace",
"split": "x"
},
"role": "workspace"
},
{
"children": [
{
@ -553,6 +1244,6 @@
},
"title": "Repixelizer",
"type": "surface-state",
"updatedAt": "2026-06-04T00:00:00Z",
"updatedAt": "2026-06-05T00:00:00Z",
"version": 1
}

View file

@ -284,6 +284,14 @@ p {
align-content: start;
}
.cultui-surface-root[data-direction="vertical"] {
grid-template-columns: minmax(0, 1fr);
}
.cultui-surface-root[data-direction="horizontal"] {
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}
.cultui-pane {
border-width: var(--border-width);
border-bottom-width: var(--border-bottom-width);
@ -593,6 +601,83 @@ body[data-pixel-art="true"] .cultui-background {
color: var(--accent);
}
.cultui-image-preview {
display: grid;
gap: 8px;
margin: 0;
}
.cultui-image-frame,
.cultui-canvas-grid {
display: grid;
place-items: center;
min-height: 160px;
border: 2px solid var(--panel-border);
background:
repeating-linear-gradient(45deg, rgba(247, 239, 216, 0.06) 0 8px, rgba(0, 0, 0, 0.18) 8px 16px),
linear-gradient(180deg, #07172c, #0a2039);
overflow: hidden;
}
.cultui-image-frame {
aspect-ratio: 1 / 1;
}
.cultui-image-frame img {
width: 100%;
height: 100%;
object-fit: cover;
}
.cultui-image-preview figcaption,
.cultui-canvas-grid {
color: var(--muted);
font-size: 0.75rem;
line-height: 1.25;
}
.cultui-canvas-placeholder {
display: grid;
gap: 8px;
}
.cultui-canvas-placeholder.editor .cultui-canvas-grid {
min-height: 280px;
background:
linear-gradient(45deg, rgba(255,255,255,0.08) 25%, transparent 25% 75%, rgba(255,255,255,0.08) 75%),
linear-gradient(45deg, rgba(255,255,255,0.08) 25%, transparent 25% 75%, rgba(255,255,255,0.08) 75%),
#142033;
background-position: 0 0, 8px 8px;
background-size: 16px 16px;
}
.cultui-status-stage {
display: grid;
gap: 8px;
padding: 12px;
border: 2px solid var(--panel-border);
background: rgba(6, 22, 39, 0.92);
}
.cultui-field {
display: grid;
gap: 4px;
}
.cultui-field-value {
display: block;
border: 2px solid var(--panel-border);
background: rgba(1, 4, 11, 0.58);
padding: 6px 8px;
}
.cultui-swatch {
width: 36px;
min-height: 36px;
border: 2px solid var(--accent);
box-shadow: inset 0 0 0 2px rgba(0,0,0,0.45);
}
@media (max-width: 900px) {
.cultui-graph {
right: 12px;

View file

@ -252,6 +252,7 @@ function applySurfaceStyles(styles) {
function renderCultComponent(node) {
const kind = node.kind || "panel";
const props = node.props || {};
const layout = node.layout || {};
const children = node.children || [];
let rendered;
@ -322,6 +323,7 @@ function renderCultComponent(node) {
if (kind === "surface") {
const surface = el("section", "cultui-surface-root");
if (layout.direction) surface.dataset.direction = layout.direction;
for (const child of children) surface.append(renderCultComponent(child));
return surface;
}
@ -378,6 +380,69 @@ function renderCultComponent(node) {
return renderCultMetric(props);
}
if (kind === "image.preview") {
const figure = el("figure", "cultui-image-preview");
const frame = el("div", "cultui-image-frame");
if (props.src) {
const image = el("img");
image.src = props.src;
image.alt = props.label || "";
image.style.imageRendering = props.imageRendering || "auto";
if (props.zoom) {
image.style.transform = `scale(${props.zoom})`;
image.style.transformOrigin = props.crop || "50% 50%";
}
frame.append(image);
} else {
frame.append(el("span", "", props.label || "image"));
}
figure.append(frame);
if (props.label) figure.append(el("figcaption", "", props.label));
return figure;
}
if (kind === "canvas.preview" || kind === "canvas.editor") {
const panel = el("div", `cultui-canvas-placeholder ${kind === "canvas.editor" ? "editor" : "preview"}`);
panel.append(el("div", "viz-label", props.label || "Canvas"));
panel.append(el("div", "cultui-canvas-grid", props.state === "empty" ? "waiting for pixels" : props.state || ""));
return panel;
}
if (kind === "status.stage") {
const panel = el("section", "cultui-status-stage");
panel.append(el("span", "status-badge", props.label || "Status"));
panel.append(el("p", "status-stage", props.stage || ""));
panel.append(el("p", "status-text", props.detail || ""));
return panel;
}
if (kind === "input.file" || kind === "dropzone") {
const control = el("button", kind === "dropzone" ? "dropzone cultui-dropzone" : "cultui-button button-secondary", props.label || "Choose File");
control.type = "button";
return control;
}
if (kind === "input.number" || kind === "input.select" || kind === "control.range") {
const field = el("label", "field cultui-field");
field.append(el("span", "field-label", props.label || ""));
field.append(el("span", "field-control cultui-field-value", props.value === undefined ? "" : String(props.value)));
return field;
}
if (kind === "control.toggle") {
const toggle = el("label", "toggle-row cultui-toggle");
toggle.append(el("span", "field-control", props.value ? "on" : "off"));
toggle.append(el("span", "toggle-copy", props.label || "Toggle"));
return toggle;
}
if (kind === "color.swatch") {
const swatch = el("div", "paint-swatch cultui-swatch");
swatch.style.background = props.value || props.color || "currentColor";
swatch.title = props.label || "Color";
return swatch;
}
if (kind === "list") {
return renderCultList(props);
}