Expand Repixelizer parity product surface
This commit is contained in:
parent
c12d78726a
commit
be84cd539f
14 changed files with 1102 additions and 3 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 |
Binary file not shown.
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 370 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 211 KiB After Width: | Height: | Size: 1.1 MiB |
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue