@ai-game-assets/phaser 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animations.d.ts +2 -0
- package/dist/animations.d.ts.map +1 -1
- package/dist/animations.js +4 -3
- package/dist/animations.js.map +1 -1
- package/dist/debug-client.d.ts +55 -2
- package/dist/debug-client.d.ts.map +1 -1
- package/dist/debug-client.js +77 -0
- package/dist/debug-client.js.map +1 -1
- package/dist/designer-support.d.ts +29 -3
- package/dist/designer-support.d.ts.map +1 -1
- package/dist/designer-support.js +557 -29
- package/dist/designer-support.js.map +1 -1
- package/dist/designer.d.ts +19 -2
- package/dist/designer.d.ts.map +1 -1
- package/dist/designer.js +1897 -143
- package/dist/designer.js.map +1 -1
- package/dist/first-drafts.d.ts.map +1 -1
- package/dist/first-drafts.js +68 -40
- package/dist/first-drafts.js.map +1 -1
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keys.d.ts +2 -0
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +6 -0
- package/dist/keys.js.map +1 -1
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +50 -3
- package/dist/loader.js.map +1 -1
- package/dist/phaser-types.d.ts +1 -0
- package/dist/phaser-types.d.ts.map +1 -1
- package/dist/runtime.d.ts +30 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +235 -8
- package/dist/runtime.js.map +1 -1
- package/dist/tileset-dialog.d.ts +121 -0
- package/dist/tileset-dialog.d.ts.map +1 -0
- package/dist/tileset-dialog.js +1594 -0
- package/dist/tileset-dialog.js.map +1 -0
- package/dist/tilesets.d.ts +13 -0
- package/dist/tilesets.d.ts.map +1 -0
- package/dist/tilesets.js +60 -0
- package/dist/tilesets.js.map +1 -0
- package/dist/voice-line-regeneration.d.ts +47 -0
- package/dist/voice-line-regeneration.d.ts.map +1 -0
- package/dist/voice-line-regeneration.js +185 -0
- package/dist/voice-line-regeneration.js.map +1 -0
- package/package.json +3 -2
package/dist/designer-support.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { aiTextureKey } from "./keys.js";
|
|
1
2
|
const assetFormatOptions = [
|
|
2
3
|
{ value: "png", label: "PNG" },
|
|
3
4
|
{ value: "jpg", label: "JPEG" },
|
|
@@ -69,6 +70,19 @@ export function createDesignerElements(options, manifest, selectedAssetId) {
|
|
|
69
70
|
const promptInput = document.createElement("textarea");
|
|
70
71
|
promptInput.className = "ai-game-assets-designer__prompt";
|
|
71
72
|
promptInput.rows = 6;
|
|
73
|
+
const promptField = labelWrap("Prompt", promptInput);
|
|
74
|
+
const tilesetPromptsField = document.createElement("section");
|
|
75
|
+
tilesetPromptsField.className = "ai-game-assets-designer__tile-prompts";
|
|
76
|
+
tilesetPromptsField.hidden = true;
|
|
77
|
+
const tilesetPromptsTitle = document.createElement("div");
|
|
78
|
+
tilesetPromptsTitle.className = "ai-game-assets-designer__tile-prompts-title";
|
|
79
|
+
tilesetPromptsTitle.textContent = "Tile prompts";
|
|
80
|
+
const tilesetPromptsHint = document.createElement("div");
|
|
81
|
+
tilesetPromptsHint.className = "ai-game-assets-designer__tile-prompts-hint";
|
|
82
|
+
tilesetPromptsHint.textContent = "Describe each tile in exact sheet order.";
|
|
83
|
+
const tilesetPromptsList = document.createElement("div");
|
|
84
|
+
tilesetPromptsList.className = "ai-game-assets-designer__tile-prompts-list";
|
|
85
|
+
tilesetPromptsField.append(tilesetPromptsTitle, tilesetPromptsHint, tilesetPromptsList);
|
|
72
86
|
const widthInput = numericInput();
|
|
73
87
|
const heightInput = numericInput();
|
|
74
88
|
const frameCountInput = numericInput();
|
|
@@ -98,7 +112,9 @@ export function createDesignerElements(options, manifest, selectedAssetId) {
|
|
|
98
112
|
}
|
|
99
113
|
const dimensionGrid = document.createElement("div");
|
|
100
114
|
dimensionGrid.className = "ai-game-assets-designer__dimensions";
|
|
101
|
-
|
|
115
|
+
const widthField = labelWrap("Width", widthInput);
|
|
116
|
+
const heightField = labelWrap("Height", heightInput);
|
|
117
|
+
dimensionGrid.append(widthField, heightField);
|
|
102
118
|
const frameCountField = labelWrap("Frames", frameCountInput);
|
|
103
119
|
const formatField = labelWrap("Format", formatSelect);
|
|
104
120
|
const audioFormatField = labelWrap("Audio format", audioFormatSelect);
|
|
@@ -135,6 +151,14 @@ export function createDesignerElements(options, manifest, selectedAssetId) {
|
|
|
135
151
|
const regenerateButton = document.createElement("button");
|
|
136
152
|
regenerateButton.type = "button";
|
|
137
153
|
regenerateButton.textContent = "Regenerate";
|
|
154
|
+
const regenerateAllLinesButton = document.createElement("button");
|
|
155
|
+
regenerateAllLinesButton.type = "button";
|
|
156
|
+
regenerateAllLinesButton.textContent = "Regenerate all lines";
|
|
157
|
+
regenerateAllLinesButton.hidden = true;
|
|
158
|
+
const mixTilesetButton = document.createElement("button");
|
|
159
|
+
mixTilesetButton.type = "button";
|
|
160
|
+
mixTilesetButton.textContent = "Mix tileset";
|
|
161
|
+
mixTilesetButton.hidden = true;
|
|
138
162
|
const uploadButton = document.createElement("button");
|
|
139
163
|
uploadButton.type = "button";
|
|
140
164
|
uploadButton.textContent = "Upload...";
|
|
@@ -148,19 +172,23 @@ export function createDesignerElements(options, manifest, selectedAssetId) {
|
|
|
148
172
|
promoteButton.type = "button";
|
|
149
173
|
promoteButton.textContent = "Promote";
|
|
150
174
|
promoteButton.disabled = true;
|
|
175
|
+
const promoteAllButton = document.createElement("button");
|
|
176
|
+
promoteAllButton.type = "button";
|
|
177
|
+
promoteAllButton.textContent = "Promote all";
|
|
178
|
+
promoteAllButton.disabled = true;
|
|
151
179
|
const restartButton = document.createElement("button");
|
|
152
180
|
restartButton.type = "button";
|
|
153
181
|
restartButton.textContent = "Restart";
|
|
154
182
|
const actions = document.createElement("div");
|
|
155
183
|
actions.className = "ai-game-assets-designer__actions";
|
|
156
|
-
actions.append(regenerateButton, uploadButton, deriveButton, versionsButton, promoteButton, restartButton);
|
|
184
|
+
actions.append(regenerateButton, regenerateAllLinesButton, mixTilesetButton, uploadButton, deriveButton, versionsButton, promoteButton, promoteAllButton, restartButton);
|
|
157
185
|
const versionLabel = document.createElement("div");
|
|
158
186
|
versionLabel.className = "ai-game-assets-designer__meta";
|
|
159
187
|
const optionsGrid = document.createElement("div");
|
|
160
188
|
optionsGrid.className = "ai-game-assets-designer__options";
|
|
161
189
|
const status = document.createElement("div");
|
|
162
190
|
status.className = "ai-game-assets-designer__status";
|
|
163
|
-
panel.append(header, targetField, labelWrap("Asset", assetBrowser), targetVariantLabel, animationField, dimensionGrid, frameCountField, formatField, audioFormatField, audioDurationField, audioLoopField, voiceTextField, labelWrap("Current", currentPreview),
|
|
191
|
+
panel.append(header, targetField, labelWrap("Asset", assetBrowser), targetVariantLabel, animationField, dimensionGrid, frameCountField, formatField, audioFormatField, audioDurationField, audioLoopField, voiceTextField, labelWrap("Current", currentPreview), promptField, tilesetPromptsField, actions, versionLabel, optionsGrid, status);
|
|
164
192
|
root.append(toggle, panel);
|
|
165
193
|
return {
|
|
166
194
|
root,
|
|
@@ -176,7 +204,9 @@ export function createDesignerElements(options, manifest, selectedAssetId) {
|
|
|
176
204
|
animationSelect,
|
|
177
205
|
animationField,
|
|
178
206
|
widthInput,
|
|
207
|
+
widthField,
|
|
179
208
|
heightInput,
|
|
209
|
+
heightField,
|
|
180
210
|
dimensionGrid,
|
|
181
211
|
frameCountInput,
|
|
182
212
|
frameCountField,
|
|
@@ -191,6 +221,9 @@ export function createDesignerElements(options, manifest, selectedAssetId) {
|
|
|
191
221
|
voiceTextInput,
|
|
192
222
|
voiceTextField,
|
|
193
223
|
promptInput,
|
|
224
|
+
promptField,
|
|
225
|
+
tilesetPromptsField,
|
|
226
|
+
tilesetPromptsList,
|
|
194
227
|
currentImage,
|
|
195
228
|
currentAudio,
|
|
196
229
|
currentAnimation,
|
|
@@ -201,8 +234,11 @@ export function createDesignerElements(options, manifest, selectedAssetId) {
|
|
|
201
234
|
uploadButton,
|
|
202
235
|
deriveButton,
|
|
203
236
|
regenerateButton,
|
|
237
|
+
regenerateAllLinesButton,
|
|
238
|
+
mixTilesetButton,
|
|
204
239
|
versionsButton,
|
|
205
240
|
promoteButton,
|
|
241
|
+
promoteAllButton,
|
|
206
242
|
restartButton,
|
|
207
243
|
versionLabel,
|
|
208
244
|
options: optionsGrid,
|
|
@@ -406,6 +442,8 @@ export function renderOptions(options) {
|
|
|
406
442
|
options.elements.options.append(card);
|
|
407
443
|
}
|
|
408
444
|
}
|
|
445
|
+
const pendingImagePreviews = new WeakMap();
|
|
446
|
+
let promotedTextureInstallId = 0;
|
|
409
447
|
export function previewOption(options) {
|
|
410
448
|
const textureKey = `ai-preview:${options.assetId}:${options.option.index}:${Date.now()}`;
|
|
411
449
|
previewImageSource({
|
|
@@ -425,25 +463,80 @@ export function previewCurrentAsset(options) {
|
|
|
425
463
|
textureKey
|
|
426
464
|
});
|
|
427
465
|
}
|
|
466
|
+
export function installPromotedImageTexture(options) {
|
|
467
|
+
const textureKey = aiTextureKey({ assetId: options.assetId });
|
|
468
|
+
promotedTextureInstallId += 1;
|
|
469
|
+
const requestKey = `promote:${options.assetId}:${promotedTextureInstallId}`;
|
|
470
|
+
return new Promise((resolve, reject) => {
|
|
471
|
+
previewImageSource({
|
|
472
|
+
...options,
|
|
473
|
+
textureKey,
|
|
474
|
+
requestKey,
|
|
475
|
+
onPreview(assetId, installedTextureKey, asset) {
|
|
476
|
+
resolve({ assetId, textureKey: installedTextureKey, asset });
|
|
477
|
+
},
|
|
478
|
+
onError: reject
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
}
|
|
428
482
|
export function previewImageSource(options) {
|
|
429
483
|
const image = new Image();
|
|
484
|
+
const pendingForScene = pendingImagePreviews.get(options.scene) ?? new Map();
|
|
485
|
+
const requestKey = options.requestKey ?? options.assetId;
|
|
486
|
+
const previousImage = pendingForScene.get(requestKey);
|
|
487
|
+
if (previousImage) {
|
|
488
|
+
previousImage.onload = null;
|
|
489
|
+
previousImage.onerror = null;
|
|
490
|
+
}
|
|
491
|
+
pendingForScene.set(requestKey, image);
|
|
492
|
+
pendingImagePreviews.set(options.scene, pendingForScene);
|
|
493
|
+
if (shouldUseAnonymousCrossOrigin(options.src)) {
|
|
494
|
+
image.crossOrigin = "anonymous";
|
|
495
|
+
}
|
|
430
496
|
image.onload = () => {
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
497
|
+
if (pendingForScene.get(requestKey) !== image)
|
|
498
|
+
return;
|
|
499
|
+
pendingForScene.delete(requestKey);
|
|
500
|
+
try {
|
|
501
|
+
if (options.scene.textures.exists(options.textureKey)) {
|
|
502
|
+
options.scene.textures.remove(options.textureKey);
|
|
503
|
+
}
|
|
504
|
+
const asset = options.assetOverride ?? options.manifest.assets[options.assetId];
|
|
505
|
+
const tileset = designerTilesetMetadata(asset);
|
|
506
|
+
if (tileset && options.scene.textures.addSpriteSheet) {
|
|
507
|
+
options.scene.textures.addSpriteSheet(options.textureKey, image, {
|
|
508
|
+
frameWidth: tileset.tileWidth,
|
|
509
|
+
frameHeight: tileset.tileHeight,
|
|
510
|
+
margin: tileset.margin,
|
|
511
|
+
spacing: tileset.spacing
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
else if (asset.frameGrid && options.scene.textures.addSpriteSheet) {
|
|
515
|
+
options.scene.textures.addSpriteSheet(options.textureKey, image, {
|
|
516
|
+
frameWidth: asset.frameGrid.frameWidth,
|
|
517
|
+
frameHeight: asset.frameGrid.frameHeight,
|
|
518
|
+
margin: asset.frameGrid.margin,
|
|
519
|
+
spacing: asset.frameGrid.spacing
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
options.scene.textures.addImage(options.textureKey, image);
|
|
524
|
+
}
|
|
525
|
+
options.onPreview(options.assetId, options.textureKey, asset);
|
|
442
526
|
}
|
|
443
|
-
|
|
444
|
-
|
|
527
|
+
catch (error) {
|
|
528
|
+
const previewError = error instanceof Error ? error : new Error(String(error));
|
|
529
|
+
if (options.onError)
|
|
530
|
+
options.onError(previewError);
|
|
531
|
+
else
|
|
532
|
+
console.error("Could not preview AI asset image.", previewError);
|
|
445
533
|
}
|
|
446
|
-
|
|
534
|
+
};
|
|
535
|
+
image.onerror = () => {
|
|
536
|
+
if (pendingForScene.get(requestKey) === image) {
|
|
537
|
+
pendingForScene.delete(requestKey);
|
|
538
|
+
}
|
|
539
|
+
options.onError?.(new Error(`Could not load preview image for "${options.assetId}".`));
|
|
447
540
|
};
|
|
448
541
|
image.src = options.src;
|
|
449
542
|
}
|
|
@@ -471,7 +564,9 @@ export async function uploadedOptionFromFile(options) {
|
|
|
471
564
|
};
|
|
472
565
|
}
|
|
473
566
|
const imageSize = await imageSizeFromSource(dataUrl);
|
|
474
|
-
const
|
|
567
|
+
const format = normalizeAssetFormatFromMimeType(mimeType, options.file.name);
|
|
568
|
+
const generationGeometry = generationOverridesFromInputs(options.elements, options.asset, format);
|
|
569
|
+
const geometry = uploadedImageGeometry(options.asset, imageSize, generationGeometry.tileset);
|
|
475
570
|
return {
|
|
476
571
|
index: 0,
|
|
477
572
|
dataUrl,
|
|
@@ -480,13 +575,38 @@ export async function uploadedOptionFromFile(options) {
|
|
|
480
575
|
model: "uploaded",
|
|
481
576
|
dimensions: geometry.dimensions,
|
|
482
577
|
frameGrid: geometry.frameGrid,
|
|
578
|
+
tileset: geometry.tileset,
|
|
483
579
|
settings: {
|
|
484
580
|
...options.asset.settings,
|
|
485
|
-
format
|
|
581
|
+
format
|
|
486
582
|
}
|
|
487
583
|
};
|
|
488
584
|
}
|
|
489
|
-
export function uploadedImageGeometry(asset, imageSize) {
|
|
585
|
+
export function uploadedImageGeometry(asset, imageSize, tilesetOverride) {
|
|
586
|
+
const baseTileset = designerTilesetMetadata(asset);
|
|
587
|
+
if (baseTileset) {
|
|
588
|
+
const tileset = {
|
|
589
|
+
...baseTileset,
|
|
590
|
+
...tilesetOverride
|
|
591
|
+
};
|
|
592
|
+
const capacity = tileset.columns * tileset.rows;
|
|
593
|
+
const tileCount = tileset.tileCount ?? capacity;
|
|
594
|
+
if (tileCount > capacity) {
|
|
595
|
+
throw new Error(`Tile count must not exceed this tileset's ${capacity}-tile capacity.`);
|
|
596
|
+
}
|
|
597
|
+
const margin = tileset.margin ?? 0;
|
|
598
|
+
const spacing = tileset.spacing ?? 0;
|
|
599
|
+
const dimensions = {
|
|
600
|
+
width: margin * 2 + tileset.columns * tileset.tileWidth +
|
|
601
|
+
Math.max(0, tileset.columns - 1) * spacing,
|
|
602
|
+
height: margin * 2 + tileset.rows * tileset.tileHeight +
|
|
603
|
+
Math.max(0, tileset.rows - 1) * spacing
|
|
604
|
+
};
|
|
605
|
+
if (imageSize.width !== dimensions.width || imageSize.height !== dimensions.height) {
|
|
606
|
+
throw new Error(`Tileset upload must be exactly ${dimensions.width}x${dimensions.height}px for the selected tile geometry; received ${imageSize.width}x${imageSize.height}px.`);
|
|
607
|
+
}
|
|
608
|
+
return { dimensions, tileset };
|
|
609
|
+
}
|
|
490
610
|
if (!asset.frameGrid) {
|
|
491
611
|
return {
|
|
492
612
|
dimensions: imageSize
|
|
@@ -588,6 +708,7 @@ const frameTouchUpPreferences = {
|
|
|
588
708
|
color: "#f8fafc",
|
|
589
709
|
tool: "brush"
|
|
590
710
|
};
|
|
711
|
+
let frameTouchUpClipboard;
|
|
591
712
|
export async function openFrameTouchUpEditor(options) {
|
|
592
713
|
const sourceImage = await loadImageElement(options.frameSrc);
|
|
593
714
|
const dialog = document.createElement("div");
|
|
@@ -646,6 +767,7 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
646
767
|
const eraserButton = touchUpButton("Eraser");
|
|
647
768
|
const pickerButton = touchUpButton("Picker");
|
|
648
769
|
const selectButton = touchUpButton("Select");
|
|
770
|
+
selectButton.title = "Select pixels. Cmd/Ctrl+C copies; Cmd/Ctrl+V pastes.";
|
|
649
771
|
const fillButton = touchUpButton("Fill");
|
|
650
772
|
const undoButton = touchUpButton("Undo");
|
|
651
773
|
const redoButton = touchUpButton("Redo");
|
|
@@ -679,6 +801,13 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
679
801
|
const selectionBox = document.createElement("div");
|
|
680
802
|
selectionBox.className = "ai-game-assets-designer__touchup-selection";
|
|
681
803
|
selectionBox.hidden = true;
|
|
804
|
+
const rotateSelectionButton = document.createElement("button");
|
|
805
|
+
rotateSelectionButton.type = "button";
|
|
806
|
+
rotateSelectionButton.className = "ai-game-assets-designer__touchup-selection-rotate";
|
|
807
|
+
rotateSelectionButton.textContent = "↻";
|
|
808
|
+
rotateSelectionButton.setAttribute("aria-label", "Rotate selection 90 degrees clockwise");
|
|
809
|
+
rotateSelectionButton.title = "Rotate selection 90° clockwise";
|
|
810
|
+
selectionBox.append(rotateSelectionButton);
|
|
682
811
|
canvasWrap.append(canvas, selectionBox);
|
|
683
812
|
const side = document.createElement("div");
|
|
684
813
|
side.className = "ai-game-assets-designer__touchup-side";
|
|
@@ -718,6 +847,7 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
718
847
|
let drawingPointerId;
|
|
719
848
|
let selectionStart;
|
|
720
849
|
let selection;
|
|
850
|
+
let floatingSelection;
|
|
721
851
|
let animationTimeout;
|
|
722
852
|
let animationCursor = 0;
|
|
723
853
|
let disposed = false;
|
|
@@ -775,6 +905,63 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
775
905
|
redoButton.disabled = redoStack.length === 0;
|
|
776
906
|
};
|
|
777
907
|
const snapshot = () => context.getImageData(0, 0, canvas.width, canvas.height);
|
|
908
|
+
const clipboardImageData = () => {
|
|
909
|
+
if (!frameTouchUpClipboard)
|
|
910
|
+
return undefined;
|
|
911
|
+
const width = Math.min(frameTouchUpClipboard.width, canvas.width);
|
|
912
|
+
const height = Math.min(frameTouchUpClipboard.height, canvas.height);
|
|
913
|
+
const imageData = context.createImageData(width, height);
|
|
914
|
+
const sourceStride = frameTouchUpClipboard.width * 4;
|
|
915
|
+
const targetStride = width * 4;
|
|
916
|
+
for (let row = 0; row < height; row += 1) {
|
|
917
|
+
imageData.data.set(frameTouchUpClipboard.pixels.subarray(row * sourceStride, (row * sourceStride) + targetStride), row * targetStride);
|
|
918
|
+
}
|
|
919
|
+
return imageData;
|
|
920
|
+
};
|
|
921
|
+
const drawFloatingSelection = () => {
|
|
922
|
+
if (!selection || !floatingSelection)
|
|
923
|
+
return;
|
|
924
|
+
context.putImageData(floatingSelection.background, 0, 0);
|
|
925
|
+
const floatingCanvas = document.createElement("canvas");
|
|
926
|
+
floatingCanvas.width = floatingSelection.pixels.width;
|
|
927
|
+
floatingCanvas.height = floatingSelection.pixels.height;
|
|
928
|
+
const floatingContext = floatingCanvas.getContext("2d");
|
|
929
|
+
if (!floatingContext)
|
|
930
|
+
return;
|
|
931
|
+
floatingContext.putImageData(floatingSelection.pixels, 0, 0);
|
|
932
|
+
context.drawImage(floatingCanvas, selection.x, selection.y);
|
|
933
|
+
};
|
|
934
|
+
const rotateImageDataWithinBounds = (imageData) => {
|
|
935
|
+
const sourceCanvas = document.createElement("canvas");
|
|
936
|
+
sourceCanvas.width = imageData.width;
|
|
937
|
+
sourceCanvas.height = imageData.height;
|
|
938
|
+
const sourceContext = sourceCanvas.getContext("2d");
|
|
939
|
+
const targetCanvas = document.createElement("canvas");
|
|
940
|
+
targetCanvas.width = imageData.width;
|
|
941
|
+
targetCanvas.height = imageData.height;
|
|
942
|
+
const targetContext = targetCanvas.getContext("2d", { willReadFrequently: true });
|
|
943
|
+
if (!sourceContext || !targetContext)
|
|
944
|
+
return imageData;
|
|
945
|
+
sourceContext.putImageData(imageData, 0, 0);
|
|
946
|
+
targetContext.imageSmoothingEnabled = false;
|
|
947
|
+
targetContext.translate(imageData.width / 2, imageData.height / 2);
|
|
948
|
+
targetContext.scale(imageData.width / imageData.height, imageData.height / imageData.width);
|
|
949
|
+
targetContext.rotate(Math.PI / 2);
|
|
950
|
+
targetContext.drawImage(sourceCanvas, -imageData.width / 2, -imageData.height / 2);
|
|
951
|
+
targetContext.setTransform(1, 0, 0, 1, 0, 0);
|
|
952
|
+
return targetContext.getImageData(0, 0, imageData.width, imageData.height);
|
|
953
|
+
};
|
|
954
|
+
const copySelection = () => {
|
|
955
|
+
if (!selection)
|
|
956
|
+
return false;
|
|
957
|
+
const imageData = floatingSelection?.pixels ?? context.getImageData(selection.x, selection.y, selection.width, selection.height);
|
|
958
|
+
frameTouchUpClipboard = {
|
|
959
|
+
width: imageData.width,
|
|
960
|
+
height: imageData.height,
|
|
961
|
+
pixels: new Uint8ClampedArray(imageData.data)
|
|
962
|
+
};
|
|
963
|
+
return true;
|
|
964
|
+
};
|
|
778
965
|
const pushUndo = () => {
|
|
779
966
|
undoStack.push(snapshot());
|
|
780
967
|
if (undoStack.length > 60)
|
|
@@ -785,6 +972,7 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
785
972
|
const restore = (imageData) => {
|
|
786
973
|
context.putImageData(imageData, 0, 0);
|
|
787
974
|
selection = undefined;
|
|
975
|
+
floatingSelection = undefined;
|
|
788
976
|
redrawEditor();
|
|
789
977
|
setDirty(true);
|
|
790
978
|
};
|
|
@@ -814,6 +1002,12 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
814
1002
|
selectionBox.style.top = `${selection.y * zoom}px`;
|
|
815
1003
|
selectionBox.style.width = `${selection.width * zoom}px`;
|
|
816
1004
|
selectionBox.style.height = `${selection.height * zoom}px`;
|
|
1005
|
+
const renderedWidth = selection.width * zoom;
|
|
1006
|
+
const renderedHeight = selection.height * zoom;
|
|
1007
|
+
rotateSelectionButton.hidden = renderedWidth < 12 || renderedHeight < 12;
|
|
1008
|
+
const handleSize = Math.max(0, Math.min(18, renderedWidth - 4, renderedHeight - 4));
|
|
1009
|
+
rotateSelectionButton.style.width = `${handleSize}px`;
|
|
1010
|
+
rotateSelectionButton.style.height = `${handleSize}px`;
|
|
817
1011
|
};
|
|
818
1012
|
const refreshPreviews = () => {
|
|
819
1013
|
stillContext.clearRect(0, 0, stillPreview.width, stillPreview.height);
|
|
@@ -997,6 +1191,13 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
997
1191
|
pushUndo();
|
|
998
1192
|
const nextX = clamp(selection.x + deltaX, 0, canvas.width - selection.width);
|
|
999
1193
|
const nextY = clamp(selection.y + deltaY, 0, canvas.height - selection.height);
|
|
1194
|
+
if (floatingSelection) {
|
|
1195
|
+
selection = { ...selection, x: nextX, y: nextY };
|
|
1196
|
+
drawFloatingSelection();
|
|
1197
|
+
setDirty(true);
|
|
1198
|
+
redrawEditor();
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1000
1201
|
const imageData = context.getImageData(selection.x, selection.y, selection.width, selection.height);
|
|
1001
1202
|
context.clearRect(selection.x, selection.y, selection.width, selection.height);
|
|
1002
1203
|
context.putImageData(imageData, nextX, nextY);
|
|
@@ -1004,15 +1205,60 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
1004
1205
|
setDirty(true);
|
|
1005
1206
|
redrawEditor();
|
|
1006
1207
|
};
|
|
1208
|
+
const rotateSelection = () => {
|
|
1209
|
+
if (!selection)
|
|
1210
|
+
return;
|
|
1211
|
+
pushUndo();
|
|
1212
|
+
const pixels = floatingSelection?.pixels ?? context.getImageData(selection.x, selection.y, selection.width, selection.height);
|
|
1213
|
+
const rotated = rotateImageDataWithinBounds(pixels);
|
|
1214
|
+
if (floatingSelection) {
|
|
1215
|
+
floatingSelection.pixels = rotated;
|
|
1216
|
+
drawFloatingSelection();
|
|
1217
|
+
}
|
|
1218
|
+
else {
|
|
1219
|
+
context.putImageData(rotated, selection.x, selection.y);
|
|
1220
|
+
}
|
|
1221
|
+
setDirty(true);
|
|
1222
|
+
redrawEditor();
|
|
1223
|
+
};
|
|
1007
1224
|
const deleteSelection = () => {
|
|
1008
1225
|
if (!selection)
|
|
1009
1226
|
return;
|
|
1010
1227
|
pushUndo();
|
|
1011
|
-
|
|
1228
|
+
if (floatingSelection) {
|
|
1229
|
+
context.putImageData(floatingSelection.background, 0, 0);
|
|
1230
|
+
floatingSelection = undefined;
|
|
1231
|
+
}
|
|
1232
|
+
else {
|
|
1233
|
+
context.clearRect(selection.x, selection.y, selection.width, selection.height);
|
|
1234
|
+
}
|
|
1012
1235
|
selection = undefined;
|
|
1013
1236
|
setDirty(true);
|
|
1014
1237
|
redrawEditor();
|
|
1015
1238
|
};
|
|
1239
|
+
const pasteSelection = () => {
|
|
1240
|
+
const pixels = clipboardImageData();
|
|
1241
|
+
if (!pixels)
|
|
1242
|
+
return false;
|
|
1243
|
+
const x = clamp(selection?.x ?? Math.floor((canvas.width - pixels.width) / 2), 0, canvas.width - pixels.width);
|
|
1244
|
+
const y = clamp(selection?.y ?? Math.floor((canvas.height - pixels.height) / 2), 0, canvas.height - pixels.height);
|
|
1245
|
+
pushUndo();
|
|
1246
|
+
floatingSelection = {
|
|
1247
|
+
background: snapshot(),
|
|
1248
|
+
pixels
|
|
1249
|
+
};
|
|
1250
|
+
selection = {
|
|
1251
|
+
x,
|
|
1252
|
+
y,
|
|
1253
|
+
width: pixels.width,
|
|
1254
|
+
height: pixels.height
|
|
1255
|
+
};
|
|
1256
|
+
selectTool("select");
|
|
1257
|
+
drawFloatingSelection();
|
|
1258
|
+
setDirty(true);
|
|
1259
|
+
redrawEditor();
|
|
1260
|
+
return true;
|
|
1261
|
+
};
|
|
1016
1262
|
const drawAnimationFrame = () => {
|
|
1017
1263
|
if (disposed)
|
|
1018
1264
|
return;
|
|
@@ -1054,6 +1300,8 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
1054
1300
|
brushMenu.hidden = true;
|
|
1055
1301
|
};
|
|
1056
1302
|
const close = () => {
|
|
1303
|
+
if (disposed)
|
|
1304
|
+
return;
|
|
1057
1305
|
disposed = true;
|
|
1058
1306
|
if (animationTimeout !== undefined)
|
|
1059
1307
|
window.clearTimeout(animationTimeout);
|
|
@@ -1063,6 +1311,7 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
1063
1311
|
window.removeEventListener("blur", cancelDrawing);
|
|
1064
1312
|
document.removeEventListener("pointerdown", closeBrushMenuOnOutsidePointer);
|
|
1065
1313
|
dialog.remove();
|
|
1314
|
+
options.onClose?.();
|
|
1066
1315
|
};
|
|
1067
1316
|
const requestClose = () => {
|
|
1068
1317
|
if (dirty && !window.confirm("You have unsaved changes. Close without saving?"))
|
|
@@ -1074,6 +1323,23 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
1074
1323
|
return;
|
|
1075
1324
|
const key = event.key.toLowerCase();
|
|
1076
1325
|
const commandModifier = event.metaKey || event.ctrlKey;
|
|
1326
|
+
const target = event.target;
|
|
1327
|
+
const isEditingInput = target instanceof HTMLInputElement ||
|
|
1328
|
+
target instanceof HTMLTextAreaElement ||
|
|
1329
|
+
target instanceof HTMLSelectElement ||
|
|
1330
|
+
(target instanceof HTMLElement && target.isContentEditable);
|
|
1331
|
+
if (isEditingInput && event.key !== "Escape")
|
|
1332
|
+
return;
|
|
1333
|
+
if (commandModifier && !event.altKey && key === "c" && copySelection()) {
|
|
1334
|
+
event.preventDefault();
|
|
1335
|
+
event.stopPropagation();
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
if (commandModifier && !event.altKey && key === "v" && pasteSelection()) {
|
|
1339
|
+
event.preventDefault();
|
|
1340
|
+
event.stopPropagation();
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1077
1343
|
if (commandModifier && !event.altKey && (key === "z" || key === "y")) {
|
|
1078
1344
|
event.preventDefault();
|
|
1079
1345
|
event.stopPropagation();
|
|
@@ -1141,6 +1407,7 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
1141
1407
|
pickColorAt(point);
|
|
1142
1408
|
return;
|
|
1143
1409
|
}
|
|
1410
|
+
floatingSelection = undefined;
|
|
1144
1411
|
pushUndo();
|
|
1145
1412
|
drawing = true;
|
|
1146
1413
|
drawingPointerId = event.pointerId;
|
|
@@ -1246,6 +1513,15 @@ export async function openFrameTouchUpEditor(options) {
|
|
|
1246
1513
|
clientY: event.clientY
|
|
1247
1514
|
});
|
|
1248
1515
|
}, { passive: false });
|
|
1516
|
+
rotateSelectionButton.addEventListener("pointerdown", (event) => {
|
|
1517
|
+
event.preventDefault();
|
|
1518
|
+
event.stopPropagation();
|
|
1519
|
+
});
|
|
1520
|
+
rotateSelectionButton.addEventListener("click", (event) => {
|
|
1521
|
+
event.preventDefault();
|
|
1522
|
+
event.stopPropagation();
|
|
1523
|
+
rotateSelection();
|
|
1524
|
+
});
|
|
1249
1525
|
closeButton.addEventListener("click", requestClose);
|
|
1250
1526
|
zoomOutButton.addEventListener("click", () => {
|
|
1251
1527
|
setZoom(zoom - 1);
|
|
@@ -1747,9 +2023,12 @@ export async function openStyleGuideEditor(options) {
|
|
|
1747
2023
|
const promoteButton = document.createElement("button");
|
|
1748
2024
|
promoteButton.type = "button";
|
|
1749
2025
|
promoteButton.textContent = "Promote style";
|
|
2026
|
+
const regenerateAllButton = document.createElement("button");
|
|
2027
|
+
regenerateAllButton.type = "button";
|
|
2028
|
+
regenerateAllButton.textContent = "Regenerate all...";
|
|
1750
2029
|
const actions = document.createElement("div");
|
|
1751
2030
|
actions.className = "ai-game-assets-designer__modal-actions";
|
|
1752
|
-
actions.append(cancelButton, promoteButton, confirmButton);
|
|
2031
|
+
actions.append(cancelButton, promoteButton, regenerateAllButton, confirmButton);
|
|
1753
2032
|
const draft = () => ({
|
|
1754
2033
|
prompt: promptInput.value.trim(),
|
|
1755
2034
|
images: images.map((image) => ({ ...image }))
|
|
@@ -1775,6 +2054,12 @@ export async function openStyleGuideEditor(options) {
|
|
|
1775
2054
|
promoteButton.disabled = false;
|
|
1776
2055
|
}
|
|
1777
2056
|
});
|
|
2057
|
+
regenerateAllButton.addEventListener("click", () => {
|
|
2058
|
+
const current = draft();
|
|
2059
|
+
options.onConfirm(current);
|
|
2060
|
+
close();
|
|
2061
|
+
void options.onRegenerateAll(current);
|
|
2062
|
+
});
|
|
1778
2063
|
card.append(title, promptField, dropZone, uploadButton, fileInput, imageList, actions);
|
|
1779
2064
|
dialog.append(card);
|
|
1780
2065
|
options.root.append(dialog);
|
|
@@ -2492,10 +2777,21 @@ export function ensureFramePreviewElement(element) {
|
|
|
2492
2777
|
return frameElement;
|
|
2493
2778
|
}
|
|
2494
2779
|
export function generationOverridesFromInputs(elements, asset, format) {
|
|
2780
|
+
const tileset = designerTilesetMetadata(asset);
|
|
2495
2781
|
const dimensions = {
|
|
2496
|
-
width: positiveIntegerInput(elements.widthInput, asset.frameGrid?.frameWidth ?? asset.dimensions?.width ?? 1),
|
|
2497
|
-
height: positiveIntegerInput(elements.heightInput, asset.frameGrid?.frameHeight ?? asset.dimensions?.height ?? 1)
|
|
2782
|
+
width: positiveIntegerInput(elements.widthInput, asset.frameGrid?.frameWidth ?? tileset?.tileWidth ?? asset.dimensions?.width ?? 1),
|
|
2783
|
+
height: positiveIntegerInput(elements.heightInput, asset.frameGrid?.frameHeight ?? tileset?.tileHeight ?? asset.dimensions?.height ?? 1)
|
|
2498
2784
|
};
|
|
2785
|
+
if (tileset) {
|
|
2786
|
+
return {
|
|
2787
|
+
tileset: {
|
|
2788
|
+
tileWidth: dimensions.width,
|
|
2789
|
+
tileHeight: dimensions.height,
|
|
2790
|
+
tileCount: positiveIntegerInput(elements.frameCountInput, tileset.tileCount ?? tileset.columns * tileset.rows)
|
|
2791
|
+
},
|
|
2792
|
+
settings: { format }
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2499
2795
|
if (!asset.frameGrid) {
|
|
2500
2796
|
return {
|
|
2501
2797
|
dimensions,
|
|
@@ -2564,6 +2860,9 @@ export function isAudioAsset(asset) {
|
|
|
2564
2860
|
asset?.kind === "voice" ||
|
|
2565
2861
|
asset?.kind === "voice-line";
|
|
2566
2862
|
}
|
|
2863
|
+
function designerTilesetMetadata(asset) {
|
|
2864
|
+
return asset?.tileset;
|
|
2865
|
+
}
|
|
2567
2866
|
export function isVoiceAsset(asset) {
|
|
2568
2867
|
return asset?.kind === "voice" || asset?.kind === "voice-line";
|
|
2569
2868
|
}
|
|
@@ -2572,6 +2871,7 @@ export function assetWithGeneratedGeometry(asset, option, options = {}) {
|
|
|
2572
2871
|
...asset,
|
|
2573
2872
|
dimensions: option.dimensions ?? asset.dimensions,
|
|
2574
2873
|
frameGrid: option.frameGrid ?? asset.frameGrid,
|
|
2874
|
+
tileset: option.tileset ?? asset.tileset,
|
|
2575
2875
|
animations: option.animations ??
|
|
2576
2876
|
(options.inheritAnimations
|
|
2577
2877
|
? asset.animations
|
|
@@ -2593,7 +2893,11 @@ export function positiveIntegerInput(input, fallback) {
|
|
|
2593
2893
|
const value = Number(input.value);
|
|
2594
2894
|
if (!Number.isFinite(value))
|
|
2595
2895
|
return fallback;
|
|
2596
|
-
|
|
2896
|
+
const normalized = Math.max(1, Math.floor(value));
|
|
2897
|
+
const maximum = Number(input.max);
|
|
2898
|
+
return input.max && Number.isFinite(maximum)
|
|
2899
|
+
? Math.min(normalized, Math.max(1, Math.floor(maximum)))
|
|
2900
|
+
: normalized;
|
|
2597
2901
|
}
|
|
2598
2902
|
export function positiveNumberInput(input, fallback) {
|
|
2599
2903
|
const value = Number(input.value);
|
|
@@ -2724,7 +3028,7 @@ export async function imageSourceToDataUrl(src) {
|
|
|
2724
3028
|
return src;
|
|
2725
3029
|
const response = await fetch(src);
|
|
2726
3030
|
if (!response.ok) {
|
|
2727
|
-
throw new Error(`Could not load current
|
|
3031
|
+
throw new Error(`Could not load current asset image (${response.status}).`);
|
|
2728
3032
|
}
|
|
2729
3033
|
const responseBlob = await response.blob();
|
|
2730
3034
|
const blob = responseBlob.type === "application/octet-stream"
|
|
@@ -2737,11 +3041,11 @@ export async function imageSourceToDataUrl(src) {
|
|
|
2737
3041
|
resolve(reader.result);
|
|
2738
3042
|
}
|
|
2739
3043
|
else {
|
|
2740
|
-
reject(new Error("Could not convert current
|
|
3044
|
+
reject(new Error("Could not convert current asset image to a data URL."));
|
|
2741
3045
|
}
|
|
2742
3046
|
});
|
|
2743
3047
|
reader.addEventListener("error", () => {
|
|
2744
|
-
reject(reader.error ?? new Error("Could not read current
|
|
3048
|
+
reject(reader.error ?? new Error("Could not read current asset image."));
|
|
2745
3049
|
});
|
|
2746
3050
|
reader.readAsDataURL(blob);
|
|
2747
3051
|
});
|
|
@@ -2947,6 +3251,36 @@ export function ensureDesignerStyles() {
|
|
|
2947
3251
|
font: inherit;
|
|
2948
3252
|
}
|
|
2949
3253
|
.ai-game-assets-designer__field textarea { resize: vertical; }
|
|
3254
|
+
.ai-game-assets-designer__tile-prompts {
|
|
3255
|
+
display: grid;
|
|
3256
|
+
gap: 7px;
|
|
3257
|
+
margin-bottom: 12px;
|
|
3258
|
+
padding: 10px;
|
|
3259
|
+
border: 1px solid #303949;
|
|
3260
|
+
border-radius: 8px;
|
|
3261
|
+
background: rgba(11, 15, 22, 0.58);
|
|
3262
|
+
}
|
|
3263
|
+
.ai-game-assets-designer__tile-prompts[hidden] { display: none; }
|
|
3264
|
+
.ai-game-assets-designer__tile-prompts-title {
|
|
3265
|
+
color: #e5edf9;
|
|
3266
|
+
font-size: 13px;
|
|
3267
|
+
font-weight: 700;
|
|
3268
|
+
}
|
|
3269
|
+
.ai-game-assets-designer__tile-prompts-hint {
|
|
3270
|
+
color: #8f9cb5;
|
|
3271
|
+
font-size: 11px;
|
|
3272
|
+
line-height: 1.35;
|
|
3273
|
+
}
|
|
3274
|
+
.ai-game-assets-designer__tile-prompts-list {
|
|
3275
|
+
display: grid;
|
|
3276
|
+
gap: 8px;
|
|
3277
|
+
}
|
|
3278
|
+
.ai-game-assets-designer__tile-prompts-list .ai-game-assets-designer__field {
|
|
3279
|
+
margin-bottom: 0;
|
|
3280
|
+
}
|
|
3281
|
+
.ai-game-assets-designer__tile-prompts-list textarea {
|
|
3282
|
+
min-height: 62px;
|
|
3283
|
+
}
|
|
2950
3284
|
.ai-game-assets-designer__target-variant {
|
|
2951
3285
|
margin: -4px 0 12px;
|
|
2952
3286
|
padding: 7px 9px;
|
|
@@ -3171,6 +3505,17 @@ export function ensureDesignerStyles() {
|
|
|
3171
3505
|
gap: 8px;
|
|
3172
3506
|
margin-top: 10px;
|
|
3173
3507
|
}
|
|
3508
|
+
.ai-game-assets-designer__tileset-tile-prompt {
|
|
3509
|
+
margin-top: 10px;
|
|
3510
|
+
padding: 9px 10px;
|
|
3511
|
+
border: 1px solid #354058;
|
|
3512
|
+
border-radius: 6px;
|
|
3513
|
+
background: rgba(37, 49, 73, 0.48);
|
|
3514
|
+
color: #d8e4f7;
|
|
3515
|
+
font-size: 12px;
|
|
3516
|
+
line-height: 1.45;
|
|
3517
|
+
white-space: pre-wrap;
|
|
3518
|
+
}
|
|
3174
3519
|
.ai-game-assets-designer__options.is-audio {
|
|
3175
3520
|
grid-template-columns: 1fr;
|
|
3176
3521
|
}
|
|
@@ -3245,6 +3590,159 @@ export function ensureDesignerStyles() {
|
|
|
3245
3590
|
box-shadow: 0 22px 70px rgba(0, 0, 0, 0.55);
|
|
3246
3591
|
padding: 14px;
|
|
3247
3592
|
}
|
|
3593
|
+
.ai-game-assets-designer__tileset-mixer-card {
|
|
3594
|
+
width: min(980px, calc(100vw - 36px));
|
|
3595
|
+
}
|
|
3596
|
+
.ai-game-assets-designer__tileset-editor-card {
|
|
3597
|
+
width: min(720px, calc(100vw - 36px));
|
|
3598
|
+
}
|
|
3599
|
+
.ai-game-assets-designer__tileset-editor-stage {
|
|
3600
|
+
border: 1px solid #303a49;
|
|
3601
|
+
border-radius: 8px;
|
|
3602
|
+
}
|
|
3603
|
+
.ai-game-assets-designer__tileset-editor-strip {
|
|
3604
|
+
margin-top: 8px;
|
|
3605
|
+
}
|
|
3606
|
+
.ai-game-assets-designer__tileset-transform-fields {
|
|
3607
|
+
margin-bottom: 6px;
|
|
3608
|
+
}
|
|
3609
|
+
.ai-game-assets-designer__tileset-transform-hint {
|
|
3610
|
+
margin: 0 0 10px;
|
|
3611
|
+
color: #aeb8c8;
|
|
3612
|
+
font-size: 12px;
|
|
3613
|
+
line-height: 1.4;
|
|
3614
|
+
}
|
|
3615
|
+
.ai-game-assets-designer__tileset-transform-thumb {
|
|
3616
|
+
width: 48px;
|
|
3617
|
+
height: 48px;
|
|
3618
|
+
display: grid;
|
|
3619
|
+
place-items: center;
|
|
3620
|
+
}
|
|
3621
|
+
.ai-game-assets-designer__tileset-transform-thumb-viewport {
|
|
3622
|
+
position: relative;
|
|
3623
|
+
display: block;
|
|
3624
|
+
overflow: hidden;
|
|
3625
|
+
border-radius: 3px;
|
|
3626
|
+
pointer-events: none;
|
|
3627
|
+
}
|
|
3628
|
+
@media (max-width: 520px) {
|
|
3629
|
+
.ai-game-assets-designer__tileset-transform-fields {
|
|
3630
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
.ai-game-assets-designer__tileset-regenerate-card {
|
|
3634
|
+
width: min(620px, calc(100vw - 36px));
|
|
3635
|
+
}
|
|
3636
|
+
.ai-game-assets-designer__tileset-regenerate-card .ai-game-assets-designer__tileset-tile-prompt {
|
|
3637
|
+
margin: 0 0 12px;
|
|
3638
|
+
}
|
|
3639
|
+
.ai-game-assets-designer__tileset-mixer-hint {
|
|
3640
|
+
margin: -5px 0 12px;
|
|
3641
|
+
color: #aeb8c8;
|
|
3642
|
+
font-size: 12px;
|
|
3643
|
+
line-height: 1.4;
|
|
3644
|
+
}
|
|
3645
|
+
.ai-game-assets-designer__tileset-mixer-body {
|
|
3646
|
+
display: grid;
|
|
3647
|
+
grid-template-columns: minmax(160px, 220px) minmax(0, 1fr);
|
|
3648
|
+
gap: 14px;
|
|
3649
|
+
min-height: 300px;
|
|
3650
|
+
}
|
|
3651
|
+
.ai-game-assets-designer__tileset-navigator-panel,
|
|
3652
|
+
.ai-game-assets-designer__tileset-preview-panel {
|
|
3653
|
+
min-width: 0;
|
|
3654
|
+
border: 1px solid #303a49;
|
|
3655
|
+
border-radius: 8px;
|
|
3656
|
+
background: #0f1218;
|
|
3657
|
+
padding: 10px;
|
|
3658
|
+
}
|
|
3659
|
+
.ai-game-assets-designer__tileset-section-title {
|
|
3660
|
+
min-height: 20px;
|
|
3661
|
+
margin-bottom: 8px;
|
|
3662
|
+
color: #dbeafe;
|
|
3663
|
+
font-size: 12px;
|
|
3664
|
+
font-weight: 700;
|
|
3665
|
+
}
|
|
3666
|
+
.ai-game-assets-designer__tileset-navigator {
|
|
3667
|
+
display: grid;
|
|
3668
|
+
grid-template-columns: repeat(auto-fill, minmax(56px, 1fr));
|
|
3669
|
+
gap: 7px;
|
|
3670
|
+
max-height: min(58vh, 540px);
|
|
3671
|
+
overflow: auto;
|
|
3672
|
+
padding: 2px;
|
|
3673
|
+
}
|
|
3674
|
+
.ai-game-assets-designer__tileset-tile {
|
|
3675
|
+
position: relative;
|
|
3676
|
+
overflow: hidden;
|
|
3677
|
+
border: 2px solid #384251;
|
|
3678
|
+
border-radius: 6px;
|
|
3679
|
+
background: #111827;
|
|
3680
|
+
cursor: pointer;
|
|
3681
|
+
}
|
|
3682
|
+
.ai-game-assets-designer__tileset-tile::after {
|
|
3683
|
+
content: attr(data-source);
|
|
3684
|
+
position: absolute;
|
|
3685
|
+
right: 2px;
|
|
3686
|
+
bottom: 2px;
|
|
3687
|
+
z-index: 3;
|
|
3688
|
+
border-radius: 999px;
|
|
3689
|
+
background: rgba(8, 12, 20, 0.88);
|
|
3690
|
+
color: #9ca8ba;
|
|
3691
|
+
padding: 1px 4px;
|
|
3692
|
+
font-size: 9px;
|
|
3693
|
+
line-height: 1.4;
|
|
3694
|
+
}
|
|
3695
|
+
.ai-game-assets-designer__tileset-tile.is-selected {
|
|
3696
|
+
border-color: #6ed3ff;
|
|
3697
|
+
box-shadow: 0 0 0 1px rgba(110, 211, 255, 0.35);
|
|
3698
|
+
}
|
|
3699
|
+
.ai-game-assets-designer__tileset-tile.is-mixed::after {
|
|
3700
|
+
background: #155e75;
|
|
3701
|
+
color: #ecfeff;
|
|
3702
|
+
}
|
|
3703
|
+
.ai-game-assets-designer__tileset-choices {
|
|
3704
|
+
display: grid;
|
|
3705
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
3706
|
+
gap: 10px;
|
|
3707
|
+
align-items: start;
|
|
3708
|
+
width: 100%;
|
|
3709
|
+
min-width: 0;
|
|
3710
|
+
}
|
|
3711
|
+
.ai-game-assets-designer__tileset-choice {
|
|
3712
|
+
display: grid;
|
|
3713
|
+
justify-items: center;
|
|
3714
|
+
gap: 7px;
|
|
3715
|
+
min-width: 0;
|
|
3716
|
+
max-width: 100%;
|
|
3717
|
+
overflow: hidden;
|
|
3718
|
+
border: 2px solid #384251;
|
|
3719
|
+
border-radius: 8px;
|
|
3720
|
+
background: #151b25;
|
|
3721
|
+
color: #dbeafe;
|
|
3722
|
+
padding: 9px;
|
|
3723
|
+
font: inherit;
|
|
3724
|
+
font-size: 12px;
|
|
3725
|
+
cursor: pointer;
|
|
3726
|
+
}
|
|
3727
|
+
.ai-game-assets-designer__tileset-choice.is-selected {
|
|
3728
|
+
border-color: #6ed3ff;
|
|
3729
|
+
background: #16283a;
|
|
3730
|
+
}
|
|
3731
|
+
.ai-game-assets-designer__tileset-choice-preview {
|
|
3732
|
+
position: relative;
|
|
3733
|
+
overflow: hidden;
|
|
3734
|
+
max-width: 100%;
|
|
3735
|
+
border-radius: 6px;
|
|
3736
|
+
background: #0a0d12;
|
|
3737
|
+
}
|
|
3738
|
+
@media (max-width: 760px) {
|
|
3739
|
+
.ai-game-assets-designer__tileset-mixer-body {
|
|
3740
|
+
grid-template-columns: 1fr;
|
|
3741
|
+
}
|
|
3742
|
+
.ai-game-assets-designer__tileset-navigator {
|
|
3743
|
+
max-height: 180px;
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3248
3746
|
.ai-game-assets-designer__modal-title {
|
|
3249
3747
|
margin-bottom: 12px;
|
|
3250
3748
|
font-weight: 700;
|
|
@@ -3268,6 +3766,9 @@ export function ensureDesignerStyles() {
|
|
|
3268
3766
|
.ai-game-assets-designer__modal-feedback[data-kind="error"] {
|
|
3269
3767
|
color: #ffb4b4;
|
|
3270
3768
|
}
|
|
3769
|
+
.ai-game-assets-designer__modal-feedback[data-kind="success"] {
|
|
3770
|
+
color: #86efac;
|
|
3771
|
+
}
|
|
3271
3772
|
.ai-game-assets-designer__version-list {
|
|
3272
3773
|
display: grid;
|
|
3273
3774
|
gap: 10px;
|
|
@@ -3381,6 +3882,9 @@ export function ensureDesignerStyles() {
|
|
|
3381
3882
|
.ai-game-assets-designer__frame-thumb.is-selected::after {
|
|
3382
3883
|
border-color: #6ed3ff;
|
|
3383
3884
|
}
|
|
3885
|
+
.ai-game-assets-designer__frame-thumb.is-previewing::after {
|
|
3886
|
+
box-shadow: inset 0 -3px 0 #facc15;
|
|
3887
|
+
}
|
|
3384
3888
|
.ai-game-assets-designer__frame-fields {
|
|
3385
3889
|
display: grid;
|
|
3386
3890
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
@@ -3530,7 +4034,7 @@ export function ensureDesignerStyles() {
|
|
|
3530
4034
|
.ai-game-assets-designer__touchup {
|
|
3531
4035
|
position: fixed;
|
|
3532
4036
|
inset: 0;
|
|
3533
|
-
z-index:
|
|
4037
|
+
z-index: 2147483647;
|
|
3534
4038
|
display: grid;
|
|
3535
4039
|
grid-template-rows: auto auto minmax(0, 1fr);
|
|
3536
4040
|
gap: 10px;
|
|
@@ -3716,6 +4220,30 @@ export function ensureDesignerStyles() {
|
|
|
3716
4220
|
box-shadow: 0 0 0 1px rgba(15, 23, 42, 0.9);
|
|
3717
4221
|
transform: translate(24px, 24px);
|
|
3718
4222
|
}
|
|
4223
|
+
.ai-game-assets-designer__touchup-selection-rotate {
|
|
4224
|
+
position: absolute;
|
|
4225
|
+
top: 2px;
|
|
4226
|
+
right: 2px;
|
|
4227
|
+
z-index: 2;
|
|
4228
|
+
display: grid;
|
|
4229
|
+
place-items: center;
|
|
4230
|
+
min-width: 0;
|
|
4231
|
+
min-height: 0;
|
|
4232
|
+
border: 1px solid #dbeafe;
|
|
4233
|
+
border-radius: 999px;
|
|
4234
|
+
background: rgba(15, 23, 42, 0.94);
|
|
4235
|
+
color: #f8fafc;
|
|
4236
|
+
padding: 0;
|
|
4237
|
+
font: 700 11px/1 ui-sans-serif, system-ui, sans-serif;
|
|
4238
|
+
cursor: pointer;
|
|
4239
|
+
pointer-events: auto;
|
|
4240
|
+
}
|
|
4241
|
+
.ai-game-assets-designer__touchup-selection-rotate:hover,
|
|
4242
|
+
.ai-game-assets-designer__touchup-selection-rotate:focus-visible {
|
|
4243
|
+
border-color: #6ed3ff;
|
|
4244
|
+
color: #6ed3ff;
|
|
4245
|
+
outline: none;
|
|
4246
|
+
}
|
|
3719
4247
|
.ai-game-assets-designer__touchup-side {
|
|
3720
4248
|
min-height: 0;
|
|
3721
4249
|
display: grid;
|