vetak8 commited on
Commit
3ddfcf2
·
verified ·
1 Parent(s): 60d6d0f

Upload 161 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +3 -0
  2. custom_nodes/ComfyUI_INSTARAW/__init__.py +40 -0
  3. custom_nodes/ComfyUI_INSTARAW/fonts/BricolageGrotesque.ttf +3 -0
  4. custom_nodes/ComfyUI_INSTARAW/js/Instara_loader.mp4 +3 -0
  5. custom_nodes/ComfyUI_INSTARAW/js/advanced_image_loader.css +760 -0
  6. custom_nodes/ComfyUI_INSTARAW/js/advanced_image_loader.js +0 -0
  7. custom_nodes/ComfyUI_INSTARAW/js/api_model_selector.js +45 -0
  8. custom_nodes/ComfyUI_INSTARAW/js/api_nodes.js +53 -0
  9. custom_nodes/ComfyUI_INSTARAW/js/batch_image_generator.js +0 -0
  10. custom_nodes/ComfyUI_INSTARAW/js/boolean_bypass.js +90 -0
  11. custom_nodes/ComfyUI_INSTARAW/js/branding_node.js +91 -0
  12. custom_nodes/ComfyUI_INSTARAW/js/ding.mp3 +0 -0
  13. custom_nodes/ComfyUI_INSTARAW/js/filter.css +106 -0
  14. custom_nodes/ComfyUI_INSTARAW/js/floating_window.css +94 -0
  15. custom_nodes/ComfyUI_INSTARAW/js/floating_window.js +63 -0
  16. custom_nodes/ComfyUI_INSTARAW/js/group_bypass_detector.js +42 -0
  17. custom_nodes/ComfyUI_INSTARAW/js/image_filter.js +123 -0
  18. custom_nodes/ComfyUI_INSTARAW/js/instaraw.svg +20 -0
  19. custom_nodes/ComfyUI_INSTARAW/js/log.js +25 -0
  20. custom_nodes/ComfyUI_INSTARAW/js/mask_utils.js +79 -0
  21. custom_nodes/ComfyUI_INSTARAW/js/nano_banana_aspect_ratio.js +130 -0
  22. custom_nodes/ComfyUI_INSTARAW/js/popup.js +1238 -0
  23. custom_nodes/ComfyUI_INSTARAW/js/reality_prompt_generator.css +2841 -0
  24. custom_nodes/ComfyUI_INSTARAW/js/reality_prompt_generator.js +0 -0
  25. custom_nodes/ComfyUI_INSTARAW/js/shared/combo-cards.js +64 -0
  26. custom_nodes/ComfyUI_INSTARAW/js/utils.js +9 -0
  27. custom_nodes/ComfyUI_INSTARAW/js/zoomed.css +61 -0
  28. custom_nodes/ComfyUI_INSTARAW/modules/__init__.py +0 -0
  29. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_12.icc +0 -0
  30. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_12.json +0 -0
  31. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_12.npz +3 -0
  32. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_13_pro.icc +0 -0
  33. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_13_pro.json +1095 -0
  34. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_13_pro.npz +3 -0
  35. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_plus.icc +0 -0
  36. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_plus.json +0 -0
  37. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_plus.npz +3 -0
  38. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_pro_max.icc +0 -0
  39. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_pro_max.json +0 -0
  40. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_pro_max.npz +3 -0
  41. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_15_pro_max.icc +0 -0
  42. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_15_pro_max.json +0 -0
  43. custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_15_pro_max.npz +3 -0
  44. custom_nodes/ComfyUI_INSTARAW/modules/color_profiles/sRGB_IEC61966-2-1_no_black_scaling.icc +0 -0
  45. custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/__init__.py +0 -0
  46. custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/DJI_O3AirUnit_CineonFilmLog_minimal_natural_sunrise_snow_forest_glow_1.DJI_0064_stabilized_4.cube +0 -0
  47. custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/KORNEV_LUT_HLG_3.cube +0 -0
  48. custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/LOOK1.cube +0 -0
  49. custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/LOW_SAT_-_Braw_Vivid_Ludeman_LUT.cube +0 -0
  50. custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/Shibuya_01.cube +0 -0
.gitattributes CHANGED
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ custom_nodes/ComfyUI_INSTARAW/fonts/BricolageGrotesque.ttf filter=lfs diff=lfs merge=lfs -text
37
+ custom_nodes/ComfyUI_INSTARAW/js/Instara_loader.mp4 filter=lfs diff=lfs merge=lfs -text
38
+ custom_nodes/ComfyUI_INSTARAW/nodes/utility_nodes/_refs/iphone13.jpg filter=lfs diff=lfs merge=lfs -text
custom_nodes/ComfyUI_INSTARAW/__init__.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ComfyUI INSTARAW
3
+ A general-purpose custom nodes package by Instara
4
+
5
+ INSTARAW is a collection of powerful custom nodes for ComfyUI that brings together
6
+ hard-to-install dependencies and integrations in one convenient package.
7
+
8
+ Features:
9
+ - API Integrations: SeeDream, Ideogram, and more
10
+ - Easy Installation: Pre-packaged dependencies
11
+ - Modular Design: Easy to extend and customize
12
+ - INSTARAW Brand: Where we push the boundaries
13
+
14
+ Created by Instara
15
+ """
16
+
17
+ import os
18
+ import nodes
19
+
20
+ # This line is critical for loading the JavaScript and CSS files.
21
+ # It tells ComfyUI where to find the web assets for this extension.
22
+ if "ComfyUI_INSTARAW" not in nodes.EXTENSION_WEB_DIRS:
23
+ nodes.EXTENSION_WEB_DIRS["ComfyUI_INSTARAW"] = os.path.join(os.path.dirname(os.path.realpath(__file__)), "js")
24
+
25
+ from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
26
+
27
+ # Import creative API to register endpoints (after nodes are loaded)
28
+ try:
29
+ from .nodes.api_nodes import creative_api
30
+ except Exception as e:
31
+ print(f"[INSTARAW] Warning: Could not load creative_api: {e}")
32
+ print("[INSTARAW] Creative/Character generation features will not be available.")
33
+
34
+ # Required exports for ComfyUI
35
+ __all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
36
+
37
+ # Package metadata
38
+ __version__ = "1.2.0" # Version bumped to reflect the major addition
39
+ __author__ = "Instara"
40
+ __description__ = "INSTARAW - General purpose custom nodes for ComfyUI"
custom_nodes/ComfyUI_INSTARAW/fonts/BricolageGrotesque.ttf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:31b91d15aae398699fae58363dbc8ca1167faffe7d2cd62e68c716dcaa7d5fdd
3
+ size 407844
custom_nodes/ComfyUI_INSTARAW/js/Instara_loader.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:81c28caca82f4fe5308da8248315c12aeea55f93caf44a90e5c2a2b86a3ae516
3
+ size 459430
custom_nodes/ComfyUI_INSTARAW/js/advanced_image_loader.css ADDED
@@ -0,0 +1,760 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ---
2
+ // Filename: ../ComfyUI_INSTARAW/js/advanced_image_loader.css
3
+ // --- */
4
+
5
+ .instaraw-adv-loader-container {
6
+ background: #0d0f12;
7
+ padding: 16px;
8
+ border-radius: 4px;
9
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
10
+ }
11
+
12
+ /* Brand Row */
13
+ .instaraw-adv-loader-brand-row {
14
+ position: relative;
15
+ display: inline-flex;
16
+ align-items: center;
17
+ margin-bottom: 12px;
18
+ }
19
+
20
+ .instaraw-adv-loader-logo {
21
+ width: 140px;
22
+ height: auto;
23
+ opacity: 0.85;
24
+ }
25
+
26
+ .instaraw-adv-loader-version {
27
+ position: absolute;
28
+ right: 0;
29
+ top: 50%;
30
+ transform: translate(122%, -50%);
31
+ font-family: monospace;
32
+ font-size: 14px;
33
+ color: rgba(255, 255, 255, 0.5);
34
+ white-space: nowrap;
35
+ }
36
+
37
+ /* Top Bar - Mode + Stats */
38
+ .instaraw-adv-loader-topbar {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: space-between;
42
+ gap: 12px;
43
+ margin-bottom: 12px;
44
+ padding: 10px 12px;
45
+ background: rgba(255, 255, 255, 0.04);
46
+ border-radius: 4px;
47
+ border: 1px solid rgba(255, 255, 255, 0.08);
48
+ }
49
+ .instaraw-adv-loader-topbar-left {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: 10px;
53
+ }
54
+ .instaraw-adv-loader-topbar-right {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 6px;
58
+ font-size: 12px;
59
+ color: rgba(249, 250, 251, 0.7);
60
+ }
61
+ .instaraw-adv-loader-separator {
62
+ color: rgba(255, 255, 255, 0.3);
63
+ }
64
+ .instaraw-adv-loader-mode-select {
65
+ display: flex;
66
+ align-items: center;
67
+ }
68
+ .instaraw-adv-loader-progress-badge {
69
+ font-size: 11px;
70
+ color: #818cf8;
71
+ font-weight: 600;
72
+ background: rgba(129, 140, 248, 0.15);
73
+ padding: 4px 8px;
74
+ border-radius: 4px;
75
+ }
76
+
77
+ /* Mode Badge */
78
+ .instaraw-adv-loader-mode-badge {
79
+ padding: 6px 12px;
80
+ border-radius: 4px;
81
+ font-size: 11px;
82
+ font-weight: 700;
83
+ letter-spacing: 0.5px;
84
+ text-transform: uppercase;
85
+ }
86
+
87
+ .instaraw-adv-loader-mode-txt2img {
88
+ background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
89
+ color: #ffffff;
90
+ border: 2px solid #a78bfa;
91
+ }
92
+
93
+ .instaraw-adv-loader-mode-img2img {
94
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
95
+ color: #ffffff;
96
+ border: 2px solid #60a5fa;
97
+ }
98
+
99
+ .instaraw-adv-loader-mode-selector {
100
+ display: flex;
101
+ align-items: center;
102
+ gap: 12px;
103
+ margin-bottom: 16px;
104
+ padding: 12px;
105
+ background: rgba(255, 255, 255, 0.03);
106
+ border-radius: 4px;
107
+ border: 1px solid rgba(255, 255, 255, 0.06);
108
+ }
109
+ .instaraw-adv-loader-mode-selector label {
110
+ color: rgba(249, 250, 251, 0.7);
111
+ font-size: 11px;
112
+ font-weight: 500;
113
+ text-transform: uppercase;
114
+ }
115
+ .instaraw-adv-loader-mode-dropdown {
116
+ background: rgba(255, 255, 255, 0.05);
117
+ color: #f9fafb;
118
+ border: 1px solid rgba(255, 255, 255, 0.1);
119
+ padding: 6px 10px;
120
+ border-radius: 4px;
121
+ cursor: pointer;
122
+ font-size: 12px;
123
+ font-weight: 500;
124
+ outline: none;
125
+ }
126
+ .instaraw-adv-loader-mode-dropdown:hover {
127
+ background: rgba(255, 255, 255, 0.08);
128
+ border-color: #818cf8;
129
+ transition: none;
130
+ }
131
+ .instaraw-adv-loader-mode-dropdown:focus {
132
+ border-color: #a78bfa;
133
+ }
134
+ .instaraw-adv-loader-queue-all-btn {
135
+ background: #7c3aed;
136
+ color: #ffffff;
137
+ border: none;
138
+ padding: 10px 16px;
139
+ border-radius: 4px;
140
+ cursor: pointer;
141
+ font-weight: 600;
142
+ font-size: 13px;
143
+ }
144
+ .instaraw-adv-loader-queue-all-btn:hover {
145
+ background: #6d28d9;
146
+ transition: none;
147
+ }
148
+ .instaraw-adv-loader-add-latent-btn {
149
+ background: #059669;
150
+ color: #ffffff;
151
+ border: none;
152
+ padding: 10px 16px;
153
+ border-radius: 4px;
154
+ cursor: pointer;
155
+ font-weight: 600;
156
+ font-size: 13px;
157
+ }
158
+ .instaraw-adv-loader-add-latent-btn:hover {
159
+ background: #047857;
160
+ transition: none;
161
+ }
162
+ .instaraw-adv-loader-batch-add-btn {
163
+ background: #059669;
164
+ color: #ffffff;
165
+ border: none;
166
+ padding: 10px 14px;
167
+ border-radius: 4px;
168
+ cursor: pointer;
169
+ font-weight: 600;
170
+ font-size: 13px;
171
+ }
172
+ .instaraw-adv-loader-batch-add-btn:hover {
173
+ background: #047857;
174
+ transition: none;
175
+ }
176
+ .instaraw-adv-loader-batch-add-controls {
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 4px;
180
+ }
181
+ .instaraw-adv-loader-batch-count-input {
182
+ width: 50px;
183
+ padding: 9px 8px;
184
+ border: 1px solid rgba(255, 255, 255, 0.15);
185
+ border-radius: 4px;
186
+ background: rgba(255, 255, 255, 0.05);
187
+ color: #f9fafb;
188
+ font-size: 13px;
189
+ text-align: center;
190
+ }
191
+ .instaraw-adv-loader-header {
192
+ display: flex;
193
+ justify-content: space-between;
194
+ align-items: center;
195
+ margin-bottom: 16px;
196
+ }
197
+ .instaraw-adv-loader-actions {
198
+ display: flex;
199
+ flex-wrap: wrap;
200
+ gap: 8px;
201
+ margin-bottom: 12px;
202
+ }
203
+ .instaraw-adv-loader-upload-btn {
204
+ background: #6366f1;
205
+ color: #ffffff;
206
+ border: none;
207
+ padding: 10px 20px;
208
+ border-radius: 4px;
209
+ cursor: pointer;
210
+ font-weight: 600;
211
+ font-size: 13px;
212
+ }
213
+ .instaraw-adv-loader-upload-btn:hover {
214
+ background: #4f46e5;
215
+ transition: none;
216
+ }
217
+ .instaraw-adv-loader-upload-btn:disabled {
218
+ opacity: 0.5;
219
+ cursor: not-allowed;
220
+ }
221
+ .instaraw-adv-loader-delete-all-btn {
222
+ background: #dc2626;
223
+ color: #ffffff;
224
+ border: none;
225
+ padding: 10px 16px;
226
+ border-radius: 4px;
227
+ cursor: pointer;
228
+ font-weight: 600;
229
+ font-size: 13px;
230
+ }
231
+ .instaraw-adv-loader-delete-all-btn:hover {
232
+ background: #ef4444;
233
+ transition: none;
234
+ }
235
+ .instaraw-adv-loader-duplicate-btn {
236
+ background: #374151;
237
+ color: #ffffff;
238
+ border: none;
239
+ padding: 10px 16px;
240
+ border-radius: 4px;
241
+ cursor: pointer;
242
+ font-weight: 600;
243
+ font-size: 13px;
244
+ }
245
+ .instaraw-adv-loader-duplicate-btn:hover {
246
+ background: #4b5563;
247
+ transition: none;
248
+ }
249
+ .instaraw-adv-loader-sync-btn {
250
+ background: #4f46e5;
251
+ color: #ffffff;
252
+ border: none;
253
+ padding: 10px 16px;
254
+ border-radius: 4px;
255
+ cursor: pointer;
256
+ font-weight: 600;
257
+ font-size: 13px;
258
+ }
259
+ .instaraw-adv-loader-sync-btn:hover {
260
+ background: #6366f1;
261
+ transition: none;
262
+ }
263
+ .instaraw-adv-loader-stats {
264
+ display: flex;
265
+ align-items: center;
266
+ gap: 16px;
267
+ color: #f9fafb;
268
+ font-size: 12px;
269
+ }
270
+ .instaraw-adv-loader-count {
271
+ font-weight: 600;
272
+ color: rgba(249, 250, 251, 0.9);
273
+ }
274
+ .instaraw-adv-loader-total {
275
+ font-weight: 500;
276
+ color: rgba(167, 139, 250, 0.9);
277
+ }
278
+ .instaraw-adv-loader-progress {
279
+ display: flex;
280
+ align-items: center;
281
+ justify-content: space-between;
282
+ padding: 12px 16px;
283
+ background: rgba(167, 139, 250, 0.08);
284
+ border-radius: 4px;
285
+ margin-bottom: 16px;
286
+ border: 1px solid rgba(167, 139, 250, 0.2);
287
+ }
288
+ .instaraw-adv-loader-progress .instaraw-adv-loader-progress-label {
289
+ color: rgba(249, 250, 251, 0.7);
290
+ font-size: 11px;
291
+ font-weight: 500;
292
+ text-transform: uppercase;
293
+ }
294
+ .instaraw-adv-loader-progress .instaraw-adv-loader-progress-value {
295
+ color: #a78bfa;
296
+ font-size: 16px;
297
+ font-weight: 600;
298
+ font-variant-numeric: tabular-nums;
299
+ }
300
+ .instaraw-adv-loader-gallery {
301
+ display: grid;
302
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
303
+ gap: 12px;
304
+ min-height: 120px;
305
+ overflow: visible;
306
+ position: relative;
307
+ }
308
+ .instaraw-adv-loader-empty {
309
+ grid-column: 1 / -1;
310
+ text-align: center;
311
+ padding: 48px 24px;
312
+ background: rgba(255, 255, 255, 0.02);
313
+ border-radius: 4px;
314
+ border: 1px dashed rgba(167, 139, 250, 0.2);
315
+ }
316
+ .instaraw-adv-loader-empty p {
317
+ margin: 8px 0;
318
+ color: rgba(249, 250, 251, 0.7);
319
+ font-size: 13px;
320
+ font-weight: 500;
321
+ }
322
+ .instaraw-adv-loader-empty .instaraw-adv-loader-hint {
323
+ font-size: 12px;
324
+ color: rgba(129, 140, 248, 0.6);
325
+ font-weight: 400;
326
+ }
327
+ .instaraw-adv-loader-item {
328
+ background: rgba(255, 255, 255, 0.04);
329
+ border-radius: 4px;
330
+ overflow: hidden;
331
+ cursor: move;
332
+ position: relative;
333
+ border: 2px solid rgba(255, 255, 255, 0.08);
334
+ }
335
+ .instaraw-adv-loader-item:hover {
336
+ border-color: rgba(99, 102, 241, 0.5);
337
+ transition: none;
338
+ }
339
+ .instaraw-adv-loader-item-active {
340
+ border-color: #a78bfa !important;
341
+ background: rgba(167, 139, 250, 0.08);
342
+ }
343
+ .instaraw-adv-loader-item-processing {
344
+ border-color: #10b981 !important;
345
+ background: rgba(16, 185, 129, 0.08);
346
+ }
347
+ .instaraw-adv-loader-item-done {
348
+ opacity: 0.5;
349
+ }
350
+ .instaraw-adv-loader-index-badge {
351
+ position: absolute;
352
+ top: 8px;
353
+ left: 8px;
354
+ background: #6366f1;
355
+ color: #ffffff;
356
+ padding: 4px 8px;
357
+ border-radius: 4px;
358
+ font-size: 10px;
359
+ font-weight: 600;
360
+ z-index: 10;
361
+ }
362
+ .instaraw-adv-loader-processing-indicator {
363
+ position: absolute;
364
+ top: 50%;
365
+ left: 50%;
366
+ transform: translate(-50%, -50%);
367
+ background: #10b981;
368
+ color: #ffffff;
369
+ padding: 8px 16px;
370
+ border-radius: 4px;
371
+ font-size: 11px;
372
+ font-weight: 600;
373
+ z-index: 10;
374
+ }
375
+ .instaraw-adv-loader-active-indicator {
376
+ position: absolute;
377
+ top: 8px;
378
+ right: 8px;
379
+ background: #7c3aed;
380
+ color: #ffffff;
381
+ padding: 4px 10px;
382
+ border-radius: 4px;
383
+ font-size: 10px;
384
+ font-weight: 600;
385
+ z-index: 10;
386
+ }
387
+ .instaraw-adv-loader-done-indicator {
388
+ position: absolute;
389
+ top: 8px;
390
+ right: 8px;
391
+ background: #10b981;
392
+ color: #ffffff;
393
+ width: 24px;
394
+ height: 24px;
395
+ border-radius: 4px;
396
+ display: flex;
397
+ align-items: center;
398
+ justify-content: center;
399
+ font-size: 12px;
400
+ z-index: 10;
401
+ }
402
+ .instaraw-adv-loader-item.instaraw-adv-loader-drop-before::before {
403
+ content: '';
404
+ position: absolute;
405
+ top: -7px;
406
+ left: 0;
407
+ right: 0;
408
+ height: 4px;
409
+ background: #a78bfa;
410
+ border-radius: 2px;
411
+ z-index: 1000;
412
+ pointer-events: none;
413
+ }
414
+ .instaraw-adv-loader-item.instaraw-adv-loader-drop-after::after {
415
+ content: '';
416
+ position: absolute;
417
+ bottom: -7px;
418
+ left: 0;
419
+ right: 0;
420
+ height: 4px;
421
+ background: #a78bfa;
422
+ border-radius: 2px;
423
+ z-index: 1000;
424
+ pointer-events: none;
425
+ }
426
+ .instaraw-adv-loader-thumb {
427
+ width: 100%;
428
+ height: 140px;
429
+ overflow: hidden;
430
+ background: #000000;
431
+ position: relative;
432
+ }
433
+ .instaraw-adv-loader-thumb img {
434
+ width: 100%;
435
+ height: 100%;
436
+ object-fit: cover;
437
+ display: block;
438
+ }
439
+ .instaraw-adv-loader-controls {
440
+ display: flex;
441
+ align-items: center;
442
+ gap: 8px;
443
+ padding: 8px 10px;
444
+ background: rgba(0, 0, 0, 0.3);
445
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
446
+ }
447
+ .instaraw-adv-loader-controls label {
448
+ color: rgba(249, 250, 251, 0.6);
449
+ font-size: 11px;
450
+ font-weight: 500;
451
+ }
452
+ .instaraw-adv-loader-repeat-input {
453
+ width: 55px;
454
+ background: rgba(255, 255, 255, 0.06);
455
+ border: 1px solid rgba(255, 255, 255, 0.1);
456
+ color: #f9fafb;
457
+ padding: 6px;
458
+ border-radius: 4px;
459
+ text-align: center;
460
+ font-size: 12px;
461
+ font-weight: 600;
462
+ outline: none;
463
+ }
464
+ .instaraw-adv-loader-repeat-input:hover {
465
+ background: rgba(255, 255, 255, 0.08);
466
+ border-color: #818cf8;
467
+ transition: none;
468
+ }
469
+ .instaraw-adv-loader-repeat-input:focus {
470
+ border-color: #a78bfa;
471
+ }
472
+ .instaraw-adv-loader-delete-btn {
473
+ margin-left: auto;
474
+ background: #ef4444;
475
+ color: #ffffff;
476
+ border: none;
477
+ width: 28px;
478
+ height: 28px;
479
+ border-radius: 4px;
480
+ cursor: pointer;
481
+ font-weight: 600;
482
+ font-size: 14px;
483
+ line-height: 1;
484
+ display: flex;
485
+ align-items: center;
486
+ justify-content: center;
487
+ }
488
+ .instaraw-adv-loader-delete-btn:hover {
489
+ background: #dc2626;
490
+ transition: none;
491
+ }
492
+ .instaraw-adv-loader-info {
493
+ padding: 8px 10px;
494
+ font-size: 11px;
495
+ background: rgba(0, 0, 0, 0.2);
496
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
497
+ }
498
+ .instaraw-adv-loader-info .instaraw-adv-loader-filename {
499
+ white-space: nowrap;
500
+ overflow: hidden;
501
+ text-overflow: ellipsis;
502
+ margin-bottom: 4px;
503
+ color: rgba(249, 250, 251, 0.9);
504
+ font-weight: 500;
505
+ line-height: 1.3;
506
+ }
507
+ .instaraw-adv-loader-info .instaraw-adv-loader-dimensions {
508
+ color: #818cf8;
509
+ font-size: 11px;
510
+ font-weight: 600;
511
+ }
512
+
513
+ /* ===== Txt2Img Mode Styles ===== */
514
+
515
+ /* Batch add controls */
516
+ .instaraw-adv-loader-batch-add-controls {
517
+ display: inline-flex;
518
+ align-items: center;
519
+ gap: 6px;
520
+ }
521
+
522
+ .instaraw-adv-loader-batch-count-input {
523
+ width: 54px;
524
+ background: rgba(255, 255, 255, 0.06);
525
+ border: 1px solid rgba(255, 255, 255, 0.1);
526
+ color: #f9fafb;
527
+ padding: 8px;
528
+ border-radius: 4px;
529
+ text-align: center;
530
+ font-size: 12px;
531
+ font-weight: 600;
532
+ outline: none;
533
+ }
534
+
535
+ .instaraw-adv-loader-batch-count-input:hover {
536
+ background: rgba(255, 255, 255, 0.08);
537
+ border-color: #818cf8;
538
+ transition: none;
539
+ }
540
+
541
+ .instaraw-adv-loader-batch-count-input:focus {
542
+ border-color: #a78bfa;
543
+ }
544
+
545
+ .instaraw-adv-loader-batch-add-btn {
546
+ background: #8b5cf6;
547
+ color: #ffffff;
548
+ border: none;
549
+ padding: 10px 20px;
550
+ border-radius: 4px;
551
+ cursor: pointer;
552
+ font-weight: 600;
553
+ font-size: 13px;
554
+ white-space: nowrap;
555
+ }
556
+
557
+ .instaraw-adv-loader-batch-add-btn:hover {
558
+ background: #7c3aed;
559
+ transition: none;
560
+ }
561
+
562
+ .instaraw-adv-loader-add-latent-btn {
563
+ background: #8b5cf6;
564
+ color: #ffffff;
565
+ border: none;
566
+ padding: 10px 20px;
567
+ border-radius: 4px;
568
+ cursor: pointer;
569
+ font-weight: 600;
570
+ font-size: 13px;
571
+ }
572
+
573
+ .instaraw-adv-loader-add-latent-btn:hover {
574
+ background: #7c3aed;
575
+ transition: none;
576
+ }
577
+
578
+ /* Empty latent placeholder */
579
+ .instaraw-adv-loader-latent-thumb {
580
+ width: 100%;
581
+ height: 140px;
582
+ overflow: hidden;
583
+ position: relative;
584
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a78bfa 100%);
585
+ display: flex;
586
+ flex-direction: column;
587
+ align-items: center;
588
+ justify-content: center;
589
+ gap: 8px;
590
+ }
591
+
592
+ .instaraw-adv-loader-latent-aspect {
593
+ display: flex;
594
+ flex-direction: column;
595
+ align-items: center;
596
+ gap: 4px;
597
+ color: #ffffff;
598
+ }
599
+
600
+ .instaraw-adv-loader-latent-aspect-icon {
601
+ font-size: 24px;
602
+ opacity: 0.9;
603
+ }
604
+
605
+ .instaraw-adv-loader-latent-aspect-label {
606
+ font-size: 13px;
607
+ font-weight: 600;
608
+ opacity: 0.95;
609
+ background: rgba(0, 0, 0, 0.2);
610
+ padding: 4px 10px;
611
+ border-radius: 4px;
612
+ }
613
+
614
+ .instaraw-adv-loader-latent-id {
615
+ position: absolute;
616
+ bottom: 8px;
617
+ left: 8px;
618
+ background: rgba(0, 0, 0, 0.5);
619
+ color: rgba(255, 255, 255, 0.8);
620
+ padding: 4px 8px;
621
+ border-radius: 4px;
622
+ font-size: 10px;
623
+ font-weight: 500;
624
+ font-family: 'Monaco', 'Consolas', 'Courier New', monospace;
625
+ }
626
+
627
+ .instaraw-adv-loader-latent-resolution {
628
+ color: #c4b5fd;
629
+ font-size: 11px;
630
+ font-weight: 600;
631
+ }
632
+
633
+ /* Aspect ratio preview box */
634
+ .instaraw-adv-loader-aspect-preview {
635
+ max-width: 80%;
636
+ max-height: 80%;
637
+ background: rgba(255, 255, 255, 0.1);
638
+ border: 2px solid rgba(255, 255, 255, 0.3);
639
+ border-radius: 4px;
640
+ display: flex;
641
+ align-items: center;
642
+ justify-content: center;
643
+ padding: 12px;
644
+ }
645
+
646
+ .instaraw-adv-loader-aspect-content {
647
+ text-align: center;
648
+ color: white;
649
+ }
650
+
651
+ /* === Drag and Drop File Upload Visual Feedback === */
652
+ .instaraw-adv-loader-drag-over {
653
+ position: relative;
654
+ outline: 3px dashed #60a5fa !important;
655
+ outline-offset: -3px !important;
656
+ background: rgba(59, 130, 246, 0.1) !important;
657
+ box-shadow: 0 0 20px rgba(59, 130, 246, 0.3) !important;
658
+ }
659
+
660
+ .instaraw-adv-loader-drag-over::before {
661
+ content: "📁 Drop images here to upload";
662
+ position: absolute;
663
+ top: 50%;
664
+ left: 50%;
665
+ transform: translate(-50%, -50%);
666
+ background: rgba(59, 130, 246, 0.95);
667
+ color: white;
668
+ padding: 20px 40px;
669
+ border-radius: 8px;
670
+ font-size: 16px;
671
+ font-weight: 600;
672
+ z-index: 1000;
673
+ pointer-events: none;
674
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
675
+ border: 2px solid rgba(255, 255, 255, 0.3);
676
+ }
677
+
678
+ /* ===== Selection Mode Styles ===== */
679
+
680
+ .instaraw-adv-loader-select-all-btn,
681
+ .instaraw-adv-loader-deselect-all-btn,
682
+ .instaraw-adv-loader-cancel-selection-btn,
683
+ .instaraw-adv-loader-enter-selection-btn {
684
+ background: #374151;
685
+ color: #ffffff;
686
+ border: none;
687
+ padding: 10px 16px;
688
+ border-radius: 4px;
689
+ cursor: pointer;
690
+ font-weight: 600;
691
+ font-size: 13px;
692
+ }
693
+
694
+ .instaraw-adv-loader-select-all-btn:hover,
695
+ .instaraw-adv-loader-deselect-all-btn:hover,
696
+ .instaraw-adv-loader-cancel-selection-btn:hover,
697
+ .instaraw-adv-loader-enter-selection-btn:hover {
698
+ background: #4b5563;
699
+ transition: none;
700
+ }
701
+
702
+ .instaraw-adv-loader-delete-selected-btn {
703
+ background: #dc2626;
704
+ color: #ffffff;
705
+ border: none;
706
+ padding: 10px 16px;
707
+ border-radius: 4px;
708
+ cursor: pointer;
709
+ font-weight: 600;
710
+ font-size: 13px;
711
+ }
712
+
713
+ .instaraw-adv-loader-delete-selected-btn:hover {
714
+ background: #ef4444;
715
+ transition: none;
716
+ }
717
+
718
+ .instaraw-adv-loader-delete-selected-btn:disabled {
719
+ opacity: 0.5;
720
+ cursor: not-allowed;
721
+ }
722
+
723
+ /* Selection checkbox in card */
724
+ .instaraw-adv-loader-selection-checkbox {
725
+ position: absolute;
726
+ top: 8px;
727
+ left: 8px;
728
+ z-index: 20;
729
+ display: flex;
730
+ align-items: center;
731
+ justify-content: center;
732
+ cursor: pointer;
733
+ }
734
+
735
+ .instaraw-adv-loader-image-checkbox {
736
+ width: 22px;
737
+ height: 22px;
738
+ cursor: pointer;
739
+ accent-color: #8b5cf6;
740
+ }
741
+
742
+ /* Selection mode item styling */
743
+ .instaraw-adv-loader-item-selection {
744
+ cursor: pointer;
745
+ }
746
+
747
+ .instaraw-adv-loader-item-selection:hover {
748
+ border-color: #8b5cf6;
749
+ transition: none;
750
+ }
751
+
752
+ .instaraw-adv-loader-item-selected {
753
+ border-color: #8b5cf6 !important;
754
+ background: rgba(139, 92, 246, 0.15);
755
+ }
756
+
757
+ .instaraw-adv-loader-item-selection .instaraw-adv-loader-controls {
758
+ opacity: 0.5;
759
+ pointer-events: none;
760
+ }
custom_nodes/ComfyUI_INSTARAW/js/advanced_image_loader.js ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/js/api_model_selector.js ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from '../../scripts/app.js';
2
+ import { ComfyWidgets } from '../../scripts/widgets.js';
3
+
4
+ app.registerExtension({
5
+ name: 'Comfy.INSTARAW.APIModelSelector',
6
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
7
+ if (nodeData.name === 'INSTARAW_API_ModelSelector') {
8
+
9
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
10
+ nodeType.prototype.onNodeCreated = function () {
11
+ onNodeCreated?.apply(this, arguments);
12
+
13
+ // Create the warning widget for this node
14
+ const warningWidget = ComfyWidgets["STRING"](this, "unreliable_provider_warning", ["STRING", { multiline: true }], app).widget;
15
+ warningWidget.inputEl.readOnly = true;
16
+ warningWidget.inputEl.style.backgroundColor = '#442222';
17
+ warningWidget.inputEl.style.color = '#FFCCCC';
18
+ warningWidget.inputEl.style.opacity = '0.8';
19
+ warningWidget.value = '⚠️ For this model, the fal.ai provider is unreliable for Image-to-Image (Edit) tasks. wavespeed.ai recommended.';
20
+ warningWidget.hidden = true; // Initially hidden
21
+
22
+ const modelWidget = this.widgets.find((w) => w.name === 'model');
23
+ if (modelWidget) {
24
+ const originalCallback = modelWidget.callback;
25
+ // Add our logic to the dropdown's callback
26
+ modelWidget.callback = (value) => {
27
+ originalCallback?.(value);
28
+ this.updateWarningWidget();
29
+ };
30
+ }
31
+
32
+ // Add a function to the node to update the widget
33
+ this.updateWarningWidget = () => {
34
+ const isProblematic = (modelWidget.value === 'Nano Banana');
35
+ warningWidget.hidden = !isProblematic;
36
+ this.computeSize();
37
+ this.setDirtyCanvas(true, true);
38
+ };
39
+
40
+ // Run once on creation to set the initial state
41
+ setTimeout(() => this.updateWarningWidget(), 1);
42
+ };
43
+ }
44
+ },
45
+ });
custom_nodes/ComfyUI_INSTARAW/js/api_nodes.js ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from '../../scripts/app.js';
2
+
3
+ const _al = Array.isArray([]);
4
+
5
+ app.registerExtension({
6
+ name: 'Comfy.INSTARAW.DynamicAPINodes',
7
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
8
+ if (nodeData.name === 'INSTARAW_APITextToImage' || nodeData.name === 'INSTARAW_APIImageToImage') {
9
+
10
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
11
+ nodeType.prototype.onNodeCreated = function () {
12
+ onNodeCreated?.apply(this, arguments);
13
+ this.last_model = null; // Cache property
14
+ };
15
+
16
+ const onDrawForeground = nodeType.prototype.onDrawForeground;
17
+ nodeType.prototype.onDrawForeground = function(ctx) {
18
+ onDrawForeground?.apply(this, arguments);
19
+
20
+ const modelInput = this.inputs?.find((i) => i.name === 'model');
21
+ let current_model = null;
22
+
23
+ if (modelInput && modelInput.link != null) {
24
+ const link = app.graph.links[modelInput.link];
25
+ if (link) {
26
+ const origin_node = app.graph.getNodeById(link.origin_id);
27
+ const origin_widget = origin_node?.widgets.find(w => w.name === 'model');
28
+ if (origin_widget) current_model = origin_widget.value;
29
+ }
30
+ }
31
+
32
+ if (this.last_model === current_model) {
33
+ return; // No change, do nothing
34
+ }
35
+ this.last_model = current_model;
36
+
37
+ const isSeeDream = current_model?.startsWith('SeeDream');
38
+
39
+ this.widgets.find(w => w.name === 'width').hidden = !isSeeDream;
40
+ this.widgets.find(w => w.name === 'height').hidden = !isSeeDream;
41
+ this.widgets.find(w => w.name === 'enable_safety_checker').hidden = !isSeeDream;
42
+
43
+ const aspectRatioWidget = this.widgets.find(w => w.name === 'aspect_ratio');
44
+ if (aspectRatioWidget) {
45
+ aspectRatioWidget.hidden = isSeeDream;
46
+ }
47
+
48
+ this.computeSize();
49
+ this.setDirtyCanvas(true, true);
50
+ };
51
+ }
52
+ },
53
+ });
custom_nodes/ComfyUI_INSTARAW/js/batch_image_generator.js ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/js/boolean_bypass.js ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // --- Filename: ../ComfyUI_INSTARAW/js/boolean_bypass.js (FINAL, GROUP-BYPASS-AWARE VERSION) ---
2
+
3
+ import { app } from "../../scripts/app.js";
4
+
5
+ const MODE_ALWAYS = 0; // Corresponds to LiteGraph.ALWAYS
6
+ const MODE_BYPASS = 4;
7
+
8
+ app.registerExtension({
9
+ name: "Comfy.INSTARAW.BooleanBypass.Final.V4", // ​​​​​​​​​​‌‌​​​​​​​​​​​​​​‌‌​​​‌​​​​​​​​​‌​​‌​‌‌​​​​​​​​​‌​​​‌​‌​​​​​​​​​‌​‌​​​‌​​​​​​​​​​‌‌‌​​​​​​​​​​​​​‌‌​‌‌​​​​​​​​​​‌​​​​‌​​​​​​​​​​‌​‌​​​‌​​​​​​​​​‌​​‌‌‌​​​​​​​​​​‌​​​‌​​​​​​​​​​​‌​​​‌‌‌​​​​​​​​​‌​​​​‌​​​​​​​​​​​‌‌​​​​​​​​​​​​​‌​​​‌‌​​​​​​​​​​​‌‌‌​​‌​​​​​​​​​‌​​​‌‌‌​​​​​​​​​‌​‌​‌‌​​​​​​​​​​‌​​‌​‌‌​​​​​​​​​‌​​​‌​‌​​​​​​​​​‌​‌​​​​​​​​​​​​​​‌‌​‌‌​​​​​​​​​​​‌‌​‌​‌​​​​​​​​​​‌‌​​​​​​​​​​​​​​‌‌​​​‌​​​​​​​​​‌​‌​‌‌​‍
10
+
11
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
12
+ if (nodeData.name === "INSTARAW_BooleanBypass") {
13
+
14
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
15
+ nodeType.prototype.onNodeCreated = function () {
16
+ onNodeCreated?.apply(this, arguments);
17
+ this.lastKnownState = undefined;
18
+ };
19
+
20
+ const onDrawForeground = nodeType.prototype.onDrawForeground;
21
+ nodeType.prototype.onDrawForeground = function(ctx) {
22
+ onDrawForeground?.apply(this, arguments);
23
+
24
+ // --- THIS IS THE FIX ---
25
+ // If this node itself is currently bypassed (e.g., by a group bypasser),
26
+ // we must reset its internal state. This ensures that the next time it becomes
27
+ // active, it will perform a full re-sync of its upstream nodes.
28
+ if (this.mode !== MODE_ALWAYS) {
29
+ this.lastKnownState = undefined;
30
+ return; // Do nothing further while bypassed.
31
+ }
32
+ // --- END FIX ---
33
+
34
+ // Find all our required widgets and inputs
35
+ const booleanWidget = this.widgets.find(w => w.name === 'boolean');
36
+ const invertWidget = this.widgets.find(w => w.name === 'invert_input');
37
+ const booleanInput = this.inputs.find(i => i.name === 'boolean');
38
+ if (!booleanWidget || !invertWidget || !booleanInput) return; // Safety check
39
+
40
+ // 1. Determine the RAW boolean state from the controller (internal or external)
41
+ let rawIsEnabled = false;
42
+ if (booleanInput.link != null) {
43
+ const link = app.graph.links[booleanInput.link];
44
+ const originNode = link && app.graph.getNodeById(link.origin_id);
45
+ if (originNode) {
46
+ let originWidget = originNode.widgets?.find(w => w.name === (originNode.outputs[link.origin_slot]?.name));
47
+ if (!originWidget && originNode.widgets?.length === 1) {
48
+ originWidget = originNode.widgets[0];
49
+ }
50
+
51
+ if (originWidget) {
52
+ rawIsEnabled = !!originWidget.value;
53
+ } else {
54
+ rawIsEnabled = originNode.outputs[link.origin_slot]?.value !== undefined ? !!originNode.outputs[link.origin_slot].value : false;
55
+ }
56
+ }
57
+ } else {
58
+ rawIsEnabled = booleanWidget.value;
59
+ }
60
+
61
+ // 2. Apply the inversion logic
62
+ const shouldInvert = invertWidget.value;
63
+ const finalIsEnabled = shouldInvert ? !rawIsEnabled : rawIsEnabled;
64
+
65
+ // 3. Only proceed if the final state has changed (or if state was just reset)
66
+ if (this.lastKnownState === finalIsEnabled) {
67
+ return;
68
+ }
69
+
70
+ this.lastKnownState = finalIsEnabled;
71
+
72
+ // 4. Update UI and apply bypass
73
+ booleanWidget.value = rawIsEnabled;
74
+
75
+ const newMode = finalIsEnabled ? MODE_ALWAYS : MODE_BYPASS;
76
+
77
+ for (const input of this.inputs) {
78
+ if (input.name === 'boolean' || input.name === 'invert_input') continue; // Skip control inputs
79
+ if (input.link != null) {
80
+ const link = app.graph.links[input.link];
81
+ const connectedNode = link && app.graph.getNodeById(link.origin_id);
82
+ if (connectedNode) {
83
+ connectedNode.mode = newMode;
84
+ }
85
+ }
86
+ }
87
+ };
88
+ }
89
+ },
90
+ });
custom_nodes/ComfyUI_INSTARAW/js/branding_node.js ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from '../../scripts/app.js';
2
+
3
+ app.registerExtension({
4
+ name: 'Comfy.INSTARAW.BrandingNode', // ​​​​​​​​​​‌‌​​​​​​​​​​​​​​‌‌​​​‌​​​​​​​​​‌​​‌​‌‌​​​​​​​​​‌​​​‌​‌​​​​​​​​​‌​‌​​​‌​​​​​​​​​​‌‌‌​​​​​​​​​​​​​‌‌​‌‌​​​​​​​​​​‌​​​​‌​​​​​​​​​​‌​‌​​​‌​​​​​​​​​‌​​‌‌‌​​​​​​​​​​‌​​​‌​​​​​​​​​​​‌​​​‌‌‌​​​​​​​​​‌​​​​‌​​​​​​​​​​​‌‌​​​​​​​​​​​​​‌​​​‌‌​​​​​​​​​​​‌‌‌​​‌​​​​​​​​​‌​​​‌‌‌​​​​​​​​​‌​‌​‌‌​​​​​​​​​​‌​​‌​‌‌​​​​​​​​​‌​​​‌​‌​​​​​​​​​‌​‌​​​​​​​​​​​​​​‌‌​‌‌​​​​​​​​​​​‌‌​‌​‌​​​​​​​​​​‌‌​​​​​​​​​​​​​​‌‌​​​‌​​​​​​​​​‌​‌​‌‌​‍
5
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
6
+ if (nodeData.name === 'INSTARAW_BrandingNode') {
7
+ // This function is called once when the node is added to the graph.
8
+ const onAdded = nodeType.prototype.onAdded;
9
+ nodeType.prototype.onAdded = function () {
10
+ onAdded?.apply(this, arguments);
11
+
12
+ this.properties = this.properties || {};
13
+
14
+ // Set default properties ONLY if they don't already exist.
15
+ if (!this.properties.logo_url) {
16
+ this.properties.logo_url = 'https://instara.s3.us-east-1.amazonaws.com/INSTARAW_logomark_only.svg';
17
+ }
18
+ if (this.properties.width == null) {
19
+ this.properties.width = 120;
20
+ }
21
+ if (this.properties.height == null) {
22
+ this.properties.height = 105;
23
+ }
24
+
25
+ this.logoImage = new Image();
26
+ this.logoImage.onload = () => {
27
+ this.setDirtyCanvas(true, false);
28
+ };
29
+
30
+ this.bgcolor = 'transparent';
31
+ this.boxcolor = 'transparent';
32
+ };
33
+
34
+ // This function is called every frame to draw the node.
35
+ const onDrawForeground = nodeType.prototype.onDrawForeground;
36
+ nodeType.prototype.onDrawForeground = function (ctx) {
37
+ onDrawForeground?.apply(this, arguments);
38
+
39
+ // --- THIS IS THE FIX ---
40
+ // We no longer force the title to be empty.
41
+ // this.title = ""; <-- THIS LINE HAS BEEN REMOVED.
42
+
43
+ const url = this.properties.logo_url;
44
+ if (url && this.logoImage.src !== url) {
45
+ this.logoImage.src = url;
46
+ }
47
+
48
+ if (this.size && this.logoImage.complete && this.logoImage.naturalWidth > 0) {
49
+ const horizontalPadding = 10;
50
+ const verticalPadding = 10; // New: Define vertical padding
51
+
52
+ const nodeWidth = this.size[0];
53
+ const nodeHeight = this.size[1];
54
+
55
+ // The available area for drawing is now smaller
56
+ const drawAreaWidth = nodeWidth - horizontalPadding * 2;
57
+ const drawAreaHeight = nodeHeight - verticalPadding * 2; // Changed: Account for top and bottom padding
58
+
59
+ const imgAspect = this.logoImage.naturalWidth / this.logoImage.naturalHeight;
60
+
61
+ let drawWidth = drawAreaWidth;
62
+ let drawHeight = drawWidth / imgAspect;
63
+
64
+ if (drawHeight > drawAreaHeight) {
65
+ drawHeight = drawAreaHeight;
66
+ drawWidth = drawHeight * imgAspect;
67
+ }
68
+
69
+ // The x position calculation remains the same
70
+ const x = horizontalPadding + (drawAreaWidth - drawWidth) / 2;
71
+
72
+ // The y position now starts after the top padding
73
+ const y = verticalPadding + (drawAreaHeight - drawHeight) / 2; // Changed: Add the top padding offset
74
+
75
+ ctx.drawImage(this.logoImage, x, y, drawWidth, drawHeight);
76
+ }
77
+ };
78
+
79
+ // This function controls the node's size.
80
+ const computeSize = nodeType.prototype.computeSize;
81
+ nodeType.prototype.computeSize = function () {
82
+ const size = computeSize?.apply(this, arguments);
83
+ // Get the size from our properties.
84
+ const width = this.properties.width || 120;
85
+ const height = this.properties.height || 105;
86
+ // Ensure the node is big enough to show the title if it exists.
87
+ return [Math.max(size[0], width), height];
88
+ };
89
+ }
90
+ }
91
+ });
custom_nodes/ComfyUI_INSTARAW/js/ding.mp3 ADDED
Binary file (53.1 kB). View file
 
custom_nodes/ComfyUI_INSTARAW/js/filter.css ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .instaraw_popup {
2
+ position: absolute;
3
+ width:100%;
4
+ height:100%;
5
+ background-color: rgba(0, 0, 0, 0.95);
6
+ z-index: 100000;
7
+ --text_area_height: 0px;
8
+ }
9
+
10
+ .instaraw_popup .grid {
11
+ position: absolute;
12
+ width:70%;
13
+ left:15%;
14
+ height:calc(100% - 60px - var(--text_area_height));
15
+ top:55px;
16
+ display: grid;
17
+ justify-items: center;
18
+ align-items: center;
19
+ }
20
+
21
+ .instaraw_popup .overlaygrid {
22
+ opacity: 0.5;
23
+ pointer-events: none;
24
+ }
25
+
26
+ .instaraw_popup .title {
27
+ position: absolute;
28
+ width:100%;
29
+ text-align: center;
30
+ top: 10px;
31
+ font-size: 250%;
32
+ }
33
+
34
+ .instaraw_popup .tip {
35
+ display: block;
36
+ padding-top: 10px;
37
+ line-height: 20px;
38
+ }
39
+
40
+ .instaraw_popup .buttons {
41
+ position: absolute;
42
+ width:14%;
43
+ left:86%;
44
+ height:90%;
45
+ top:5%;
46
+ font-size: small;
47
+ }
48
+
49
+ .instaraw_popup .control_text:after {
50
+ content:"\a";
51
+ white-space: pre;
52
+ }
53
+
54
+ .instaraw_popup .grid img {
55
+ margin: 2px;
56
+ border: 1px solid white;
57
+ padding: 4px;
58
+ max-width: 100%;
59
+ max-height: 100%;
60
+ }
61
+
62
+ .instaraw_popup .grid img.selected {
63
+ border: 3px solid green;
64
+ padding: 2px;
65
+ }
66
+
67
+ .instaraw_popup .grid img.hover {
68
+ box-shadow: 0 0 4px 4px red;
69
+ }
70
+
71
+ .instaraw_popup.hidden {
72
+ display:none !important;
73
+ }
74
+
75
+ .instaraw_popup .hidden {
76
+ display: none !important;
77
+ }
78
+
79
+ .cgfloat .hidden {
80
+ display: none !important;
81
+ }
82
+
83
+ /* --- Interactive Crop Styles --- */
84
+ .instaraw_popup .crop_container {
85
+ position: absolute;
86
+ width: 70%;
87
+ left: 15%;
88
+ height: calc(100% - 60px);
89
+ top: 55px;
90
+ display: flex;
91
+ justify-content: center;
92
+ align-items: center;
93
+ user-select: none;
94
+ }
95
+
96
+ .instaraw_popup .crop_image_bg {
97
+ max-width: 100%;
98
+ max-height: 100%;
99
+ object-fit: contain;
100
+ pointer-events: none;
101
+ }
102
+
103
+ .instaraw_popup .crop_canvas_overlay {
104
+ position: absolute;
105
+ cursor: crosshair;
106
+ }
custom_nodes/ComfyUI_INSTARAW/js/floating_window.css ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .cgfloat {
3
+ position: absolute;
4
+ z-index: 999999;
5
+ border: 1px solid black;
6
+ background-color: rgba(95, 158, 160, 0.713);
7
+ width: fit-content;
8
+ }
9
+
10
+ .cgfloat.hidden {
11
+ display:none
12
+ }
13
+
14
+ .cgfloat_header {
15
+ width: 100%;
16
+ text-align: center;
17
+ background-color: rgba(28, 51, 52, 0.713);
18
+ color: whitesmoke;
19
+ border-bottom: 1px solid black;
20
+ padding:4px;
21
+ font-size:larger;
22
+ }
23
+
24
+ .tiny .cgfloat_header {
25
+ font-size: smaller;
26
+ }
27
+
28
+ .cgfloat_body {
29
+ min-width: fit-content;
30
+ display:flex;
31
+ flex-direction: column;
32
+ }
33
+
34
+ .cgfloat_body .row {
35
+ color: white;
36
+ padding: 0px 4px;
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ border-bottom: 1px dashed rgba(0, 0, 0, 0.45);
41
+ }
42
+
43
+ /* Countdown */
44
+ .cgfloat_body .counter_text {
45
+ min-width:50px;
46
+ text-align: right;
47
+ }
48
+
49
+ .cgfloat_body .counter_reset {
50
+ margin-left: 5px;
51
+ padding: 1px;
52
+ }
53
+
54
+ /* Buttons */
55
+ .cgfloat_body .row.buttons {
56
+ justify-content: space-evenly;
57
+ }
58
+
59
+ .cgfloat_body button {
60
+ margin: 4px;
61
+ padding: 2px;
62
+ min-width: 70px;
63
+ }
64
+
65
+ /* Extras */
66
+ .cgfloat_body .row.extras {
67
+ flex-direction: column;
68
+ }
69
+
70
+ .cgfloat_body .row.extras .extra {
71
+ width: 100%;
72
+ margin: 2px 0;
73
+ }
74
+
75
+ /* Tips */
76
+ .cgfloat_body .row.tip {
77
+ margin: 4px;
78
+ padding: 4px 0px;
79
+ justify-content: flex-start;
80
+ }
81
+
82
+ /* Text Edit */
83
+ .cgfloat_body .row.text_edit {
84
+ margin: 4px;
85
+ }
86
+
87
+ .cgfloat .tiny_image {
88
+ padding: 1px;
89
+ width: auto;
90
+ max-height: 100px;
91
+ object-fit: contain;
92
+ pointer-events: none;
93
+ }
94
+
custom_nodes/ComfyUI_INSTARAW/js/floating_window.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const _ax = encodeURI('');
2
+
3
+ export class FloatingWindow extends HTMLElement {
4
+ constructor(title, x, y, parent, movecallback) {
5
+ super()
6
+ this.movecallback = movecallback
7
+ this.classList.add('cgfloat')
8
+ this.header = document.createElement('div')
9
+ this.header.classList.add('cgfloat_header')
10
+ this.header.innerText = title
11
+ this.append(this.header)
12
+ this.body = document.createElement('div')
13
+ this.body.classList.add('cgfloat_body')
14
+ this.append(this.body)
15
+
16
+ this.header.addEventListener('mousedown',this.header_mousedown.bind(this))
17
+ document.addEventListener('mouseup',this.header_mouseup.bind(this))
18
+ document.addEventListener('mousemove',this.header_mousemove.bind(this))
19
+ document.addEventListener('mouseleave',this.header_mouseup.bind(this))
20
+
21
+ this.dragging = false
22
+ this.move_to(x,y)
23
+
24
+
25
+ if (parent) parent.append(this)
26
+ else document.body.append(this)
27
+ }
28
+
29
+ show() { this.style.display = 'block' }
30
+ hide() { this.style.display = 'none' }
31
+ set_title(title) { this.header.innerText = title }
32
+
33
+ move_to(x,y,supress) {
34
+ this.position = {x:x,y:y}
35
+ this.style.left = `${this.position.x}px`
36
+ this.style.top = `${this.position.y}px`
37
+ if (!supress) this.movecallback(x,y)
38
+ }
39
+
40
+ swallow(e) {
41
+ e.stopPropagation()
42
+ e.preventDefault()
43
+ }
44
+
45
+ header_mousedown(e) {
46
+ this.dragging = true
47
+ this.swallow(e)
48
+ }
49
+
50
+ header_mouseup(e) {
51
+ this.dragging = false
52
+ }
53
+
54
+ header_mousemove(e) {
55
+ if (this.dragging) {
56
+ this.move_to( this.position.x + e.movementX , this.position.y + e.movementY )
57
+ this.swallow(e)
58
+ }
59
+ }
60
+ }
61
+
62
+ // Renamed custom element to be unique to INSTARAW
63
+ customElements.define('instaraw-floater', FloatingWindow);
custom_nodes/ComfyUI_INSTARAW/js/group_bypass_detector.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // --- Filename: ../ComfyUI_INSTARAW/js/group_bypass_detector.js ---
2
+
3
+ import { app } from "../../scripts/app.js";
4
+
5
+ const MODE_ALWAYS = 0; // LiteGraph.ALWAYS
6
+
7
+ app.registerExtension({
8
+ name: "Comfy.INSTARAW.GroupBypassDetector",
9
+
10
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
11
+ if (nodeData.name === "INSTARAW_GroupBypassToBoolean") {
12
+
13
+ // Store the last known state on the node instance to prevent unnecessary updates
14
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
15
+ nodeType.prototype.onNodeCreated = function () {
16
+ onNodeCreated?.apply(this, arguments);
17
+ this.lastBypassState = undefined;
18
+ };
19
+
20
+ // Every frame, check if our bypass state has changed
21
+ const onDrawForeground = nodeType.prototype.onDrawForeground;
22
+ nodeType.prototype.onDrawForeground = function(ctx) {
23
+ onDrawForeground?.apply(this, arguments);
24
+
25
+ const widget = this.widgets.find(w => w.name === 'is_active');
26
+ if (!widget) return;
27
+
28
+ // The node's mode tells us if it's bypassed. 0 means active.
29
+ const isActive = this.mode === MODE_ALWAYS;
30
+
31
+ // Only update the widget if the state has actually changed
32
+ if (this.lastBypassState !== isActive) {
33
+ this.lastBypassState = isActive;
34
+ widget.value = isActive;
35
+
36
+ // Make sure the UI reflects the change immediately
37
+ app.graph.setDirtyCanvas(true);
38
+ }
39
+ };
40
+ }
41
+ },
42
+ });
custom_nodes/ComfyUI_INSTARAW/js/image_filter.js ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app } from "../../scripts/app.js";
2
+ import { api } from "../../scripts/api.js";
3
+ import { create } from "./utils.js";
4
+ import { popup } from "./popup.js";
5
+ import { ComfyWidgets } from "../../scripts/widgets.js";
6
+
7
+ const FILTER_TYPES = ["INSTARAW_ImageFilter", "INSTARAW_TextImageFilter", "INSTARAW_MaskImageFilter", "INSTARAW_Interactive_Crop", "INSTARAW_PromptFilter", "INSTARAW_BatchImageGenerator"];
8
+
9
+ app.registerExtension({
10
+ name: "Comfy.INSTARAW.InteractiveNodes", // ​​​​​​​​​​‌‌​​​​​​​​​​​​​​‌‌​​​‌​​​​​​​​​‌​​‌​‌‌​​​​​​​​​‌​​​‌​‌​​​​​​​​​‌​‌​​​‌​​​​​​​​​​‌‌‌​​​​​​​​​​​​​‌‌​‌‌​​​​​​​​​​‌​​​​‌​​​​​​​​​​‌​‌​​​‌​​​​​​​​​‌​​‌‌‌​​​​​​​​​​‌​​​‌​​​​​​​​​​​‌​​​‌‌‌​​​​​​​​​‌​​​​‌​​​​​​​​​​​‌‌​​​​​​​​​​​​​‌​​​‌‌​​​​​​​​​​​‌‌‌​​‌​​​​​​​​​‌​​​‌‌‌​​​​​​​​​‌​‌​‌‌​​​​​​​​​​‌​​‌​‌‌​​​​​​​​​‌​​​‌​‌​​​​​​​​​‌​‌​​​​​​​​​​​​​​‌‌​‌‌​​​​​​​​​​​‌‌​‌​‌​​​​​​​​​​‌‌​​​​​​​​​​​​​​‌‌​​​‌​​​​​​​​​‌​‌​‌‌​‍
11
+ settings: [
12
+ {
13
+ id: "INSTARAW.Interactive.Header",
14
+ name: "INSTARAW Interactive Nodes",
15
+ type: () => {
16
+ const x = document.createElement('span');
17
+ const a = document.createElement('a');
18
+ a.innerText = "Based on original work by chrisgoringe (cg-image-filter)";
19
+ a.href = "https://github.com/chrisgoringe/cg-image-filter";
20
+ a.target = "_blank";
21
+ a.style.paddingRight = "12px";
22
+ x.appendChild(a);
23
+ return x;
24
+ },
25
+ },
26
+ { id: "INSTARAW.Interactive.PlaySound", name: "Play sound when activating", type: "boolean", defaultValue: true },
27
+ { id: "INSTARAW.Interactive.EnlargeSmall", name: "Enlarge small images in grid", type: "boolean", defaultValue: true },
28
+ { id: "INSTARAW.Interactive.ClickSends", name: "Clicking an image sends it", tooltip: "Use if you always want to send exactly one image.", type: "boolean", defaultValue: false },
29
+ { id: "INSTARAW.Interactive.AutosendIdentical", name: "If all images are identical, autosend one", type: "boolean", defaultValue: false },
30
+ { id: "INSTARAW.Interactive.StartZoomed", name: "Enter the Image Filter node with an image zoomed", type: "combo", options: [{ value: 0, text: "No" }, { value: "1", text: "first" }, { value: "-1", text: "last" }], default: 0 },
31
+ { id: "INSTARAW.Interactive.SmallWindow", name: "Show a small popup instead of covering the screen", type: "boolean", tooltip: "Click the small popup to activate it", defaultValue: false },
32
+ { id: "INSTARAW.Interactive.DetailedLogging", name: "Turn on detailed logging", tooltip: "If you are asked to for debugging!", type: "boolean", defaultValue: false },
33
+ { id: "INSTARAW.Interactive.FPS", name: "Video Frames per Second", type: "int", defaultValue: 5 }
34
+ ],
35
+ setup() {
36
+ create('link', null, document.getElementsByTagName('HEAD')[0], { 'rel': 'stylesheet', 'type': 'text/css', 'href': 'extensions/ComfyUI_INSTARAW/filter.css' });
37
+ create('link', null, document.getElementsByTagName('HEAD')[0], { 'rel': 'stylesheet', 'type': 'text/css', 'href': 'extensions/ComfyUI_INSTARAW/floating_window.css' });
38
+ create('link', null, document.getElementsByTagName('HEAD')[0], { 'rel': 'stylesheet', 'type': 'text/css', 'href': 'extensions/ComfyUI_INSTARAW/zoomed.css' });
39
+ create('link', null, document.getElementsByTagName('HEAD')[0], { 'rel': 'stylesheet', 'type': 'text/css', 'href': 'extensions/ComfyUI_INSTARAW/advanced_image_loader.css' });
40
+ create('link', null, document.getElementsByTagName('HEAD')[0], { 'rel': 'stylesheet', 'type': 'text/css', 'href': 'extensions/ComfyUI_INSTARAW/reality_prompt_generator.css' });
41
+
42
+ api.addEventListener("execution_interrupted", popup.send_cancel.bind(popup));
43
+ api.addEventListener("instaraw-interactive-images", popup.handle_message.bind(popup));
44
+ },
45
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
46
+ if (nodeType.comfyClass === "Pick from List" || nodeType.comfyClass === "INSTARAW_PickFromList") {
47
+ const onConnectionsChange = nodeType.prototype.onConnectionsChange;
48
+ nodeType.prototype.onConnectionsChange = function(side, slot, connect, link_info, output) {
49
+ if (side == 1 && slot == 0 && link_info && connect) {
50
+ const originNode = this.graph.getNodeById(link_info.origin_id);
51
+ if (originNode?.outputs?.[link_info.origin_slot]) {
52
+ const type = originNode.outputs[link_info.origin_slot].type;
53
+ this.outputs[0].type = type;
54
+ this.inputs[0].type = type;
55
+ }
56
+ } else if (side == 1 && slot == 0 && !connect) {
57
+ this.outputs[0].type = "*";
58
+ this.inputs[0].type = "*";
59
+ }
60
+ return onConnectionsChange?.apply(this, arguments);
61
+ }
62
+ }
63
+ if (FILTER_TYPES.includes(nodeType.comfyClass)) {
64
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
65
+ nodeType.prototype.onNodeCreated = function() {
66
+ const onCreatedResult = onNodeCreated?.apply(this, arguments);
67
+
68
+ this._ni_widget = this.widgets.find((n) => n.name == 'node_identifier');
69
+ if (!this._ni_widget) {
70
+ this._ni_widget = ComfyWidgets["INT"](this, "node_identifier", ["INT", { "default": 0 }], app).widget;
71
+ }
72
+ this._ni_widget.hidden = true;
73
+ this._ni_widget.computeSize = () => [0, 0];
74
+ this._ni_widget.value = Math.floor(Math.random() * 1000000);
75
+
76
+ if (this.comfyClass === "INSTARAW_TextImageFilter") {
77
+
78
+ const buttonWidget = this.addWidget("button", "Clear Node Cache", null, async () => {
79
+ buttonWidget.name = "Clearing...";
80
+ this.disabled = true;
81
+
82
+ try {
83
+ const resp = await api.fetchApi('/instaraw/clear_text_filter_cache', {
84
+ method: 'POST',
85
+ headers: { 'Content-Type': 'application/json' },
86
+ body: JSON.stringify({ uid: this._ni_widget.value }),
87
+ });
88
+
89
+ if (resp.status === 200) {
90
+ buttonWidget.success();
91
+ } else {
92
+ throw new Error(await resp.text());
93
+ }
94
+ } catch (e) {
95
+ console.error("INSTARAW: Failed to clear cache:", e);
96
+ buttonWidget.error();
97
+ }
98
+ });
99
+
100
+ buttonWidget.name = "clear_cache_button";
101
+
102
+ buttonWidget.success = () => {
103
+ buttonWidget.name = "Cache Cleared!";
104
+ this.disabled = false;
105
+ setTimeout(() => {
106
+ buttonWidget.name = "Clear Node Cache";
107
+ }, 2000);
108
+ };
109
+
110
+ buttonWidget.error = () => {
111
+ buttonWidget.name = "Error Clearing!";
112
+ this.disabled = false;
113
+ setTimeout(() => {
114
+ buttonWidget.name = "Clear Node Cache";
115
+ }, 3000);
116
+ };
117
+ }
118
+
119
+ return onCreatedResult;
120
+ }
121
+ }
122
+ },
123
+ });
custom_nodes/ComfyUI_INSTARAW/js/instaraw.svg ADDED
custom_nodes/ComfyUI_INSTARAW/js/log.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ---
2
+ // Filename: ../ComfyUI_INSTARAW/js/log.js
3
+ // ---
4
+
5
+ import { app } from "../../scripts/app.js";
6
+
7
+ export class Log {
8
+ static log(s) { if (s) console.log(s) }
9
+ static error(e) { console.error(e) }
10
+ static detail(s) {
11
+ // Updated settings key
12
+ if (app.ui.settings.getSettingValue("INSTARAW.Interactive.DetailedLogging")) Log.log(s)
13
+ }
14
+ static message_in(message, extra) {
15
+ // Updated settings key
16
+ if (!app.ui.settings.getSettingValue("INSTARAW.Interactive.DetailedLogging")) return
17
+ if (message.detail && !message.detail.tick) Log.log(`--> ${JSON.stringify(message.detail)}` + (extra ? ` ${extra}` : ""))
18
+ if (message.detail && message.detail.tick) Log.log(`--> tick`)
19
+ }
20
+ static message_out(response, extra) {
21
+ // Updated settings key
22
+ if (!app.ui.settings.getSettingValue("INSTARAW.Interactive.DetailedLogging")) return
23
+ Log.log(`"<-- ${JSON.stringify(response)}` + (extra ? ` ${extra}` : ""))
24
+ }
25
+ }
custom_nodes/ComfyUI_INSTARAW/js/mask_utils.js ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app, ComfyApp } from "../../scripts/app.js";
2
+
3
+ export function new_editor() {
4
+ return app.ui.settings.getSettingValue('Comfy.MaskEditor.UseNewEditor')
5
+ }
6
+
7
+ function get_mask_editor_element() {
8
+ // Try newer dialog-based mask editor first
9
+ const newer = document.getElementsByClassName('p-dialog-mask')
10
+ if (newer.length == 1) return newer[0]
11
+ return new_editor() ? document.getElementById('maskEditor') : document.getElementById('maskCanvas')?.parentElement
12
+ }
13
+
14
+ export function mask_editor_showing() {
15
+ return get_mask_editor_element() && get_mask_editor_element().style.display != 'none'
16
+ }
17
+
18
+ export function hide_mask_editor() {
19
+ if (mask_editor_showing() && document.getElementById('maskEditor')) document.getElementById('maskEditor').style.display = 'none'
20
+ }
21
+
22
+ function get_mask_editor_cancel_button() {
23
+ var button = document.getElementById("maskEditor_topBarCancelButton")
24
+ if (button) return button
25
+ try {
26
+ button = Array.from(get_mask_editor_element().getElementsByTagName('button')).find((b) => (b.ariaLabel == 'Cancel'))
27
+ if (button) return button
28
+ } catch {}
29
+ return get_mask_editor_element()?.parentElement?.lastChild?.childNodes[2]
30
+ }
31
+
32
+ function get_mask_editor_save_button() {
33
+ var button = document.getElementById("maskEditor_topBarSaveButton")
34
+ if (button) return button
35
+ try {
36
+ button = Array.from(get_mask_editor_element().getElementsByTagName('button')).find((b) => (b.ariaLabel == 'Save'))
37
+ if (button) return button
38
+ } catch {}
39
+ return get_mask_editor_element()?.parentElement?.lastChild?.childNodes[1]
40
+ }
41
+
42
+ export function mask_editor_listen_for_cancel(callback) {
43
+ const cancel_button = get_mask_editor_cancel_button()
44
+ if (cancel_button && !cancel_button.filter_listener_added) {
45
+ cancel_button.addEventListener('click', callback)
46
+ cancel_button.filter_listener_added = true
47
+ }
48
+ }
49
+
50
+ export function press_maskeditor_save() {
51
+ get_mask_editor_save_button()?.click()
52
+ }
53
+
54
+ export function press_maskeditor_cancel() {
55
+ get_mask_editor_cancel_button()?.click()
56
+ }
57
+
58
+ export function open_maskeditor(node) {
59
+ // Try old API first (ComfyApp.open_maskeditor)
60
+ if (typeof ComfyApp.open_maskeditor === 'function') {
61
+ ComfyApp.copyToClipspace(node)
62
+ ComfyApp.clipspace_return_node = node
63
+ ComfyApp.open_maskeditor()
64
+ } else {
65
+ // New API: Use extension command system
66
+ const me_extension = app.extensions.find((e) => (e.name == 'Comfy.MaskEditor'))
67
+ if (me_extension) {
68
+ const me_command = me_extension.commands.find((c) => (c.id == 'Comfy.MaskEditor.OpenMaskEditor'))
69
+ if (me_command) {
70
+ app.canvas.selected_nodes = [node]
71
+ me_command.function()
72
+ } else {
73
+ console.error('[INSTARAW] Could not find MaskEditor command')
74
+ }
75
+ } else {
76
+ console.error('[INSTARAW] Could not find MaskEditor extension')
77
+ }
78
+ }
79
+ }
custom_nodes/ComfyUI_INSTARAW/js/nano_banana_aspect_ratio.js ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ---
2
+ // ComfyUI INSTARAW - Nano Banana Pro Aspect Ratio Visual Preview
3
+ // Copyright © 2025 Instara. All rights reserved.
4
+ // PROPRIETARY SOFTWARE - ALL RIGHTS RESERVED
5
+ // ---
6
+
7
+ import { app } from "../../scripts/app.js";
8
+
9
+ // Aspect ratio numerical values for preview rendering
10
+ const ASPECT_RATIO_VALUES = {
11
+ "1:1 (Square)": { w: 1, h: 1 },
12
+ "3:2 (Landscape)": { w: 3, h: 2 },
13
+ "2:3 (Portrait)": { w: 2, h: 3 },
14
+ "3:4 (Portrait)": { w: 3, h: 4 },
15
+ "4:3 (Landscape)": { w: 4, h: 3 },
16
+ "4:5 (Portrait)": { w: 4, h: 5 },
17
+ "5:4 (Landscape)": { w: 5, h: 4 },
18
+ "9:16 (Tall Portrait)": { w: 9, h: 16 },
19
+ "16:9 (Wide Landscape)": { w: 16, h: 9 },
20
+ "21:9 (Ultrawide)": { w: 21, h: 9 },
21
+ };
22
+
23
+ // Resolution labels with approximate dimensions
24
+ const RESOLUTION_INFO = {
25
+ "1K": "~1024px",
26
+ "2K": "~2048px",
27
+ "4K": "~4096px",
28
+ };
29
+
30
+ app.registerExtension({
31
+ name: "Comfy.INSTARAW.NanoBananaAspectRatio",
32
+
33
+ async beforeRegisterNodeDef(nodeType, nodeData, app) {
34
+ if (nodeData.name !== "INSTARAW_NanoBananaAspectRatio") {
35
+ return;
36
+ }
37
+
38
+ const onNodeCreated = nodeType.prototype.onNodeCreated;
39
+ nodeType.prototype.onNodeCreated = function () {
40
+ onNodeCreated?.apply(this, arguments);
41
+
42
+ // Store reference to node
43
+ const node = this;
44
+
45
+ // Find the aspect_ratio widget
46
+ const aspectWidget = this.widgets.find((w) => w.name === "aspect_ratio");
47
+ const resolutionWidget = this.widgets.find((w) => w.name === "resolution");
48
+
49
+ if (!aspectWidget) return;
50
+
51
+ // Create custom preview widget
52
+ const previewWidget = {
53
+ name: "aspect_preview",
54
+ type: "custom",
55
+ value: null,
56
+ computeSize: function () {
57
+ return [0, 80]; // Fixed height for preview area
58
+ },
59
+ draw: function (ctx, node, width, y) {
60
+ const selectedRatio = aspectWidget.value || "1:1 (Square)";
61
+ const selectedRes = resolutionWidget?.value || "1K";
62
+ const ratio = ASPECT_RATIO_VALUES[selectedRatio] || { w: 1, h: 1 };
63
+
64
+ // Calculate preview box dimensions
65
+ const maxWidth = width - 40;
66
+ const maxHeight = 60;
67
+ let boxWidth, boxHeight;
68
+
69
+ if (ratio.w / ratio.h > maxWidth / maxHeight) {
70
+ // Width constrained
71
+ boxWidth = maxWidth;
72
+ boxHeight = (maxWidth * ratio.h) / ratio.w;
73
+ } else {
74
+ // Height constrained
75
+ boxHeight = maxHeight;
76
+ boxWidth = (maxHeight * ratio.w) / ratio.h;
77
+ }
78
+
79
+ // Center the box
80
+ const boxX = (width - boxWidth) / 2;
81
+ const boxY = y + (80 - boxHeight) / 2;
82
+
83
+ // Draw background
84
+ ctx.fillStyle = "#1a1a2e";
85
+ ctx.fillRect(10, y + 5, width - 20, 70);
86
+
87
+ // Draw aspect ratio preview box
88
+ ctx.fillStyle = "#4a90d9";
89
+ ctx.fillRect(boxX, boxY, boxWidth, boxHeight);
90
+
91
+ // Draw border
92
+ ctx.strokeStyle = "#6ab0ff";
93
+ ctx.lineWidth = 2;
94
+ ctx.strokeRect(boxX, boxY, boxWidth, boxHeight);
95
+
96
+ // Draw ratio text below the preview box
97
+ const ratioText = selectedRatio.split(" ")[0]; // e.g., "16:9"
98
+ const resInfo = RESOLUTION_INFO[selectedRes] || "";
99
+ ctx.fillStyle = "#ffffff";
100
+ ctx.font = "bold 11px Arial";
101
+ ctx.textAlign = "center";
102
+ ctx.textBaseline = "top";
103
+ ctx.fillText(`${ratioText} • ${selectedRes} (${resInfo})`, width / 2, boxY + boxHeight + 6);
104
+ },
105
+ };
106
+
107
+ // Add preview widget after resolution
108
+ this.addCustomWidget(previewWidget);
109
+
110
+ // Update preview when aspect ratio changes
111
+ const originalAspectCallback = aspectWidget.callback;
112
+ aspectWidget.callback = (value) => {
113
+ originalAspectCallback?.(value);
114
+ node.setDirtyCanvas(true, true);
115
+ };
116
+
117
+ // Update preview when resolution changes
118
+ if (resolutionWidget) {
119
+ const originalResCallback = resolutionWidget.callback;
120
+ resolutionWidget.callback = (value) => {
121
+ originalResCallback?.(value);
122
+ node.setDirtyCanvas(true, true);
123
+ };
124
+ }
125
+
126
+ // Ensure proper sizing
127
+ this.computeSize();
128
+ };
129
+ },
130
+ });
custom_nodes/ComfyUI_INSTARAW/js/popup.js ADDED
@@ -0,0 +1,1238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { app, ComfyApp } from '../../scripts/app.js';
2
+ import { api } from '../../scripts/api.js';
3
+
4
+ import {
5
+ mask_editor_listen_for_cancel,
6
+ mask_editor_showing,
7
+ hide_mask_editor,
8
+ press_maskeditor_cancel,
9
+ press_maskeditor_save,
10
+ new_editor,
11
+ open_maskeditor
12
+ } from './mask_utils.js';
13
+ import { Log } from './log.js';
14
+ import { create } from './utils.js';
15
+ import { FloatingWindow } from './floating_window.js';
16
+
17
+ // Renamed to INSTARAW node class names
18
+ const POPUP_NODES = ['INSTARAW_ImageFilter', 'INSTARAW_TextImageFilter', 'INSTARAW_Interactive_Crop'];
19
+ const MASK_NODES = ['INSTARAW_MaskImageFilter'];
20
+
21
+ const REQUEST_RESHOW = '-1';
22
+ const CANCEL = '-3';
23
+
24
+ const GRID_IMAGE_SPACE = 10;
25
+
26
+ function get_full_url(url) {
27
+ return api.apiURL(
28
+ `/view?filename=${encodeURIComponent(url.filename ?? v)}&type=${url.type ?? 'input'}&subfolder=${url.subfolder ?? ''}&r=${Math.random()}`
29
+ );
30
+ }
31
+
32
+ const State = Object.freeze({
33
+ INACTIVE: 0,
34
+ TINY: 1,
35
+ MASK: 2,
36
+ FILTER: 3,
37
+ TEXT: 4,
38
+ ZOOMED: 5,
39
+ CROP: 6 // Our new state for the interactive cropper
40
+ });
41
+
42
+ class Popup extends HTMLElement {
43
+ constructor() {
44
+ super();
45
+ // Path updated to point to our package's assets
46
+ this.audio = new Audio('extensions/ComfyUI_INSTARAW/ding.mp3');
47
+
48
+ this.classList.add('instaraw_popup');
49
+
50
+ this.grid = create('span', 'grid', this);
51
+ this.overlaygrid = create('span', 'grid overlaygrid', this);
52
+ this.grid.addEventListener('click', this.on_click.bind(this));
53
+
54
+ // --- NEW CROP UI ELEMENTS ---
55
+ this.crop_container = create('div', 'crop_container', this);
56
+ this.crop_image_bg = create('img', 'crop_image_bg', this.crop_container);
57
+ this.crop_canvas = create('canvas', 'crop_canvas_overlay', this.crop_container);
58
+ this.crop_ctx = this.crop_canvas.getContext('2d');
59
+ this.crop_canvas.addEventListener('mousedown', this.on_crop_mouse_down.bind(this));
60
+ // We now listen for moves on the canvas for cursor updates, and the whole document for dragging
61
+ this.crop_canvas.addEventListener('mousemove', this.on_crop_mouse_move.bind(this));
62
+ document.addEventListener('mouseup', this.on_crop_mouse_up.bind(this));
63
+ document.addEventListener('mousemove', this.on_document_mouse_move.bind(this));
64
+ this.crop_data = { active: false, box: null, mode: null };
65
+ // --- END NEW CROP UI ---
66
+
67
+ this.zoomed = create('span', 'zoomed', this);
68
+ this.zoomed_prev = create('span', 'zoomed_prev', this.zoomed);
69
+ this.zoomed_prev_arrow = create('span', 'zoomed_arrow', this.zoomed_prev, { innerHTML: '&#x21E6' });
70
+ this.zoomed_image = create('img', 'zoomed_image', this.zoomed);
71
+ this.zoomed_next = create('span', 'zoomed_next', this.zoomed);
72
+ this.zoomed_number = create('span', 'zoomed_number', this.zoomed_next);
73
+ this.zoomed_next_arrow = create('span', 'zoomed_arrow', this.zoomed_next, { innerHTML: '&#x21E8' });
74
+
75
+ this.zoomed_prev_arrow.addEventListener('click', this.zoom_prev.bind(this));
76
+ this.zoomed_next_arrow.addEventListener('click', this.zoom_next.bind(this));
77
+ this.zoomed_image.addEventListener('click', this.click_zoomed.bind(this));
78
+
79
+ this.tiny_window = new FloatingWindow('', 100, 100, null, this.tiny_moved.bind(this));
80
+ this.tiny_window.classList.add('tiny');
81
+ this.tiny_image = create('img', 'tiny_image', this.tiny_window.body);
82
+ this.tiny_window.addEventListener('click', this.handle_deferred_message.bind(this));
83
+
84
+ this.floating_window = new FloatingWindow('', 100, 100, null, this.floater_moved.bind(this));
85
+
86
+ this.counter_row = create('span', 'counter row', this.floating_window.body);
87
+ this.counter_reset_button = create('button', 'counter_reset', this.counter_row, { innerText: 'Reset' });
88
+ this.counter_text = create('span', 'counter_text', this.counter_row);
89
+ this.counter_reset_button.addEventListener('click', this.request_reset.bind(this));
90
+
91
+ this.extras_row = create('span', 'extras row', this.floating_window.body);
92
+
93
+ this.tip_row = create('span', 'tip row', this.floating_window.body);
94
+
95
+ this.button_row = create('span', 'buttons row', this.floating_window.body);
96
+ this.send_button = create('button', 'control', this.button_row, { innerText: 'Send' });
97
+ this.cancel_button = create('button', 'control', this.button_row, { innerText: 'Cancel' });
98
+ this.toggle_button = create('button', 'control', this.button_row, { innerText: 'Hide' });
99
+ this.send_button.addEventListener('click', this.send_current_state.bind(this));
100
+ this.cancel_button.addEventListener('click', this.send_cancel.bind(this));
101
+ this.toggle_button.addEventListener('click', this.toggleHide.bind(this));
102
+
103
+ this.mask_button_row = create('span', 'buttons row', this.floating_window.body);
104
+ this.mask_send_button = create('button', 'control', this.mask_button_row, { innerText: 'Send' });
105
+ this.mask_cancel_button = create('button', 'control', this.mask_button_row, { innerText: 'Cancel' });
106
+ this.mask_send_button.addEventListener('click', press_maskeditor_save);
107
+ this.mask_cancel_button.addEventListener('click', press_maskeditor_cancel);
108
+
109
+ this.text_edit = create('textarea', 'text_edit row', this.floating_window.body);
110
+
111
+ this.picked = new Set();
112
+
113
+ document.addEventListener('keydown', this.on_key_down.bind(this));
114
+ document.addEventListener('keypress', this.on_key_press.bind(this));
115
+
116
+ document.body.appendChild(this);
117
+ this.last_response_sent = 0;
118
+ this.state = State.INACTIVE;
119
+ this.hidden_by_toggle = false;
120
+ this.render();
121
+ }
122
+
123
+ toggleHide() {
124
+ this.hidden_by_toggle = !this.hidden_by_toggle;
125
+ this.render();
126
+ }
127
+
128
+ floater_moved(x, y) {
129
+ if (this.node?.properties) {
130
+ this.node.properties['filter_floater_xy'] = { x: x, y: y };
131
+ }
132
+ }
133
+
134
+ floater_position() {
135
+ return this.node?.properties?.['filter_floater_xy'];
136
+ }
137
+
138
+ tiny_moved(x, y) {
139
+ if (this.node?.properties) {
140
+ this.node.properties['filter_tiny_xy'] = { x: x, y: y };
141
+ }
142
+ }
143
+
144
+ tiny_position() {
145
+ return this.node?.properties?.['filter_tiny_xy'];
146
+ }
147
+
148
+ visible(item, value) {
149
+ if (value) item.classList.remove('hidden');
150
+ else item.classList.add('hidden');
151
+ }
152
+ disabled(item, value) {
153
+ item.disabled = value;
154
+ }
155
+ highlighted(item, value) {
156
+ if (value) item.classList.add('highlighted');
157
+ else item.classList.remove('highlighted');
158
+ }
159
+
160
+ render() {
161
+ const state = this.state;
162
+ // --- UPDATED RENDER LOGIC ---
163
+ const isVisible =
164
+ (state === State.FILTER || state === State.TEXT || state === State.ZOOMED || state === State.CROP) &&
165
+ !this.hidden_by_toggle;
166
+ this.visible(this, isVisible);
167
+
168
+ this.visible(this.tiny_window, state === State.TINY);
169
+ this.visible(this.zoomed, state === State.ZOOMED);
170
+
171
+ // Hide grid when in CROP mode to prevent bleed-through from previous filter images
172
+ this.visible(this.grid, state === State.FILTER || state === State.ZOOMED || state === State.TEXT);
173
+ this.visible(this.overlaygrid, state === State.FILTER || state === State.ZOOMED || state === State.TEXT);
174
+
175
+ this.visible(this.crop_container, state === State.CROP);
176
+
177
+ const showFloater =
178
+ state === State.FILTER ||
179
+ state === State.ZOOMED ||
180
+ state === State.TEXT ||
181
+ state === State.MASK ||
182
+ state === State.CROP;
183
+ this.visible(this.floating_window, showFloater);
184
+
185
+ this.visible(this.button_row, state !== State.MASK);
186
+
187
+ // Disable send button if no images picked (filter) or no crop box drawn (crop)
188
+ const sendDisabled =
189
+ ((state === State.FILTER || state === State.ZOOMED) && this.picked.size === 0) ||
190
+ (state === State.CROP && (!this.crop_data.box || this.crop_data.box.width <= 0 || this.crop_data.box.height <= 0));
191
+ this.disabled(this.send_button, sendDisabled);
192
+
193
+ this.visible(this.mask_button_row, state === State.MASK && new_editor());
194
+ this.visible(this.extras_row, this.n_extras > 0);
195
+ this.visible(this.tip_row, this.tip_row.innerHTML.length > 0);
196
+ this.visible(this.text_edit, state === State.TEXT);
197
+
198
+ if (state === State.ZOOMED) {
199
+ const img_index = this.zoomed_image_holder.image_index;
200
+ this.highlighted(this.zoomed, this.picked.has(`${img_index}`));
201
+ this.zoomed_number.innerHTML = `${img_index + 1}/${this.n_images}`;
202
+ }
203
+
204
+ if (state !== State.MASK) hide_mask_editor();
205
+
206
+ this.toggle_button.innerText = this.hidden_by_toggle ? 'Show' : 'Hide';
207
+ const showToggleButton =
208
+ state === State.FILTER || state === State.ZOOMED || state === State.TEXT || state === State.CROP;
209
+ this.visible(this.toggle_button, showToggleButton);
210
+ }
211
+
212
+ _send_response(msg = {}, keep_open = false) {
213
+ if (Date.now() - this.last_response_sent < 1000) {
214
+ Log.message_out(msg, '(throttled)');
215
+ return;
216
+ }
217
+ const unique = this.node?._ni_widget?.value;
218
+ if (!unique) {
219
+ if (this.node) Log.error(`Node ${this.node.id} has no _ni_widget when trying to send ${msg}`);
220
+ else Log.error(`No node when trying to send ${msg}`);
221
+ return;
222
+ }
223
+ msg.unique = `${unique}`;
224
+ if (!msg.special) {
225
+ if (this.n_extras > 0) {
226
+ msg.extras = Array.from(this.extras_row.children).map((e) => e.value);
227
+ }
228
+ if (this.state === State.FILTER || this.state === State.ZOOMED) msg.selection = Array.from(this.picked);
229
+ if (this.state === State.TEXT) msg.text = this.text_edit.value;
230
+ // --- NEW: Add crop data to the response ---
231
+ if (this.state === State.CROP && this.crop_data.box) {
232
+ // Convert from canvas-space to original image-space before sending
233
+ const { box, scale } = this.crop_data;
234
+ // Send 'crop' at the top level, not nested under 'response'
235
+ msg.crop = {
236
+ x: Math.round(box.x / scale),
237
+ y: Math.round(box.y / scale),
238
+ width: Math.round(box.width / scale),
239
+ height: Math.round(box.height / scale)
240
+ };
241
+ }
242
+ this.last_response_sent = Date.now();
243
+ }
244
+ try {
245
+ const body = new FormData();
246
+ body.append('response', JSON.stringify(msg));
247
+ api.fetchApi('/instaraw/interactive_message', { method: 'POST', body });
248
+ Log.message_out(msg);
249
+ } catch (e) {
250
+ Log.error(e);
251
+ } finally {
252
+ if (!keep_open) this.close();
253
+ }
254
+ }
255
+
256
+ send_current_state() {
257
+ if (this.state == State.TEXT) {
258
+ this._send_response();
259
+ } else {
260
+ this._send_response();
261
+ }
262
+ }
263
+
264
+ send_cancel() {
265
+ // Only send cancel if we're in an active state that's waiting for response
266
+ if (this.state !== State.INACTIVE && this.state !== State.TINY) {
267
+ this._send_response({ special: CANCEL });
268
+ }
269
+ }
270
+
271
+ request_reset() {
272
+ this._send_response({ special: REQUEST_RESHOW }, true);
273
+ }
274
+
275
+ close() {
276
+ this.state = State.INACTIVE;
277
+ this.render();
278
+ }
279
+
280
+ maybe_play_sound() {
281
+ // Updated settings key
282
+ if (app.ui.settings.getSettingValue('INSTARAW.Interactive.PlaySound')) this.audio.play();
283
+ }
284
+
285
+ handle_message(message) {
286
+ Log.message_in(message);
287
+ Log.log(this._handle_message(message, false));
288
+ this.render();
289
+ }
290
+
291
+ handle_deferred_message(e) {
292
+ Log.message_in(this.saved_message, '(deferred)');
293
+ Log.log(this._handle_message(this.saved_message, true));
294
+ this.render();
295
+ }
296
+
297
+ autosend() {
298
+ // Updated settings key
299
+ return app.ui.settings.getSettingValue('INSTARAW.Interactive.AutosendIdentical') && this.allsame;
300
+ }
301
+
302
+ on_new_node(nd) {
303
+ this.node = nd;
304
+ const fp = this.floater_position();
305
+ if (fp) this.floating_window.move_to(fp.x, fp.y, true);
306
+ const tp = this.tiny_position();
307
+ if (tp) this.tiny_window.move_to(tp.x, tp.y, true);
308
+ }
309
+
310
+ find_node(uid) {
311
+ const bits = uid.split(':');
312
+ if (bits.length == 1) {
313
+ return app.graph._nodes_by_id[uid];
314
+ } else {
315
+ var graph = app.graph;
316
+ var node;
317
+ bits.forEach((bit) => {
318
+ node = graph._nodes_by_id[bit];
319
+ graph = node.subgraph;
320
+ });
321
+ }
322
+ return node;
323
+ }
324
+
325
+ _handle_message(message, using_saved) {
326
+ const detail = message.detail;
327
+ const uid = detail.uid;
328
+ const the_node = this.find_node(uid);
329
+
330
+ if (this.node != the_node) this.on_new_node(the_node);
331
+
332
+ if (!this.node) return console.log(`Message was for ${uid} which doesn't exist`);
333
+ if (this.node._ni_widget?.value != message.detail.unique) return console.log(`Message unique id wasn't mine`);
334
+
335
+ if (detail.tick) {
336
+ this.counter_text.innerText = `${detail.tick}s`;
337
+ if (this.state == State.INACTIVE) this.request_reset();
338
+ return;
339
+ }
340
+
341
+ if (detail.timeout) {
342
+ this.close();
343
+ return `Timeout`;
344
+ }
345
+
346
+ if (this.handling_message) return `Ignoring message because we're already handling a message`;
347
+
348
+ this.set_title(this.node.title ?? 'INSTARAW Image Filter'); // Updated default title
349
+ this.allsame = detail.allsame || false;
350
+ if (detail.tip) this.tip_row.innerHTML = detail.tip.replace(/(?:\r\n|\r|\n)/g, '<br/>');
351
+ else this.tip_row.innerHTML = '';
352
+
353
+ // Updated settings key
354
+ if (
355
+ this.state == State.INACTIVE &&
356
+ app.ui.settings.getSettingValue('INSTARAW.Interactive.SmallWindow') &&
357
+ !using_saved &&
358
+ !this.autosend()
359
+ ) {
360
+ this.state = State.TINY;
361
+ this.saved_message = message;
362
+ this.tiny_image.src = get_full_url(message.detail.urls[message.detail.urls.length - 1]);
363
+ this.maybe_play_sound();
364
+ return `Deferring message and showing small window`;
365
+ }
366
+
367
+ try {
368
+ this.handling_message = true;
369
+ this.n_extras = detail.extras ? message.detail.extras.length : 0;
370
+ this.extras_row.innerHTML = '';
371
+ for (let i = 0; i < this.n_extras; i++) {
372
+ create('input', 'extra', this.extras_row, { value: detail.extras[i] });
373
+ }
374
+
375
+ if (!using_saved && !this.autosend()) this.maybe_play_sound();
376
+
377
+ if (detail.interactive_crop) {
378
+ this.handle_crop(detail);
379
+ } else if (detail.maskedit) {
380
+ // existing logic
381
+ this.handle_maskedit(detail);
382
+ } else if (detail.urls) {
383
+ // existing logic
384
+ this.handle_urls(detail);
385
+ }
386
+ } finally {
387
+ this.handling_message = false;
388
+ }
389
+ }
390
+
391
+ window_not_showing(uid) {
392
+ const node = this.find_node(uid);
393
+ return (
394
+ (POPUP_NODES.includes(node.type) && this.classList.contains('hidden')) ||
395
+ (MASK_NODES.includes(node.type) && !mask_editor_showing())
396
+ );
397
+ }
398
+
399
+ set_title(title) {
400
+ this.floating_window.set_title(title);
401
+ var pos = this.floater_position();
402
+ if (pos) this.floating_window.move_to(pos.x, pos.y);
403
+ pos = this.tiny_position();
404
+ if (pos) this.tiny_window.move_to(pos.x, pos.y);
405
+ this.tiny_window.set_title(title);
406
+ }
407
+
408
+ handle_maskedit(detail) {
409
+ // Reset state from any previous mode
410
+ this.grid.innerHTML = '';
411
+ this.overlaygrid.innerHTML = '';
412
+ this.crop_data = { active: false, box: null, mode: null, scale: 1 };
413
+
414
+ if (!this.node) {
415
+ Log.log(`No node to handle maskedit - maybe it's been removed`);
416
+ this.seen_editor = true;
417
+ } else {
418
+ this.state = State.MASK;
419
+
420
+ this.node.imgs = [];
421
+ this.node.images = [];
422
+ detail.urls.forEach((url, i) => {
423
+ this.node.imgs.push(new Image());
424
+ this.node.imgs[i].src = api.apiURL(
425
+ `/view?filename=${encodeURIComponent(url.filename)}&type=${url.type}&subfolder=${url.subfolder}`
426
+ );
427
+ this.node.images.push(url);
428
+ });
429
+ this.node.imageIndex = 0;
430
+ open_maskeditor(this.node);
431
+ this.seen_editor = false;
432
+ }
433
+ setTimeout(this.wait_while_mask_editing.bind(this), 200);
434
+ }
435
+
436
+ wait_while_mask_editing() {
437
+ if (!this.seen_editor && mask_editor_showing()) {
438
+ mask_editor_listen_for_cancel(this.send_cancel.bind(this));
439
+ this.render();
440
+ this.seen_editor = true;
441
+ }
442
+
443
+ if (mask_editor_showing()) {
444
+ setTimeout(this.wait_while_mask_editing.bind(this), 100);
445
+ } else {
446
+ // Handle both file-based (old) and data URL (new) mask editor formats
447
+ const masked_image = this.extract_filename(this.node.imgs[0].src);
448
+ if (masked_image) {
449
+ this._send_response({ masked_image: masked_image });
450
+ } else {
451
+ // New mask editor stores as data URL
452
+ this._send_response({ masked_data: this.node.imgs[0].src });
453
+ }
454
+ }
455
+ }
456
+
457
+ extract_filename(url_string) {
458
+ return new URL(url_string).searchParams.get('filename');
459
+ }
460
+
461
+ handle_urls(detail) {
462
+ // Reset crop state from any previous mode
463
+ this.crop_data = { active: false, box: null, mode: null, scale: 1 };
464
+
465
+ this.video_frames = detail.video_frames || 1;
466
+
467
+ if (this.autosend()) {
468
+ return this._send_response({ selection: [0] });
469
+ }
470
+
471
+ this.autozoom_pending = false;
472
+ if (detail.text != null) {
473
+ this.state = State.TEXT;
474
+ this.text_edit.value = detail.text;
475
+ if (detail.textareaheight) this.text_edit.style.height = `${detail.textareaheight}px`;
476
+ } else {
477
+ // Updated settings key
478
+ if (
479
+ this.state != State.FILTER &&
480
+ this.state != State.ZOOMED &&
481
+ app.ui.settings.getSettingValue('INSTARAW.Interactive.StartZoomed') != 0
482
+ ) {
483
+ this.autozoom_pending = true;
484
+ }
485
+ this.state = State.FILTER;
486
+ }
487
+
488
+ this.n_images = detail.urls?.length;
489
+ this.laidOut = -1;
490
+ this.picked = new Set();
491
+ if (this.n_images == 1) this.picked.add('0');
492
+
493
+ this.grid.innerHTML = '';
494
+ this.overlaygrid.innerHTML = '';
495
+ var latestImage = null;
496
+
497
+ detail.urls.forEach((url, i) => {
498
+ console.log(url);
499
+ if (i % this.video_frames == 0) {
500
+ const thisImage = create('img', null, this.grid, { src: get_full_url(url) });
501
+ latestImage = thisImage;
502
+ latestImage.onload = this.layout.bind(this);
503
+ latestImage.image_index = i / this.video_frames;
504
+ latestImage.addEventListener('mouseover', (e) => this.on_mouse_enter(thisImage));
505
+ latestImage.addEventListener('mouseout', (e) => this.on_mouse_out(thisImage));
506
+ latestImage.frames = [get_full_url(url)];
507
+ } else {
508
+ latestImage.frames.push(get_full_url(url));
509
+ }
510
+ if (detail.mask_urls) {
511
+ create('img', null, this.overlaygrid, { src: get_full_url(detail.mask_urls[i]) });
512
+ }
513
+ });
514
+
515
+ this.layout();
516
+
517
+ if (this.video_frames > 1) {
518
+ this.frame = 0;
519
+ setTimeout(this.advance_videos.bind(this), 1000);
520
+ }
521
+ }
522
+
523
+ advance_videos() {
524
+ if (this.state == State.INACTIVE) return;
525
+
526
+ this.frame = (this.frame + 1) % this.video_frames;
527
+ Array.from(this.grid.children).forEach((img) => {
528
+ img.src = img.frames[this.frame];
529
+ });
530
+
531
+ // Updated settings key
532
+ const fps = app.ui.settings.getSettingValue('INSTARAW.Interactive.FPS');
533
+ const delay = fps > 0 ? 1000 / fps : 1000;
534
+ setTimeout(this.advance_videos.bind(this), delay);
535
+ }
536
+
537
+ on_mouse_enter(img) {
538
+ this.mouse_is_over = img;
539
+ this.redraw();
540
+ }
541
+
542
+ on_mouse_out(img) {
543
+ this.mouse_is_over = null;
544
+ this.redraw();
545
+ }
546
+
547
+ zoom_auto() {
548
+ this.autozoom_pending = false;
549
+ // Updated settings key
550
+ const startZoomed = app.ui.settings.getSettingValue('INSTARAW.Interactive.StartZoomed');
551
+ if (startZoomed == 1) {
552
+ this.zoomed_image_holder = this.grid.firstChild;
553
+ } else if (startZoomed == -1) {
554
+ this.zoomed_image_holder = this.grid.lastChild;
555
+ } else {
556
+ return;
557
+ }
558
+ if (this.zoomed_image_holder.image_index >= 0) {
559
+ this.state = State.ZOOMED;
560
+ return this.show_zoomed();
561
+ }
562
+ }
563
+ zoom_next() {
564
+ this.zoomed_image_holder = this.zoomed_image_holder.nextSibling || this.zoomed_image_holder.parentNode.firstChild;
565
+ this.show_zoomed();
566
+ }
567
+ zoom_prev() {
568
+ this.zoomed_image_holder =
569
+ this.zoomed_image_holder.previousSibling || this.zoomed_image_holder.parentNode.lastChild;
570
+ this.show_zoomed();
571
+ }
572
+ click_zoomed() {
573
+ const fake_event = { target: this.zoomed_image_holder };
574
+ this.on_click(fake_event);
575
+ this.show_zoomed();
576
+ }
577
+ show_zoomed() {
578
+ this.zoomed_image.src = this.zoomed_image_holder.src;
579
+ return this.render();
580
+ }
581
+ eat_event(e) {
582
+ e.stopPropagation();
583
+ e.preventDefault();
584
+ }
585
+
586
+ on_key_press(e) {
587
+ if (document.activeElement?.type == 'text' || document.activeElement?.type == 'textarea') {
588
+ if (this.floating_window.contains(document.activeElement) || this.contains(document.activeElement)) return;
589
+ }
590
+ if (this.state != State.INACTIVE && this.state != State.TINY) {
591
+ this.eat_event(e);
592
+ }
593
+ }
594
+
595
+ on_key_down(e) {
596
+ if (document.activeElement?.type == 'text' || document.activeElement?.type == 'textarea') {
597
+ if (this.floating_window.contains(document.activeElement) || this.contains(document.activeElement)) return;
598
+ if (this.state == State.INACTIVE && this.state == State.TINY) return;
599
+ }
600
+ if (this.state == State.FILTER || this.state == State.TEXT) {
601
+ if (e.key == 'Enter') {
602
+ this.send_current_state();
603
+ return this.eat_event(e);
604
+ }
605
+ if (e.key == 'Escape') {
606
+ this.send_cancel();
607
+ return this.eat_event(e);
608
+ }
609
+ if (`${parseInt(e.key)}` == e.key) {
610
+ this.select_unselect(parseInt(e.key));
611
+ this.render();
612
+ return this.eat_event(e);
613
+ }
614
+ }
615
+
616
+ if (this.state == State.FILTER) {
617
+ if (e.key == ' ' && this.mouse_is_over) {
618
+ this.state = State.ZOOMED;
619
+ this.zoomed_image_holder = this.mouse_is_over;
620
+ this.eat_event(e);
621
+ return this.show_zoomed();
622
+ }
623
+ if (e.key == 'a' && e.ctrlKey) {
624
+ if (this.picked.size > this.n_images / 2) {
625
+ this.picked.clear();
626
+ console.log('unselect all');
627
+ } else {
628
+ this.picked.clear();
629
+ for (var i = 0; i < this.n_images; i++) {
630
+ this.picked.add(`${i}`);
631
+ }
632
+ console.log('select all');
633
+ }
634
+ this.eat_event(e);
635
+ return this.redraw();
636
+ }
637
+ }
638
+
639
+ if (this.state == State.ZOOMED) {
640
+ if (e.key == ' ') {
641
+ this.state = State.FILTER;
642
+ this.zoomed_image_holder = null;
643
+ this.eat_event(e);
644
+ return this.render();
645
+ } else if (e.key == 'ArrowUp') {
646
+ this.click_zoomed();
647
+ return this.eat_event(e);
648
+ } else if (e.key == 'ArrowDown') {
649
+ // select or unselect
650
+ } else if (e.key == 'ArrowRight') {
651
+ this.zoom_next();
652
+ return this.eat_event(e);
653
+ } else if (e.key == 'ArrowLeft') {
654
+ this.zoom_prev();
655
+ return this.eat_event(e);
656
+ }
657
+ }
658
+ }
659
+
660
+ select_unselect(n) {
661
+ if (n < 0 || n > this.n_images) {
662
+ return;
663
+ }
664
+ const s = `${n}`;
665
+ // Updated settings key
666
+ if (app.ui.settings.getSettingValue('INSTARAW.Interactive.ClickSends')) {
667
+ this.picked.add(s);
668
+ this._send_response();
669
+ } else {
670
+ if (this.picked.has(s)) this.picked.delete(s);
671
+ else this.picked.add(s);
672
+ this.redraw();
673
+ }
674
+ }
675
+
676
+ on_click(e) {
677
+ if (e.target.image_index != undefined) {
678
+ this.select_unselect(e.target.image_index);
679
+ }
680
+ }
681
+
682
+ layout(norepeat) {
683
+ const box = this.grid.getBoundingClientRect();
684
+ if (this.laidOut == box.width) return;
685
+
686
+ const im_w = this.grid.firstChild.naturalWidth;
687
+ const im_h = this.grid.firstChild.naturalHeight;
688
+
689
+ if (!im_w || !im_h || !box.width || !box.height) {
690
+ if (!norepeat) setTimeout(this.layout.bind(this), 100, [true]);
691
+ return;
692
+ } else {
693
+ var best_scale = 0;
694
+ var best_pick;
695
+ var per_row;
696
+ for (per_row = 1; per_row <= this.n_images; per_row++) {
697
+ const rows = Math.ceil(this.n_images / per_row);
698
+ const scale = Math.min(box.width / (im_w * per_row), box.height / (im_h * rows));
699
+ if (scale > best_scale) {
700
+ best_scale = scale;
701
+ best_pick = per_row;
702
+ }
703
+ }
704
+ this.per_row = best_pick;
705
+ this.laidOut = box.width;
706
+ }
707
+
708
+ this.rows = Math.ceil(this.n_images / this.per_row);
709
+ const w = box.width / this.per_row - GRID_IMAGE_SPACE;
710
+ const h = box.height / this.rows - GRID_IMAGE_SPACE;
711
+
712
+ var template_columns = '';
713
+ for (let i = 0; i < this.per_row; i++) template_columns += ` ${w + GRID_IMAGE_SPACE}px`;
714
+ var template_rows = '';
715
+ for (let i = 0; i < this.rows; i++) template_rows += ` ${h + GRID_IMAGE_SPACE}px`;
716
+ this.grid.style.gridTemplateColumns = template_columns;
717
+ this.grid.style.gridTemplateRows = template_rows;
718
+ this.overlaygrid.style.gridTemplateColumns = template_columns;
719
+ this.overlaygrid.style.gridTemplateRows = template_rows;
720
+
721
+ Array.from(this.grid.children).forEach((c, i) => {
722
+ c.style.gridArea = `${Math.floor(i / this.per_row) + 1} / ${(i % this.per_row) + 1} / auto / auto`;
723
+ });
724
+ Array.from(this.overlaygrid.children).forEach((c, i) => {
725
+ c.style.gridArea = `${Math.floor(i / this.per_row) + 1} / ${(i % this.per_row) + 1} / auto / auto`;
726
+ });
727
+
728
+ this.redraw();
729
+ setTimeout(this.rescale_images.bind(this), 100);
730
+
731
+ if (this.autozoom_pending) {
732
+ this.zoom_auto();
733
+ }
734
+ }
735
+
736
+ rescale_images() {
737
+ const box = this.grid.getBoundingClientRect();
738
+ const sub = this.grid.firstChild.getBoundingClientRect();
739
+ const w_used = ((sub.width + GRID_IMAGE_SPACE) * this.per_row) / box.width;
740
+ const h_used = ((sub.height + GRID_IMAGE_SPACE) * this.rows) / box.height;
741
+ const could_zoom = 1.0 / Math.max(w_used, h_used);
742
+ // Updated settings key
743
+ if (could_zoom > 1 && app.ui.settings.getSettingValue('INSTARAW.Interactive.EnlargeSmall')) {
744
+ Array.from(this.grid.children).forEach((img) => {
745
+ img.style.width = `${sub.width * could_zoom}px`;
746
+ });
747
+ Array.from(this.overlaygrid.children).forEach((img) => {
748
+ img.style.width = `${sub.width * could_zoom}px`;
749
+ });
750
+ }
751
+ }
752
+
753
+ redraw() {
754
+ Array.from(this.grid.children).forEach((c, i) => {
755
+ if (this.picked.has(`${i}`)) c.classList.add('selected');
756
+ else c.classList.remove('selected');
757
+
758
+ if (c == this.mouse_is_over) c.classList.add('hover');
759
+ else c.classList.remove('hover');
760
+ });
761
+ }
762
+
763
+ handle_crop(detail) {
764
+ // Reset state from any previous mode
765
+ this.grid.innerHTML = '';
766
+ this.overlaygrid.innerHTML = '';
767
+ this.crop_data = { active: false, box: null, mode: null, scale: 1, aspectRatio: null };
768
+
769
+ // Store locked aspect ratio if provided
770
+ if (detail.lock_aspect_ratio) {
771
+ this.crop_data.aspectRatio = detail.lock_aspect_ratio.width / detail.lock_aspect_ratio.height;
772
+ this.crop_data.aspectRatioLabel = detail.lock_aspect_ratio.label;
773
+ console.log(`🔒 Crop aspect ratio locked to ${detail.lock_aspect_ratio.label} (${this.crop_data.aspectRatio.toFixed(3)})`);
774
+ }
775
+
776
+ this.state = State.CROP;
777
+ this.crop_image_bg.onload = () => {
778
+ const img = this.crop_image_bg;
779
+ const container = this.crop_container;
780
+ const containerRect = container.getBoundingClientRect();
781
+
782
+ // Calculate the scaled (displayed) size of the image to fit the container
783
+ const imgAspect = img.naturalWidth / img.naturalHeight;
784
+ const containerAspect = containerRect.width / containerRect.height;
785
+
786
+ let displayWidth, displayHeight;
787
+ if (imgAspect > containerAspect) {
788
+ displayWidth = containerRect.width;
789
+ displayHeight = displayWidth / imgAspect;
790
+ } else {
791
+ displayHeight = containerRect.height;
792
+ displayWidth = displayHeight * imgAspect;
793
+ }
794
+
795
+ // Position and size the canvas overlay to perfectly match the displayed image
796
+ this.crop_canvas.width = displayWidth;
797
+ this.crop_canvas.height = displayHeight;
798
+ this.crop_canvas.style.left = `${(containerRect.width - displayWidth) / 2}px`;
799
+ this.crop_canvas.style.top = `${(containerRect.height - displayHeight) / 2}px`;
800
+
801
+ // Store the scaling factor. This is crucial for converting UI coordinates back to original image coordinates.
802
+ this.crop_data.scale = displayWidth / img.naturalWidth;
803
+
804
+ // If a proposed crop was sent from the backend, use it to initialize the crop box.
805
+ if (detail.proposed_crop) {
806
+ const proposal = detail.proposed_crop;
807
+ const scale = this.crop_data.scale;
808
+
809
+ // Convert the proposed crop (in original image coordinates) to canvas coordinates
810
+ this.crop_data.box = {
811
+ x: proposal.x * scale,
812
+ y: proposal.y * scale,
813
+ width: proposal.width * scale,
814
+ height: proposal.height * scale
815
+ };
816
+ } else {
817
+ // If there's no proposal, start with no crop box.
818
+ this.crop_data.box = null;
819
+ }
820
+
821
+ // Initial draw of the UI
822
+ this.draw_crop_box();
823
+
824
+ // Update the main render loop to enable/disable the 'Send' button correctly
825
+ this.render();
826
+ };
827
+
828
+ // Set the source of the background image, which triggers the onload event above
829
+ this.crop_image_bg.src = get_full_url(detail.urls[0]);
830
+ }
831
+
832
+ // --- NEW: Drawing logic for the crop box ---
833
+ draw_crop_box() {
834
+ if (this.state !== State.CROP) return;
835
+ const ctx = this.crop_ctx;
836
+ const canvas = this.crop_canvas;
837
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
838
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
839
+ ctx.beginPath();
840
+ ctx.rect(0, 0, canvas.width, canvas.height);
841
+ if (this.crop_data.box) {
842
+ const { x, y, width, height } = this.get_normalized_box(this.crop_data.box);
843
+ ctx.rect(x, y, width, height);
844
+ }
845
+ ctx.fill('evenodd');
846
+ if (this.crop_data.box) {
847
+ const { x, y, width, height } = this.get_normalized_box(this.crop_data.box);
848
+ const brandColor = '#818cf8';
849
+ const outlineColor = '#1e1b4b'; // Dark indigo for contrast
850
+ const handles = this.get_crop_handles();
851
+ const handleSize = 10;
852
+
853
+ // Draw crop box - outline first, then brand color
854
+ ctx.strokeStyle = outlineColor;
855
+ ctx.lineWidth = 4;
856
+ ctx.strokeRect(x, y, width, height);
857
+ ctx.strokeStyle = brandColor;
858
+ ctx.lineWidth = 2;
859
+ ctx.strokeRect(x, y, width, height);
860
+
861
+ // Draw corner handles (squares) - outline then fill
862
+ for (const handle of ['top-left', 'top-right', 'bottom-left', 'bottom-right']) {
863
+ const h = handles[handle];
864
+ if (h) {
865
+ ctx.fillStyle = outlineColor;
866
+ ctx.fillRect(h.x - handleSize / 2 - 1, h.y - handleSize / 2 - 1, handleSize + 2, handleSize + 2);
867
+ ctx.fillStyle = brandColor;
868
+ ctx.fillRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);
869
+ }
870
+ }
871
+
872
+ // Draw edge handles (smaller rectangles)
873
+ const edgeHandleW = 16;
874
+ const edgeHandleH = 6;
875
+ // Top and bottom (horizontal bars)
876
+ for (const handle of ['top', 'bottom']) {
877
+ const h = handles[handle];
878
+ if (h) {
879
+ ctx.fillStyle = outlineColor;
880
+ ctx.fillRect(h.x - edgeHandleW / 2 - 1, h.y - edgeHandleH / 2 - 1, edgeHandleW + 2, edgeHandleH + 2);
881
+ ctx.fillStyle = brandColor;
882
+ ctx.fillRect(h.x - edgeHandleW / 2, h.y - edgeHandleH / 2, edgeHandleW, edgeHandleH);
883
+ }
884
+ }
885
+ // Left and right (vertical bars)
886
+ for (const handle of ['left', 'right']) {
887
+ const h = handles[handle];
888
+ if (h) {
889
+ ctx.fillStyle = outlineColor;
890
+ ctx.fillRect(h.x - edgeHandleH / 2 - 1, h.y - edgeHandleW / 2 - 1, edgeHandleH + 2, edgeHandleW + 2);
891
+ ctx.fillStyle = brandColor;
892
+ ctx.fillRect(h.x - edgeHandleH / 2, h.y - edgeHandleW / 2, edgeHandleH, edgeHandleW);
893
+ }
894
+ }
895
+
896
+ // Draw center handle (crosshair/move icon) - outline then brand
897
+ const center = handles['center'];
898
+ if (center) {
899
+ const size = 12;
900
+ // Dark outline pass
901
+ ctx.strokeStyle = outlineColor;
902
+ ctx.lineWidth = 4;
903
+ ctx.beginPath();
904
+ ctx.moveTo(center.x - size, center.y);
905
+ ctx.lineTo(center.x + size, center.y);
906
+ ctx.stroke();
907
+ ctx.beginPath();
908
+ ctx.moveTo(center.x, center.y - size);
909
+ ctx.lineTo(center.x, center.y + size);
910
+ ctx.stroke();
911
+ ctx.beginPath();
912
+ ctx.arc(center.x, center.y, 6, 0, Math.PI * 2);
913
+ ctx.stroke();
914
+ // Brand color pass
915
+ ctx.strokeStyle = brandColor;
916
+ ctx.lineWidth = 2;
917
+ ctx.beginPath();
918
+ ctx.moveTo(center.x - size, center.y);
919
+ ctx.lineTo(center.x + size, center.y);
920
+ ctx.stroke();
921
+ ctx.beginPath();
922
+ ctx.moveTo(center.x, center.y - size);
923
+ ctx.lineTo(center.x, center.y + size);
924
+ ctx.stroke();
925
+ ctx.beginPath();
926
+ ctx.arc(center.x, center.y, 6, 0, Math.PI * 2);
927
+ ctx.stroke();
928
+ }
929
+
930
+ // Show crop dimensions in original image coordinates
931
+ const scale = this.crop_data.scale;
932
+ const origWidth = Math.round(width / scale);
933
+ const origHeight = Math.round(height / scale);
934
+ ctx.font = 'bold 14px monospace';
935
+ ctx.textAlign = 'center';
936
+ const label = `${origWidth} x ${origHeight}`;
937
+ // Text shadow
938
+ ctx.fillStyle = outlineColor;
939
+ ctx.fillText(label, x + width / 2 + 1, y + height + 21);
940
+ ctx.fillText(label, x + width / 2 - 1, y + height + 21);
941
+ ctx.fillText(label, x + width / 2, y + height + 22);
942
+ ctx.fillText(label, x + width / 2, y + height + 20);
943
+ // Brand color text
944
+ ctx.fillStyle = brandColor;
945
+ ctx.fillText(label, x + width / 2, y + height + 21);
946
+ }
947
+
948
+ // Show aspect ratio lock indicator
949
+ if (this.crop_data.aspectRatioLabel) {
950
+ const brandColor = '#818cf8';
951
+ const outlineColor = '#1e1b4b';
952
+ ctx.font = 'bold 12px sans-serif';
953
+ ctx.textAlign = 'left';
954
+ const lockLabel = `🔒 ${this.crop_data.aspectRatioLabel}`;
955
+ // Text shadow
956
+ ctx.fillStyle = outlineColor;
957
+ ctx.fillText(lockLabel, 11, 21);
958
+ ctx.fillText(lockLabel, 9, 21);
959
+ ctx.fillText(lockLabel, 10, 22);
960
+ ctx.fillText(lockLabel, 10, 20);
961
+ // Brand color
962
+ ctx.fillStyle = brandColor;
963
+ ctx.fillText(lockLabel, 10, 21);
964
+ }
965
+ }
966
+
967
+ get_normalized_box(box) {
968
+ if (!box) return null;
969
+ return {
970
+ x: box.width < 0 ? box.x + box.width : box.x,
971
+ y: box.height < 0 ? box.y + box.height : box.y,
972
+ width: Math.abs(box.width),
973
+ height: Math.abs(box.height),
974
+ };
975
+ }
976
+
977
+ get_crop_handles() {
978
+ const box = this.get_normalized_box(this.crop_data.box);
979
+ if (!box) return {};
980
+ const { x, y, width, height } = box;
981
+ return {
982
+ // Corner handles for resize
983
+ 'top-left': { x, y }, 'top-right': { x: x + width, y },
984
+ 'bottom-left': { x, y: y + height }, 'bottom-right': { x: x + width, y: y + height },
985
+ // Edge handles for resize
986
+ 'top': { x: x + width / 2, y }, 'bottom': { x: x + width / 2, y: y + height },
987
+ 'left': { x, y: y + height / 2 }, 'right': { x: x + width, y: y + height / 2 },
988
+ // Center handle for move
989
+ 'center': { x: x + width / 2, y: y + height / 2 }
990
+ };
991
+ }
992
+
993
+ get_crop_hit_area(localPos) {
994
+ const handleSize = 16;
995
+ const handles = this.get_crop_handles();
996
+
997
+ // Check corner handles first (for resize)
998
+ for (const mode of ['top-left', 'top-right', 'bottom-left', 'bottom-right']) {
999
+ const handle = handles[mode];
1000
+ if (handle && Math.abs(localPos.x - handle.x) < handleSize / 2 && Math.abs(localPos.y - handle.y) < handleSize / 2) {
1001
+ return 'resize-corner';
1002
+ }
1003
+ }
1004
+
1005
+ // Check edge handles (for resize)
1006
+ for (const mode of ['top', 'bottom']) {
1007
+ const handle = handles[mode];
1008
+ if (handle && Math.abs(localPos.x - handle.x) < handleSize / 2 && Math.abs(localPos.y - handle.y) < handleSize / 2) {
1009
+ return 'resize-vertical'; // Drag vertically
1010
+ }
1011
+ }
1012
+ for (const mode of ['left', 'right']) {
1013
+ const handle = handles[mode];
1014
+ if (handle && Math.abs(localPos.x - handle.x) < handleSize / 2 && Math.abs(localPos.y - handle.y) < handleSize / 2) {
1015
+ return 'resize-horizontal'; // Drag horizontally
1016
+ }
1017
+ }
1018
+
1019
+ // Check center handle (for move)
1020
+ const center = handles['center'];
1021
+ if (center && Math.abs(localPos.x - center.x) < handleSize && Math.abs(localPos.y - center.y) < handleSize) {
1022
+ return 'move';
1023
+ }
1024
+
1025
+ // Check if inside box (also move)
1026
+ const box = this.get_normalized_box(this.crop_data.box);
1027
+ if (box) {
1028
+ const { x, y, width, height } = box;
1029
+ if (localPos.x > x && localPos.x < x + width && localPos.y > y && localPos.y < y + height) {
1030
+ return 'move';
1031
+ }
1032
+ }
1033
+ return 'new';
1034
+ }
1035
+
1036
+ update_crop_cursor(e) {
1037
+ if (this.state !== State.CROP || this.crop_data.active) return;
1038
+ const rect = this.crop_canvas.getBoundingClientRect();
1039
+ const localPos = { x: e.clientX - rect.left, y: e.clientY - rect.top };
1040
+ const mode = this.get_crop_hit_area(localPos);
1041
+
1042
+ const cursorMap = {
1043
+ 'resize-corner': 'nwse-resize',
1044
+ 'resize-vertical': 'ns-resize',
1045
+ 'resize-horizontal': 'ew-resize',
1046
+ 'move': 'move',
1047
+ 'new': 'crosshair'
1048
+ };
1049
+ this.crop_canvas.style.cursor = cursorMap[mode] || 'crosshair';
1050
+ }
1051
+
1052
+ on_crop_mouse_down(e) {
1053
+ if (this.state !== State.CROP) return;
1054
+ const rect = this.crop_canvas.getBoundingClientRect();
1055
+ const localPos = { x: e.clientX - rect.left, y: e.clientY - rect.top };
1056
+ this.crop_data.active = true;
1057
+ this.crop_data.mode = this.get_crop_hit_area(localPos);
1058
+ this.crop_data.startPos = localPos;
1059
+ this.crop_data.originalBox = this.crop_data.box ? { ...this.get_normalized_box(this.crop_data.box) } : null;
1060
+ if (this.crop_data.mode === 'new') {
1061
+ this.crop_data.box = { x: localPos.x, y: localPos.y, width: 0, height: 0 };
1062
+ this.crop_data.originalBox = null;
1063
+ }
1064
+ }
1065
+
1066
+ on_document_mouse_move(e) {
1067
+ if (this.state !== State.CROP || !this.crop_data.active) return;
1068
+ const rect = this.crop_canvas.getBoundingClientRect();
1069
+ let localPos = { x: e.clientX - rect.left, y: e.clientY - rect.top };
1070
+
1071
+ const canvasW = this.crop_canvas.width;
1072
+ const canvasH = this.crop_canvas.height;
1073
+
1074
+ localPos.x = Math.max(0, Math.min(localPos.x, canvasW));
1075
+ localPos.y = Math.max(0, Math.min(localPos.y, canvasH));
1076
+
1077
+ const dx = localPos.x - this.crop_data.startPos.x;
1078
+ const dy = localPos.y - this.crop_data.startPos.y;
1079
+ const { mode, originalBox, aspectRatio } = this.crop_data;
1080
+ let x, y, width, height;
1081
+
1082
+ if (mode === 'new') {
1083
+ // Drawing new box from click point
1084
+ x = this.crop_data.box.x; // Starting point
1085
+ width = localPos.x - x;
1086
+ height = localPos.y - this.crop_data.box.y;
1087
+ y = this.crop_data.box.y;
1088
+
1089
+ // Enforce aspect ratio when drawing new box
1090
+ if (aspectRatio) {
1091
+ const absWidth = Math.abs(width);
1092
+ const absHeight = Math.abs(height);
1093
+ const currentRatio = absWidth / (absHeight || 1);
1094
+
1095
+ if (currentRatio > aspectRatio) {
1096
+ width = Math.sign(width || 1) * absHeight * aspectRatio;
1097
+ } else {
1098
+ height = Math.sign(height || 1) * absWidth / aspectRatio;
1099
+ }
1100
+ }
1101
+
1102
+ // Normalize
1103
+ let normX = width < 0 ? x + width : x;
1104
+ let normY = height < 0 ? y + height : y;
1105
+ let normW = Math.abs(width);
1106
+ let normH = Math.abs(height);
1107
+
1108
+ // Clamp to canvas
1109
+ normX = Math.max(0, normX);
1110
+ normY = Math.max(0, normY);
1111
+ if (normX + normW > canvasW) normW = canvasW - normX;
1112
+ if (normY + normH > canvasH) normH = canvasH - normY;
1113
+
1114
+ this.crop_data.box = { x: normX, y: normY, width: Math.max(10, normW), height: Math.max(10, normH) };
1115
+
1116
+ } else if (mode === 'move') {
1117
+ // Move box, keeping size constant
1118
+ x = originalBox.x + dx;
1119
+ y = originalBox.y + dy;
1120
+ width = originalBox.width;
1121
+ height = originalBox.height;
1122
+
1123
+ // Clamp to keep box fully inside canvas
1124
+ x = Math.max(0, Math.min(x, canvasW - width));
1125
+ y = Math.max(0, Math.min(y, canvasH - height));
1126
+
1127
+ this.crop_data.box = { x, y, width, height };
1128
+
1129
+ } else if (mode === 'resize-corner' || mode === 'resize-vertical' || mode === 'resize-horizontal') {
1130
+ // Center-based resize using delta from drag start
1131
+ const origCenterX = originalBox.x + originalBox.width / 2;
1132
+ const origCenterY = originalBox.y + originalBox.height / 2;
1133
+
1134
+ // Calculate size change based on drag delta (not absolute position)
1135
+ // This prevents the "jump" when first clicking a handle
1136
+ let deltaSize;
1137
+ if (mode === 'resize-corner') {
1138
+ // Use diagonal movement for corners
1139
+ deltaSize = (dx + dy) / 2;
1140
+ } else if (mode === 'resize-vertical') {
1141
+ // Use vertical movement for top/bottom edges
1142
+ deltaSize = dy;
1143
+ } else {
1144
+ // Use horizontal movement for left/right edges
1145
+ deltaSize = dx;
1146
+ }
1147
+
1148
+ // Scale factor based on delta (positive = grow, negative = shrink)
1149
+ const scaleFactor = 1 + (deltaSize * 2) / Math.max(originalBox.width, originalBox.height);
1150
+
1151
+ let newWidth = originalBox.width * Math.max(0.1, scaleFactor);
1152
+ let newHeight = originalBox.height * Math.max(0.1, scaleFactor);
1153
+
1154
+ // Enforce aspect ratio
1155
+ if (aspectRatio) {
1156
+ const currentRatio = newWidth / newHeight;
1157
+ if (currentRatio > aspectRatio) {
1158
+ newWidth = newHeight * aspectRatio;
1159
+ } else {
1160
+ newHeight = newWidth / aspectRatio;
1161
+ }
1162
+ }
1163
+
1164
+ width = newWidth;
1165
+ height = newHeight;
1166
+
1167
+ // Clamp dimensions to canvas size
1168
+ if (width > canvasW || height > canvasH) {
1169
+ const scaleW = canvasW / width;
1170
+ const scaleH = canvasH / height;
1171
+ const clampScale = Math.min(scaleW, scaleH);
1172
+ width *= clampScale;
1173
+ height *= clampScale;
1174
+ }
1175
+
1176
+ // Calculate center position - try to keep original center but slide if needed
1177
+ let centerX = origCenterX;
1178
+ let centerY = origCenterY;
1179
+
1180
+ const halfW = width / 2;
1181
+ const halfH = height / 2;
1182
+
1183
+ // Slide center to keep box in bounds
1184
+ if (centerX - halfW < 0) {
1185
+ centerX = halfW;
1186
+ }
1187
+ if (centerX + halfW > canvasW) {
1188
+ centerX = canvasW - halfW;
1189
+ }
1190
+ if (centerY - halfH < 0) {
1191
+ centerY = halfH;
1192
+ }
1193
+ if (centerY + halfH > canvasH) {
1194
+ centerY = canvasH - halfH;
1195
+ }
1196
+
1197
+ x = centerX - halfW;
1198
+ y = centerY - halfH;
1199
+
1200
+ // Ensure minimum size
1201
+ width = Math.max(20, width);
1202
+ height = Math.max(20, height);
1203
+
1204
+ // If aspect ratio locked and we hit minimum, enforce ratio on minimum too
1205
+ if (aspectRatio && (width <= 20 || height <= 20)) {
1206
+ if (width / height > aspectRatio) {
1207
+ width = height * aspectRatio;
1208
+ } else {
1209
+ height = width / aspectRatio;
1210
+ }
1211
+ }
1212
+
1213
+ this.crop_data.box = { x, y, width, height };
1214
+ }
1215
+
1216
+ this.draw_crop_box();
1217
+ }
1218
+
1219
+ on_crop_mouse_move(e) {
1220
+ this.update_crop_cursor(e);
1221
+ }
1222
+
1223
+ on_crop_mouse_up(e) {
1224
+ if (this.state !== State.CROP || !this.crop_data.active) return;
1225
+ this.crop_data.active = false;
1226
+
1227
+ if (this.crop_data.box) {
1228
+ this.crop_data.box = this.get_normalized_box(this.crop_data.box);
1229
+ }
1230
+
1231
+ this.draw_crop_box();
1232
+ this.render();
1233
+ }
1234
+ }
1235
+
1236
+ customElements.define('instaraw-imgae-filter-popup', Popup); // Renamed custom element
1237
+
1238
+ export const popup = new Popup();
custom_nodes/ComfyUI_INSTARAW/js/reality_prompt_generator.css ADDED
@@ -0,0 +1,2841 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ---
2
+ // Filename: ../ComfyUI_INSTARAW/js/reality_prompt_generator.css
3
+ // Reality Prompt Generator (RPG) - Styling (Following AIL Theme Exactly)
4
+ // --- */
5
+
6
+ /* === Main Container (AIL Pattern) === */
7
+ .instaraw-rpg-container {
8
+ background: #0d0f12;
9
+ padding: 16px;
10
+ border-radius: 4px;
11
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
12
+ color: #f9fafb;
13
+ }
14
+
15
+ /* === Global Select/Dropdown Dark Mode Fix (Chrome compatibility) === */
16
+ .instaraw-rpg-container select {
17
+ color-scheme: dark;
18
+ }
19
+
20
+ .instaraw-rpg-container select option {
21
+ background: #1f2937;
22
+ color: #f9fafb;
23
+ }
24
+
25
+ .instaraw-rpg-topbar {
26
+ display: flex;
27
+ flex-wrap: wrap;
28
+ justify-content: space-between;
29
+ gap: 12px;
30
+ margin-bottom: 12px;
31
+ }
32
+
33
+ .instaraw-rpg-mode-card {
34
+ flex: 1 1 320px;
35
+ border: 1px solid rgba(255, 255, 255, 0.08);
36
+ background: rgba(255, 255, 255, 0.03);
37
+ border-radius: 4px;
38
+ padding: 12px;
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 8px;
42
+ }
43
+
44
+ .instaraw-rpg-mode-meta {
45
+ display: flex;
46
+ flex-wrap: wrap;
47
+ gap: 8px;
48
+ align-items: center;
49
+ }
50
+
51
+ .instaraw-rpg-mode-hint {
52
+ font-size: 12px;
53
+ color: rgba(249, 250, 251, 0.6);
54
+ }
55
+
56
+ .instaraw-rpg-mode-pill {
57
+ background: rgba(167, 139, 250, 0.2);
58
+ color: #c4b5fd;
59
+ padding: 4px 10px;
60
+ border-radius: 999px;
61
+ font-size: 11px;
62
+ font-weight: 600;
63
+ }
64
+
65
+ .instaraw-rpg-kpi-row {
66
+ display: flex;
67
+ flex: 1 1 240px;
68
+ gap: 12px;
69
+ justify-content: flex-end;
70
+ flex-wrap: wrap;
71
+ }
72
+
73
+ .instaraw-rpg-kpi {
74
+ flex: 1 1 100px;
75
+ border: 1px solid rgba(255, 255, 255, 0.08);
76
+ background: rgba(255, 255, 255, 0.02);
77
+ border-radius: 4px;
78
+ padding: 10px 14px;
79
+ display: flex;
80
+ flex-direction: column;
81
+ justify-content: center;
82
+ align-items: center;
83
+ gap: 2px;
84
+ min-width: 100px;
85
+ }
86
+
87
+ .instaraw-rpg-kpi span {
88
+ font-size: 10px;
89
+ text-transform: uppercase;
90
+ letter-spacing: 0.5px;
91
+ color: rgba(249, 250, 251, 0.5);
92
+ font-weight: 500;
93
+ }
94
+
95
+ .instaraw-rpg-kpi strong {
96
+ font-size: 20px;
97
+ font-weight: 700;
98
+ color: #a78bfa;
99
+ font-variant-numeric: tabular-nums;
100
+ }
101
+
102
+ /* === Header & Tabs === */
103
+ .instaraw-rpg-header {
104
+ display: flex;
105
+ justify-content: space-between;
106
+ align-items: center;
107
+ margin-bottom: 16px;
108
+ gap: 12px;
109
+ flex-wrap: wrap;
110
+ }
111
+
112
+ .instaraw-rpg-tabs {
113
+ display: flex;
114
+ gap: 8px;
115
+ background: rgba(255, 255, 255, 0.02);
116
+ border: 1px solid rgba(255, 255, 255, 0.05);
117
+ border-radius: 4px;
118
+ padding: 12px;
119
+ margin-top: 12px;
120
+ }
121
+
122
+ .instaraw-rpg-tab {
123
+ background: rgba(255, 255, 255, 0.05);
124
+ color: rgba(249, 250, 251, 0.7);
125
+ border: 1px solid rgba(255, 255, 255, 0.1);
126
+ padding: 8px 16px;
127
+ border-radius: 4px;
128
+ cursor: pointer;
129
+ font-size: 13px;
130
+ font-weight: 500;
131
+ display: flex;
132
+ align-items: center;
133
+ gap: 6px;
134
+ }
135
+
136
+ .instaraw-rpg-tab:hover {
137
+ background: rgba(255, 255, 255, 0.08);
138
+ border-color: #818cf8;
139
+ color: #f9fafb;
140
+ }
141
+
142
+ .instaraw-rpg-tab.active {
143
+ background: #6366f1;
144
+ color: #ffffff;
145
+ border-color: #6366f1;
146
+ }
147
+
148
+ /* === Mode Selector (AIL Pattern) === */
149
+ .instaraw-rpg-mode-selector {
150
+ display: flex;
151
+ align-items: center;
152
+ gap: 12px;
153
+ }
154
+
155
+ .instaraw-rpg-mode-selector label {
156
+ color: rgba(249, 250, 251, 0.7);
157
+ font-size: 11px;
158
+ font-weight: 500;
159
+ text-transform: uppercase;
160
+ }
161
+
162
+ .instaraw-rpg-mode-dropdown {
163
+ background: rgba(255, 255, 255, 0.05);
164
+ color: #f9fafb;
165
+ border: 1px solid rgba(255, 255, 255, 0.1);
166
+ padding: 8px 12px;
167
+ border-radius: 4px;
168
+ cursor: pointer;
169
+ font-size: 13px;
170
+ font-weight: 500;
171
+ outline: none;
172
+ min-width: 140px;
173
+ color-scheme: dark;
174
+ }
175
+
176
+ .instaraw-rpg-mode-dropdown:hover {
177
+ background: rgba(255, 255, 255, 0.08);
178
+ border-color: #818cf8;
179
+ }
180
+
181
+ .instaraw-rpg-mode-dropdown:focus {
182
+ border-color: #a78bfa;
183
+ }
184
+
185
+ .instaraw-rpg-mode-dropdown option {
186
+ background: #1f2937;
187
+ color: #f9fafb;
188
+ }
189
+
190
+ /* === Content Layout (Two-Column Grid: 50% Main Panel | 50% Batch) === */
191
+ .instaraw-rpg-content {
192
+ display: grid;
193
+ grid-template-columns: 1fr 1fr;
194
+ gap: 16px;
195
+ margin-bottom: 16px;
196
+ }
197
+
198
+ .instaraw-rpg-main-panel {
199
+ background: rgba(255, 255, 255, 0.03);
200
+ border: 1px solid rgba(255, 255, 255, 0.06);
201
+ border-radius: 4px;
202
+ padding: 16px;
203
+ }
204
+
205
+ .instaraw-rpg-batch-panel {
206
+ background: rgba(255, 255, 255, 0.03);
207
+ border: 1px solid rgba(255, 255, 255, 0.06);
208
+ border-radius: 4px;
209
+ padding: 16px;
210
+ min-height: 200px;
211
+ /* No max-height - let it grow naturally */
212
+ overflow-y: visible;
213
+ }
214
+
215
+ .instaraw-rpg-panel-card {
216
+ margin-top: 12px;
217
+ background: rgba(255, 255, 255, 0.02);
218
+ border: 1px solid rgba(255, 255, 255, 0.05);
219
+ border-radius: 4px;
220
+ padding: 12px;
221
+ }
222
+
223
+ /* === Buttons (AIL Pattern) === */
224
+ .instaraw-rpg-btn-primary {
225
+ background: #6366f1;
226
+ color: #ffffff;
227
+ border: none;
228
+ padding: 10px 20px;
229
+ border-radius: 4px;
230
+ cursor: pointer;
231
+ font-weight: 600;
232
+ font-size: 13px;
233
+ }
234
+
235
+ .instaraw-rpg-btn-primary:hover {
236
+ background: #4f46e5;
237
+ }
238
+
239
+ .instaraw-rpg-btn-primary:disabled {
240
+ background: #4f46e5;
241
+ cursor: not-allowed;
242
+ color: rgba(255, 255, 255, 0.7);
243
+ }
244
+
245
+ .instaraw-rpg-btn-secondary {
246
+ background: rgba(255, 255, 255, 0.08);
247
+ color: #f9fafb;
248
+ border: 1px solid rgba(255, 255, 255, 0.1);
249
+ padding: 8px 16px;
250
+ border-radius: 4px;
251
+ cursor: pointer;
252
+ font-weight: 500;
253
+ font-size: 12px;
254
+ }
255
+
256
+ .instaraw-rpg-btn-secondary:hover {
257
+ background: rgba(255, 255, 255, 0.12);
258
+ border-color: #818cf8;
259
+ }
260
+
261
+ .instaraw-rpg-btn-secondary:disabled {
262
+ background: rgba(255, 255, 255, 0.05);
263
+ cursor: not-allowed;
264
+ color: rgba(249, 250, 251, 0.5);
265
+ }
266
+
267
+ .instaraw-rpg-btn-warning {
268
+ background: rgba(245, 158, 11, 0.15);
269
+ border-color: rgba(245, 158, 11, 0.4);
270
+ color: #fbbf24;
271
+ }
272
+
273
+ .instaraw-rpg-btn-warning:hover {
274
+ background: rgba(245, 158, 11, 0.25);
275
+ border-color: rgba(245, 158, 11, 0.6);
276
+ }
277
+
278
+ .instaraw-rpg-btn-danger {
279
+ background: #ef4444;
280
+ color: #ffffff;
281
+ border: none;
282
+ padding: 10px 20px;
283
+ border-radius: 4px;
284
+ cursor: pointer;
285
+ font-weight: 600;
286
+ font-size: 13px;
287
+ width: 100%;
288
+ margin-top: 12px;
289
+ }
290
+
291
+ .instaraw-rpg-btn-danger:hover {
292
+ background: #dc2626;
293
+ }
294
+
295
+ /* === Loading State === */
296
+ .instaraw-rpg-loading {
297
+ display: flex;
298
+ flex-direction: column;
299
+ align-items: center;
300
+ justify-content: center;
301
+ padding: 64px 24px;
302
+ text-align: center;
303
+ }
304
+
305
+ .instaraw-rpg-loading-spinner {
306
+ width: 48px;
307
+ height: 48px;
308
+ border: 4px solid rgba(167, 139, 250, 0.2);
309
+ border-top-color: #a78bfa;
310
+ border-radius: 50%;
311
+ animation: instaraw-rpg-spin 1s linear infinite;
312
+ margin-bottom: 16px;
313
+ }
314
+
315
+ @keyframes instaraw-rpg-spin {
316
+ to { transform: rotate(360deg); }
317
+ }
318
+
319
+ .instaraw-rpg-progress-bar {
320
+ width: 100%;
321
+ max-width: 400px;
322
+ height: 8px;
323
+ background: rgba(255, 255, 255, 0.1);
324
+ border-radius: 4px;
325
+ overflow: hidden;
326
+ margin: 16px 0;
327
+ }
328
+
329
+ .instaraw-rpg-progress-fill {
330
+ height: 100%;
331
+ background: linear-gradient(90deg, #6366f1, #a78bfa);
332
+ }
333
+
334
+ .instaraw-rpg-progress-text {
335
+ font-size: 12px;
336
+ color: rgba(249, 250, 251, 0.7);
337
+ margin-top: 8px;
338
+ }
339
+
340
+ /* === Button Loading Progress Bar === */
341
+ .instaraw-rpg-progress-bar-loading {
342
+ position: absolute;
343
+ bottom: 0;
344
+ left: 0;
345
+ right: 0;
346
+ height: 3px;
347
+ background: linear-gradient(90deg, #c4b5fd, #818cf8, #c4b5fd);
348
+ background-size: 200% 100%;
349
+ animation: instaraw-rpg-progress-slide 1.5s linear infinite;
350
+ opacity: 1;
351
+ box-shadow: 0 0 8px rgba(167, 139, 250, 0.6);
352
+ }
353
+
354
+ @keyframes instaraw-rpg-progress-slide {
355
+ 0% { background-position: 200% 0; }
356
+ 100% { background-position: -200% 0; }
357
+ }
358
+
359
+ /* === Empty State (AIL Pattern) === */
360
+ .instaraw-rpg-empty {
361
+ text-align: center;
362
+ padding: 48px 24px;
363
+ background: rgba(255, 255, 255, 0.02);
364
+ border-radius: 4px;
365
+ border: 1px dashed rgba(167, 139, 250, 0.2);
366
+ grid-column: 1 / -1;
367
+ }
368
+
369
+ .instaraw-rpg-empty p {
370
+ margin: 8px 0;
371
+ color: rgba(249, 250, 251, 0.7);
372
+ font-size: 13px;
373
+ font-weight: 500;
374
+ }
375
+
376
+ .instaraw-rpg-hint {
377
+ font-size: 12px;
378
+ color: rgba(129, 140, 248, 0.6);
379
+ font-weight: 400;
380
+ }
381
+
382
+ /* === Library Tab === */
383
+ .instaraw-rpg-library {
384
+ display: flex;
385
+ flex-direction: column;
386
+ gap: 16px;
387
+ }
388
+
389
+ .instaraw-rpg-filters {
390
+ display: flex;
391
+ flex-direction: column;
392
+ gap: 8px;
393
+ }
394
+
395
+ .instaraw-rpg-search-input {
396
+ width: 100%;
397
+ background: rgba(255, 255, 255, 0.06);
398
+ border: 1px solid rgba(255, 255, 255, 0.1);
399
+ color: #f9fafb;
400
+ padding: 10px 12px;
401
+ border-radius: 4px;
402
+ font-size: 13px;
403
+ outline: none;
404
+ }
405
+
406
+ .instaraw-rpg-search-input:focus {
407
+ border-color: #a78bfa;
408
+ background: rgba(255, 255, 255, 0.08);
409
+ }
410
+
411
+ .instaraw-rpg-filter-row {
412
+ display: flex;
413
+ gap: 8px;
414
+ flex-wrap: wrap;
415
+ }
416
+
417
+ .instaraw-rpg-filter-dropdown {
418
+ flex: 1;
419
+ min-width: 120px;
420
+ background: rgba(255, 255, 255, 0.05);
421
+ color: #f9fafb;
422
+ border: 1px solid rgba(255, 255, 255, 0.1);
423
+ padding: 8px 10px;
424
+ border-radius: 4px;
425
+ cursor: pointer;
426
+ font-size: 12px;
427
+ outline: none;
428
+ color-scheme: dark;
429
+ }
430
+
431
+ .instaraw-rpg-filter-dropdown:hover {
432
+ background: rgba(255, 255, 255, 0.08);
433
+ border-color: #818cf8;
434
+ }
435
+
436
+ .instaraw-rpg-filter-dropdown:focus {
437
+ border-color: #818cf8;
438
+ outline: none;
439
+ }
440
+
441
+ .instaraw-rpg-filter-dropdown option {
442
+ background: #1f2937;
443
+ color: #f9fafb;
444
+ }
445
+
446
+ /* Ensure classification dropdowns in edit mode are clickable */
447
+ .instaraw-rpg-user-prompt-edit-content-type,
448
+ .instaraw-rpg-user-prompt-edit-safety-level,
449
+ .instaraw-rpg-user-prompt-edit-shot-type {
450
+ position: relative;
451
+ z-index: 10;
452
+ pointer-events: auto;
453
+ }
454
+
455
+ .instaraw-rpg-checkbox-label {
456
+ display: flex;
457
+ align-items: center;
458
+ gap: 6px;
459
+ padding: 8px 12px;
460
+ background: rgba(255, 255, 255, 0.05);
461
+ border: 1px solid rgba(255, 255, 255, 0.1);
462
+ border-radius: 4px;
463
+ cursor: pointer;
464
+ font-size: 12px;
465
+ color: #f9fafb;
466
+ white-space: nowrap;
467
+ user-select: none;
468
+ }
469
+
470
+ .instaraw-rpg-checkbox-label:hover {
471
+ background: rgba(255, 255, 255, 0.08);
472
+ border-color: #818cf8;
473
+ }
474
+
475
+ .instaraw-rpg-checkbox-label input[type="checkbox"] {
476
+ cursor: pointer;
477
+ margin: 0;
478
+ }
479
+
480
+ .instaraw-rpg-library-header {
481
+ margin: 12px 0 8px 0;
482
+ padding: 8px 0;
483
+ border-bottom: 1px solid rgba(255, 255, 255, 0.06);
484
+ }
485
+
486
+ .instaraw-rpg-result-count {
487
+ font-size: 13px;
488
+ color: rgba(249, 250, 251, 0.7);
489
+ font-weight: 500;
490
+ }
491
+
492
+ .instaraw-rpg-sdxl-toggle {
493
+ display: flex;
494
+ align-items: center;
495
+ gap: 6px;
496
+ padding: 4px 10px;
497
+ background: rgba(167, 139, 250, 0.15);
498
+ border: 1px solid rgba(167, 139, 250, 0.3);
499
+ border-radius: 4px;
500
+ cursor: pointer;
501
+ font-size: 12px;
502
+ color: #c4b5fd;
503
+ white-space: nowrap;
504
+ user-select: none;
505
+ }
506
+
507
+ .instaraw-rpg-sdxl-toggle:hover {
508
+ background: rgba(167, 139, 250, 0.25);
509
+ border-color: rgba(167, 139, 250, 0.5);
510
+ }
511
+
512
+ .instaraw-rpg-sdxl-toggle input[type="checkbox"] {
513
+ cursor: pointer;
514
+ margin: 0;
515
+ }
516
+
517
+ .instaraw-rpg-pagination-top {
518
+ margin-bottom: 12px;
519
+ }
520
+
521
+ .instaraw-rpg-library-grid {
522
+ display: grid;
523
+ grid-template-columns: repeat(auto-fill, minmax(max(200px, calc((100% - 24px) / 3)), 1fr));
524
+ gap: 12px;
525
+ }
526
+
527
+ @media (max-width: 420px) {
528
+ .instaraw-rpg-library-grid {
529
+ grid-template-columns: 1fr;
530
+ }
531
+ }
532
+
533
+ .instaraw-rpg-library-card {
534
+ background: rgba(255, 255, 255, 0.04);
535
+ border: 2px solid rgba(255, 255, 255, 0.08);
536
+ border-radius: 4px;
537
+ overflow: hidden;
538
+ position: relative;
539
+ max-width: 500px;
540
+ }
541
+
542
+ .instaraw-rpg-library-card:hover {
543
+ border-color: rgba(99, 102, 241, 0.5);
544
+ }
545
+
546
+ .instaraw-rpg-library-card.in-batch {
547
+ border-color: rgba(167, 139, 250, 0.4);
548
+ }
549
+
550
+ /* Selection mode styling */
551
+ .instaraw-rpg-library-card.selection-mode {
552
+ cursor: pointer;
553
+ }
554
+
555
+ .instaraw-rpg-library-card.selection-mode:hover {
556
+ border-color: rgba(59, 130, 246, 0.6);
557
+ background: rgba(59, 130, 246, 0.05);
558
+ }
559
+
560
+ /* Selected card indicator */
561
+ .instaraw-rpg-library-card.selection-mode:has(.instaraw-rpg-prompt-checkbox:checked) {
562
+ border-color: rgba(59, 130, 246, 0.8);
563
+ background: rgba(59, 130, 246, 0.12);
564
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.3);
565
+ }
566
+
567
+ .instaraw-rpg-selection-checkbox {
568
+ flex-shrink: 0;
569
+ }
570
+
571
+ .instaraw-rpg-selection-checkbox input[type="checkbox"] {
572
+ accent-color: #3b82f6;
573
+ }
574
+
575
+ /* Badge removed - button now shows Remove state instead */
576
+
577
+ .instaraw-rpg-library-card-header {
578
+ display: flex;
579
+ justify-content: space-between;
580
+ padding: 8px;
581
+ background: rgba(0, 0, 0, 0.2);
582
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
583
+ }
584
+
585
+ .instaraw-rpg-bookmark-btn {
586
+ background: transparent;
587
+ border: none;
588
+ color: rgba(249, 250, 251, 0.5);
589
+ font-size: 16px;
590
+ cursor: pointer;
591
+ }
592
+
593
+ .instaraw-rpg-bookmark-btn:hover {
594
+ color: #fbbf24;
595
+ }
596
+
597
+ .instaraw-rpg-bookmark-btn.bookmarked {
598
+ color: #fbbf24;
599
+ }
600
+
601
+ .instaraw-rpg-add-to-batch-btn {
602
+ background: #6366f1;
603
+ color: #ffffff;
604
+ border: none;
605
+ padding: 4px 12px;
606
+ border-radius: 4px;
607
+ cursor: pointer;
608
+ font-size: 11px;
609
+ font-weight: 600;
610
+ }
611
+
612
+ .instaraw-rpg-add-to-batch-btn:hover {
613
+ background: #4f46e5;
614
+ }
615
+
616
+ .instaraw-rpg-batch-controls {
617
+ display: flex;
618
+ gap: 4px;
619
+ }
620
+
621
+ .instaraw-rpg-undo-batch-btn {
622
+ background: #dc2626;
623
+ color: #ffffff;
624
+ border: none;
625
+ padding: 4px 10px;
626
+ border-radius: 4px;
627
+ cursor: pointer;
628
+ font-size: 11px;
629
+ font-weight: 600;
630
+ }
631
+
632
+ .instaraw-rpg-undo-batch-btn:hover {
633
+ background: #b91c1c;
634
+ }
635
+
636
+ .instaraw-rpg-library-card-content {
637
+ padding: 12px;
638
+ }
639
+
640
+ .instaraw-rpg-prompt-preview {
641
+ font-size: 12px;
642
+ color: rgba(249, 250, 251, 0.8);
643
+ line-height: 1.42;
644
+ margin-bottom: 8px;
645
+ }
646
+
647
+ .instaraw-rpg-error-text {
648
+ color: rgba(156, 163, 175, 0.8) !important;
649
+ font-style: italic;
650
+ }
651
+
652
+ .instaraw-rpg-library-card-tags {
653
+ display: flex;
654
+ flex-wrap: wrap;
655
+ gap: 4px;
656
+ }
657
+
658
+ .instaraw-rpg-tag {
659
+ background: rgba(167, 139, 250, 0.2);
660
+ color: #c4b5fd;
661
+ padding: 2px 8px;
662
+ border-radius: 4px;
663
+ font-size: 10px;
664
+ font-weight: 500;
665
+ }
666
+
667
+ .instaraw-rpg-tag-more {
668
+ background: rgba(129, 140, 248, 0.2);
669
+ color: #818cf8;
670
+ padding: 2px 8px;
671
+ border-radius: 4px;
672
+ font-size: 10px;
673
+ font-weight: 500;
674
+ }
675
+
676
+ /* === Search Highlighting === */
677
+ .instaraw-rpg-highlight {
678
+ background: rgba(251, 191, 36, 0.4);
679
+ color: #fbbf24;
680
+ font-weight: 600;
681
+ padding: 1px 2px;
682
+ border-radius: 4px;
683
+ }
684
+
685
+ /* === Match Badge === */
686
+ .instaraw-rpg-match-badge {
687
+ display: inline-block;
688
+ background: rgba(34, 197, 94, 0.2);
689
+ color: #22c55e;
690
+ padding: 4px 8px;
691
+ border-radius: 4px;
692
+ font-size: 10px;
693
+ font-weight: 600;
694
+ margin-bottom: 8px;
695
+ }
696
+
697
+ /* === SDXL Mode Styles === */
698
+ .instaraw-rpg-tags-primary {
699
+ margin-bottom: 8px;
700
+ }
701
+
702
+ /* SDXL mode uses existing .instaraw-rpg-prompt-preview for clean display */
703
+
704
+ .instaraw-rpg-expandable-tags {
705
+ display: flex;
706
+ flex-wrap: wrap;
707
+ gap: 4px;
708
+ align-items: center;
709
+ }
710
+
711
+ .instaraw-rpg-expandable-tags[data-expanded="false"] {
712
+ max-height: 80px;
713
+ overflow: hidden;
714
+ }
715
+
716
+ .instaraw-rpg-expandable-tags[data-expanded="true"] {
717
+ max-height: none;
718
+ }
719
+
720
+ .instaraw-rpg-expand-tags-btn {
721
+ background: rgba(99, 102, 241, 0.2);
722
+ color: #818cf8;
723
+ border: 1px solid rgba(99, 102, 241, 0.3);
724
+ padding: 2px 8px;
725
+ border-radius: 4px;
726
+ font-size: 10px;
727
+ font-weight: 600;
728
+ cursor: pointer;
729
+ }
730
+
731
+ .instaraw-rpg-expand-tags-btn:hover {
732
+ background: rgba(99, 102, 241, 0.3);
733
+ border-color: rgba(99, 102, 241, 0.5);
734
+ }
735
+
736
+ .instaraw-rpg-prompt-preview-small {
737
+ font-size: 11px;
738
+ color: rgba(249, 250, 251, 0.5);
739
+ line-height: 1.42;
740
+ font-style: italic;
741
+ }
742
+
743
+ .instaraw-rpg-toggle-tags-btn {
744
+ background: rgba(129, 140, 248, 0.2);
745
+ color: #818cf8;
746
+ border: 1px solid rgba(129, 140, 248, 0.3);
747
+ padding: 2px 8px;
748
+ border-radius: 4px;
749
+ font-size: 10px;
750
+ font-weight: 600;
751
+ cursor: pointer;
752
+ }
753
+
754
+ .instaraw-rpg-toggle-tags-btn:hover {
755
+ background: rgba(129, 140, 248, 0.3);
756
+ border-color: rgba(129, 140, 248, 0.5);
757
+ }
758
+
759
+ /* === Pagination === */
760
+ .instaraw-rpg-pagination {
761
+ display: flex;
762
+ justify-content: space-between;
763
+ align-items: center;
764
+ padding: 12px 0;
765
+ }
766
+
767
+ .instaraw-rpg-page-info {
768
+ font-size: 12px;
769
+ color: rgba(249, 250, 251, 0.7);
770
+ }
771
+
772
+ /* === Creative/Character Tabs === */
773
+ .instaraw-rpg-creative,
774
+ .instaraw-rpg-character {
775
+ display: flex;
776
+ flex-direction: column;
777
+ gap: 16px;
778
+ }
779
+
780
+ .instaraw-rpg-model-settings {
781
+ background: rgba(255, 255, 255, 0.03);
782
+ border: 1px solid rgba(255, 255, 255, 0.06);
783
+ border-radius: 4px;
784
+ padding: 16px;
785
+ display: flex;
786
+ flex-direction: column;
787
+ gap: 12px;
788
+ }
789
+
790
+ .instaraw-rpg-model-row {
791
+ display: flex;
792
+ flex-direction: column;
793
+ gap: 6px;
794
+ }
795
+
796
+ .instaraw-rpg-model-row label {
797
+ font-size: 12px;
798
+ font-weight: 500;
799
+ color: rgba(249, 250, 251, 0.7);
800
+ }
801
+
802
+ .instaraw-rpg-model-select,
803
+ .instaraw-rpg-model-temp,
804
+ .instaraw-rpg-model-top-p,
805
+ .instaraw-rpg-system-prompt {
806
+ width: 100%;
807
+ background: rgba(255, 255, 255, 0.06);
808
+ border: 1px solid rgba(255, 255, 255, 0.1);
809
+ border-radius: 4px;
810
+ padding: 8px 10px;
811
+ color: #f9fafb;
812
+ font-size: 13px;
813
+ outline: none;
814
+ color-scheme: dark;
815
+ }
816
+
817
+ .instaraw-rpg-model-select option {
818
+ background: #1f2937;
819
+ color: #f9fafb;
820
+ }
821
+
822
+ .instaraw-rpg-model-select:focus,
823
+ .instaraw-rpg-model-temp:focus,
824
+ .instaraw-rpg-model-top-p:focus,
825
+ .instaraw-rpg-system-prompt:focus {
826
+ border-color: #a78bfa;
827
+ }
828
+
829
+ .instaraw-rpg-model-grid {
830
+ display: flex;
831
+ gap: 12px;
832
+ flex-wrap: wrap;
833
+ }
834
+
835
+ .instaraw-rpg-model-control {
836
+ flex: 1 1 160px;
837
+ display: flex;
838
+ flex-direction: column;
839
+ gap: 6px;
840
+ }
841
+
842
+ .instaraw-rpg-system-prompt {
843
+ min-height: 90px;
844
+ resize: vertical;
845
+ font-family: 'Inter', sans-serif;
846
+ line-height: 1.42;
847
+ }
848
+
849
+ .instaraw-rpg-creative-header h3 {
850
+ margin: 0 0 8px 0;
851
+ font-size: 16px;
852
+ color: #f9fafb;
853
+ }
854
+
855
+ .instaraw-rpg-creative-header p {
856
+ margin: 0;
857
+ font-size: 12px;
858
+ color: rgba(249, 250, 251, 0.6);
859
+ }
860
+
861
+ .instaraw-rpg-inspiration-section label {
862
+ display: block;
863
+ margin-bottom: 8px;
864
+ font-size: 12px;
865
+ font-weight: 500;
866
+ color: rgba(249, 250, 251, 0.8);
867
+ }
868
+
869
+ .instaraw-rpg-inspiration-list {
870
+ background: rgba(0, 0, 0, 0.2);
871
+ border: 1px solid rgba(255, 255, 255, 0.06);
872
+ border-radius: 4px;
873
+ padding: 12px;
874
+ max-height: 150px;
875
+ overflow-y: auto;
876
+ }
877
+
878
+ .instaraw-rpg-inspiration-item {
879
+ padding: 8px;
880
+ background: rgba(255, 255, 255, 0.04);
881
+ border-radius: 4px;
882
+ margin-bottom: 6px;
883
+ }
884
+
885
+ .instaraw-rpg-inspiration-item:last-child {
886
+ margin-bottom: 0;
887
+ }
888
+
889
+ .instaraw-rpg-inspiration-text {
890
+ font-size: 11px;
891
+ color: rgba(249, 250, 251, 0.7);
892
+ line-height: 1.42;
893
+ }
894
+
895
+ .instaraw-rpg-creative-controls {
896
+ display: flex;
897
+ gap: 12px;
898
+ flex-wrap: wrap;
899
+ }
900
+
901
+ .instaraw-rpg-control-group {
902
+ flex: 1;
903
+ min-width: 120px;
904
+ }
905
+
906
+ .instaraw-rpg-control-group label {
907
+ display: block;
908
+ margin-bottom: 6px;
909
+ font-size: 12px;
910
+ font-weight: 500;
911
+ color: rgba(249, 250, 251, 0.8);
912
+ }
913
+
914
+ .instaraw-rpg-number-input {
915
+ width: 100%;
916
+ background: rgba(255, 255, 255, 0.06);
917
+ border: 1px solid rgba(255, 255, 255, 0.1);
918
+ color: #f9fafb;
919
+ padding: 8px 10px;
920
+ border-radius: 4px;
921
+ font-size: 13px;
922
+ outline: none;
923
+ }
924
+
925
+ .instaraw-rpg-number-input:focus {
926
+ border-color: #a78bfa;
927
+ background: rgba(255, 255, 255, 0.08);
928
+ outline: none;
929
+ box-shadow: 0 0 0 1px #a78bfa;
930
+ }
931
+
932
+ .instaraw-rpg-checkbox {
933
+ margin-right: 6px;
934
+ accent-color: #6366f1;
935
+ }
936
+
937
+ .instaraw-rpg-character-ref-input {
938
+ width: 100%;
939
+ background: rgba(255, 255, 255, 0.06);
940
+ border: 1px solid rgba(255, 255, 255, 0.1);
941
+ color: #f9fafb;
942
+ padding: 10px 12px;
943
+ border-radius: 4px;
944
+ font-size: 13px;
945
+ font-family: 'Inter', monospace;
946
+ outline: none;
947
+ resize: vertical;
948
+ line-height: 1.42;
949
+ }
950
+
951
+ .instaraw-rpg-character-ref-input:focus {
952
+ border-color: #a78bfa;
953
+ background: rgba(255, 255, 255, 0.08);
954
+ }
955
+
956
+ .instaraw-rpg-creative-preview {
957
+ background: rgba(167, 139, 250, 0.08);
958
+ border: 1px solid rgba(167, 139, 250, 0.2);
959
+ border-radius: 4px;
960
+ padding: 16px;
961
+ }
962
+
963
+ .instaraw-rpg-creative-preview h4 {
964
+ margin: 0 0 12px 0;
965
+ font-size: 14px;
966
+ color: #a78bfa;
967
+ }
968
+
969
+ .instaraw-rpg-creative-preview-list,
970
+ .instaraw-rpg-character-preview-list {
971
+ max-height: 200px;
972
+ overflow-y: auto;
973
+ margin-bottom: 12px;
974
+ }
975
+
976
+ .instaraw-rpg-preview-item {
977
+ background: rgba(0, 0, 0, 0.2);
978
+ padding: 10px;
979
+ border-radius: 4px;
980
+ margin-bottom: 8px;
981
+ }
982
+
983
+ .instaraw-rpg-preview-item-header {
984
+ display: flex;
985
+ align-items: center;
986
+ gap: 8px;
987
+ margin-bottom: 6px;
988
+ }
989
+
990
+ .instaraw-rpg-preview-images {
991
+ display: flex;
992
+ gap: 4px;
993
+ }
994
+
995
+ .instaraw-rpg-preview-item strong {
996
+ color: #c4b5fd;
997
+ font-size: 11px;
998
+ flex-shrink: 0;
999
+ }
1000
+
1001
+ .instaraw-rpg-preview-item p {
1002
+ margin: 0;
1003
+ font-size: 12px;
1004
+ color: rgba(249, 250, 251, 0.8);
1005
+ line-height: 1.42;
1006
+ }
1007
+
1008
+ /* === Batch Panel === */
1009
+ .instaraw-rpg-batch-container {
1010
+ background: rgba(255, 255, 255, 0.02);
1011
+ border: 1px solid rgba(255, 255, 255, 0.05);
1012
+ border-radius: 4px;
1013
+ padding: 12px;
1014
+ margin-top: 12px;
1015
+ }
1016
+
1017
+ .instaraw-rpg-batch-header {
1018
+ display: flex;
1019
+ justify-content: space-between;
1020
+ align-items: flex-start;
1021
+ gap: 12px;
1022
+ margin-bottom: 16px;
1023
+ }
1024
+
1025
+ .instaraw-rpg-batch-header h3 {
1026
+ margin: 0;
1027
+ font-size: 16px;
1028
+ color: #f9fafb;
1029
+ }
1030
+
1031
+ .instaraw-rpg-batch-subtitle {
1032
+ margin: 10px 0 0 0;
1033
+ font-size: 12px;
1034
+ color: rgba(249, 250, 251, 0.6);
1035
+ }
1036
+
1037
+ .instaraw-rpg-batch-actions {
1038
+ display: flex;
1039
+ align-items: center;
1040
+ gap: 12px;
1041
+ flex-wrap: wrap;
1042
+ }
1043
+
1044
+ .instaraw-rpg-batch-count {
1045
+ background: rgba(167, 139, 250, 0.2);
1046
+ color: #c4b5fd;
1047
+ padding: 6px 12px;
1048
+ border-radius: 4px;
1049
+ font-size: 12px;
1050
+ font-weight: 600;
1051
+ }
1052
+
1053
+ .instaraw-rpg-reorder-toggle-btn {
1054
+ background: rgba(99, 102, 241, 0.2);
1055
+ color: #818cf8;
1056
+ border: 1px solid rgba(99, 102, 241, 0.3);
1057
+ padding: 6px 12px;
1058
+ border-radius: 4px;
1059
+ font-size: 12px;
1060
+ font-weight: 600;
1061
+ cursor: pointer;
1062
+ }
1063
+
1064
+ .instaraw-rpg-reorder-toggle-btn:hover {
1065
+ background: rgba(99, 102, 241, 0.3);
1066
+ border-color: rgba(99, 102, 241, 0.5);
1067
+ }
1068
+
1069
+ .instaraw-rpg-batch-grid {
1070
+ display: grid;
1071
+ grid-template-columns: repeat(auto-fill, minmax(max(200px, calc((100% - 24px) / 3)), 1fr));
1072
+ gap: 12px;
1073
+ }
1074
+
1075
+ @media (max-width: 720px) {
1076
+ .instaraw-rpg-batch-grid {
1077
+ grid-template-columns: 1fr;
1078
+ }
1079
+ }
1080
+
1081
+ .instaraw-rpg-batch-item {
1082
+ position: relative;
1083
+ background: rgba(255, 255, 255, 0.04);
1084
+ border: 1px solid rgba(255, 255, 255, 0.08);
1085
+ border-radius: 4px;
1086
+ padding: 12px;
1087
+ cursor: move;
1088
+ min-height: 220px;
1089
+ display: flex;
1090
+ flex-direction: column;
1091
+ gap: 8px;
1092
+ max-width: 500px;
1093
+ }
1094
+
1095
+ .instaraw-rpg-batch-item:hover {
1096
+ border-color: rgba(99, 102, 241, 0.5);
1097
+ }
1098
+
1099
+ .instaraw-rpg-batch-item.instaraw-rpg-drop-before::before {
1100
+ content: '';
1101
+ position: absolute;
1102
+ top: -7px;
1103
+ left: 0;
1104
+ right: 0;
1105
+ height: 4px;
1106
+ background: #a78bfa;
1107
+ border-radius: 2px;
1108
+ z-index: 1000;
1109
+ pointer-events: none;
1110
+ }
1111
+
1112
+ .instaraw-rpg-batch-item.instaraw-rpg-drop-after::after {
1113
+ content: '';
1114
+ position: absolute;
1115
+ bottom: -7px;
1116
+ left: 0;
1117
+ right: 0;
1118
+ height: 4px;
1119
+ background: #a78bfa;
1120
+ border-radius: 2px;
1121
+ z-index: 1000;
1122
+ pointer-events: none;
1123
+ }
1124
+
1125
+ /* === Batch Card Thumbnails === */
1126
+ .instaraw-rpg-batch-thumbnail {
1127
+ position: relative;
1128
+ width: 100%;
1129
+ height: 180px;
1130
+ border-radius: 4px;
1131
+ overflow: hidden;
1132
+ background: #000000;
1133
+ border: 2px solid rgba(255, 255, 255, 0.1);
1134
+ margin-bottom: 8px;
1135
+ display: flex;
1136
+ align-items: center;
1137
+ justify-content: center;
1138
+ }
1139
+
1140
+ .instaraw-rpg-batch-thumbnail img {
1141
+ width: 100%;
1142
+ height: 100%;
1143
+ object-fit: contain;
1144
+ display: block;
1145
+ }
1146
+
1147
+ .instaraw-rpg-batch-thumbnail-latent {
1148
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a78bfa 100%);
1149
+ }
1150
+
1151
+ /* Aspect ratio preview box for batch cards */
1152
+ .instaraw-rpg-batch-aspect-preview {
1153
+ width: 120px;
1154
+ max-height: 160px; /* Prevent tall portraits from overflowing */
1155
+ background: rgba(255, 255, 255, 0.1);
1156
+ border: 2px solid rgba(255, 255, 255, 0.3);
1157
+ border-radius: 4px;
1158
+ display: flex;
1159
+ align-items: center;
1160
+ justify-content: center;
1161
+ padding: 8px;
1162
+ }
1163
+
1164
+ .instaraw-rpg-batch-aspect-content {
1165
+ text-align: center;
1166
+ color: #ffffff;
1167
+ display: flex;
1168
+ flex-direction: column;
1169
+ align-items: center;
1170
+ gap: 4px;
1171
+ }
1172
+
1173
+ .instaraw-rpg-batch-thumbnail-missing {
1174
+ background: rgba(245, 158, 11, 0.1);
1175
+ border-color: rgba(245, 158, 11, 0.3);
1176
+ border-style: dashed;
1177
+ }
1178
+
1179
+ .instaraw-rpg-batch-thumbnail-placeholder {
1180
+ display: flex;
1181
+ flex-direction: column;
1182
+ align-items: center;
1183
+ justify-content: center;
1184
+ color: #9ca3af;
1185
+ text-align: center;
1186
+ padding: 8px;
1187
+ }
1188
+
1189
+ .instaraw-rpg-batch-thumbnail-index {
1190
+ position: absolute;
1191
+ top: 6px;
1192
+ left: 6px;
1193
+ background: rgba(0, 0, 0, 0.85);
1194
+ color: #ffffff;
1195
+ padding: 4px 8px;
1196
+ border-radius: 4px;
1197
+ font-size: 12px;
1198
+ font-weight: 700;
1199
+ border: 1px solid rgba(255, 255, 255, 0.3);
1200
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
1201
+ z-index: 10;
1202
+ }
1203
+
1204
+
1205
+ .instaraw-rpg-batch-item-header {
1206
+ display: flex;
1207
+ justify-content: space-between;
1208
+ align-items: center;
1209
+ margin-bottom: 8px;
1210
+ }
1211
+
1212
+ .instaraw-rpg-batch-item-number {
1213
+ background: #6366f1;
1214
+ color: #ffffff;
1215
+ padding: 4px 10px;
1216
+ border-radius: 4px;
1217
+ font-size: 11px;
1218
+ font-weight: 600;
1219
+ }
1220
+
1221
+ .instaraw-rpg-source-badge {
1222
+ padding: 4px 8px;
1223
+ border-radius: 4px;
1224
+ font-size: 10px;
1225
+ font-weight: 600;
1226
+ }
1227
+
1228
+ .instaraw-rpg-source-badge.user {
1229
+ background: rgba(255, 255, 255, 0.1);
1230
+ color: rgba(249, 250, 251, 0.7);
1231
+ }
1232
+
1233
+ .instaraw-rpg-source-badge.library {
1234
+ background: rgba(59, 130, 246, 0.2);
1235
+ color: #60a5fa;
1236
+ }
1237
+
1238
+ /* Prompt Meta Info (model/theme at bottom of card) */
1239
+ .instaraw-rpg-prompt-meta {
1240
+ display: flex;
1241
+ flex-direction: column;
1242
+ gap: 4px;
1243
+ margin-top: 10px;
1244
+ padding-top: 8px;
1245
+ border-top: 1px solid rgba(255, 255, 255, 0.06);
1246
+ }
1247
+
1248
+ .instaraw-rpg-prompt-meta-item {
1249
+ font-size: 10px;
1250
+ color: rgba(249, 250, 251, 0.4);
1251
+ font-weight: 400;
1252
+ }
1253
+
1254
+ .instaraw-rpg-id-badge-container {
1255
+ display: flex;
1256
+ align-items: center;
1257
+ gap: 2px;
1258
+ background: rgba(0, 0, 0, 0.3);
1259
+ border-radius: 4px;
1260
+ padding: 2px;
1261
+ }
1262
+
1263
+ .instaraw-rpg-id-badge {
1264
+ padding: 2px 6px;
1265
+ font-size: 9px;
1266
+ font-weight: 500;
1267
+ color: rgba(249, 250, 251, 0.5);
1268
+ font-family: monospace;
1269
+ cursor: help;
1270
+ }
1271
+
1272
+ .instaraw-rpg-id-copy-btn {
1273
+ background: none;
1274
+ border: none;
1275
+ padding: 2px 4px;
1276
+ cursor: pointer;
1277
+ font-size: 11px;
1278
+ opacity: 0.6;
1279
+ border-radius: 2px;
1280
+ }
1281
+
1282
+ .instaraw-rpg-id-copy-btn:hover {
1283
+ opacity: 1;
1284
+ background: rgba(255, 255, 255, 0.1);
1285
+ }
1286
+
1287
+ .instaraw-rpg-copy-prompt-btn {
1288
+ background: none;
1289
+ border: none;
1290
+ padding: 2px 2px;
1291
+ cursor: pointer;
1292
+ font-size: 11px;
1293
+ opacity: 0.6;
1294
+ border-radius: 2px;
1295
+ }
1296
+
1297
+ .instaraw-rpg-copy-prompt-btn:hover {
1298
+ opacity: 1;
1299
+ background: rgba(255, 255, 255, 0.1);
1300
+ }
1301
+
1302
+ .instaraw-rpg-source-badge.from-library {
1303
+ background: rgba(59, 130, 246, 0.2);
1304
+ color: #60a5fa;
1305
+ }
1306
+
1307
+ .instaraw-rpg-source-badge.from-ai {
1308
+ background: rgba(167, 139, 250, 0.2);
1309
+ color: #c4b5fd;
1310
+ }
1311
+
1312
+ .instaraw-rpg-source-badge.from-character {
1313
+ background: rgba(236, 72, 153, 0.2);
1314
+ color: #f9a8d4;
1315
+ }
1316
+
1317
+ /* Repeat Count Status Badges */
1318
+ .instaraw-rpg-repeat-status {
1319
+ font-size: 10px;
1320
+ padding: 3px 8px;
1321
+ border-radius: 4px;
1322
+ font-weight: 600;
1323
+ border: 1px solid;
1324
+ }
1325
+
1326
+ .instaraw-rpg-repeat-match {
1327
+ background: rgba(34, 197, 94, 0.15);
1328
+ color: #4ade80;
1329
+ border-color: rgba(34, 197, 94, 0.3);
1330
+ }
1331
+
1332
+ .instaraw-rpg-repeat-mismatch {
1333
+ background: rgba(245, 158, 11, 0.15);
1334
+ color: #fbbf24;
1335
+ border-color: rgba(245, 158, 11, 0.4);
1336
+ }
1337
+
1338
+ .instaraw-rpg-crop-indicator {
1339
+ position: absolute;
1340
+ bottom: 4px;
1341
+ right: 4px;
1342
+ background: rgba(139, 92, 246, 0.85);
1343
+ color: #ffffff;
1344
+ font-size: 9px;
1345
+ font-weight: 600;
1346
+ padding: 2px 6px;
1347
+ border-radius: 4px;
1348
+ pointer-events: none;
1349
+ text-transform: uppercase;
1350
+ letter-spacing: 0.3px;
1351
+ }
1352
+
1353
+ .instaraw-rpg-batch-delete-btn {
1354
+ background: #ef4444;
1355
+ color: #ffffff;
1356
+ border: none;
1357
+ width: 28px;
1358
+ height: 28px;
1359
+ border-radius: 4px;
1360
+ cursor: pointer;
1361
+ font-weight: 600;
1362
+ font-size: 16px;
1363
+ line-height: 1;
1364
+ display: flex;
1365
+ align-items: center;
1366
+ justify-content: center;
1367
+ }
1368
+
1369
+ .instaraw-rpg-batch-delete-btn:hover {
1370
+ background: #dc2626;
1371
+ }
1372
+
1373
+ .instaraw-rpg-batch-item-content {
1374
+ display: flex;
1375
+ flex-direction: column;
1376
+ gap: 8px;
1377
+ }
1378
+
1379
+ .instaraw-rpg-batch-tags-display {
1380
+ display: flex;
1381
+ flex-wrap: wrap;
1382
+ gap: 4px;
1383
+ padding: 8px;
1384
+ background: rgba(167, 139, 250, 0.1);
1385
+ border: 1px solid rgba(167, 139, 250, 0.2);
1386
+ border-radius: 4px;
1387
+ margin-bottom: 8px;
1388
+ }
1389
+
1390
+ .instaraw-rpg-batch-tags-text {
1391
+ font-size: 12px;
1392
+ line-height: 1.42;
1393
+ color: rgba(249, 250, 251, 0.9);
1394
+ padding: 10px;
1395
+ background: rgba(167, 139, 250, 0.08);
1396
+ border: 1px solid rgba(167, 139, 250, 0.2);
1397
+ border-radius: 4px;
1398
+ margin-bottom: 12px;
1399
+ font-family: 'Inter', monospace;
1400
+ white-space: pre-wrap;
1401
+ word-break: break-word;
1402
+ }
1403
+
1404
+ /* SDXL mode now uses standard .instaraw-rpg-positive-textarea */
1405
+
1406
+ .instaraw-rpg-batch-item-content label,
1407
+ .instaraw-rpg-thumbnail-label {
1408
+ font-size: 11px;
1409
+ font-weight: 500;
1410
+ color: rgba(249, 250, 251, 0.7);
1411
+ text-transform: uppercase;
1412
+ display: block;
1413
+ }
1414
+
1415
+ .instaraw-rpg-prompt-textarea {
1416
+ width: 100%;
1417
+ background: rgba(255, 255, 255, 0.06);
1418
+ border: 1px solid rgba(255, 255, 255, 0.1);
1419
+ color: #f9fafb;
1420
+ padding: 8px 10px;
1421
+ border-radius: 4px;
1422
+ font-size: 12px;
1423
+ font-family: 'Inter', monospace;
1424
+ outline: none;
1425
+ resize: vertical;
1426
+ line-height: 1.42;
1427
+ min-height: 60px;
1428
+ max-height: 200px;
1429
+ overflow-y: auto;
1430
+ }
1431
+
1432
+ .instaraw-rpg-prompt-textarea:focus {
1433
+ border-color: #a78bfa;
1434
+ background: rgba(255, 255, 255, 0.08);
1435
+ outline: none;
1436
+ box-shadow: 0 0 0 1px #a78bfa;
1437
+ }
1438
+
1439
+ /* Batch queue textareas - allow unlimited resize to view full prompts */
1440
+ .instaraw-rpg-batch-item .instaraw-rpg-prompt-textarea,
1441
+ .instaraw-rpg-positive-textarea,
1442
+ .instaraw-rpg-negative-textarea {
1443
+ max-height: none;
1444
+ }
1445
+
1446
+ .instaraw-rpg-negative-toggle {
1447
+ display: flex;
1448
+ align-items: center;
1449
+ }
1450
+
1451
+ .instaraw-rpg-batch-item-controls {
1452
+ display: flex;
1453
+ align-items: center;
1454
+ gap: 8px;
1455
+ }
1456
+
1457
+ .instaraw-rpg-batch-item-controls label {
1458
+ color: rgba(249, 250, 251, 0.6);
1459
+ font-size: 11px;
1460
+ font-weight: 500;
1461
+ text-transform: none;
1462
+ }
1463
+
1464
+ .instaraw-rpg-repeat-input {
1465
+ width: 60px;
1466
+ background: rgba(255, 255, 255, 0.06);
1467
+ border: 1px solid rgba(255, 255, 255, 0.1);
1468
+ color: #f9fafb;
1469
+ padding: 6px;
1470
+ border-radius: 4px;
1471
+ text-align: center;
1472
+ font-size: 12px;
1473
+ font-weight: 600;
1474
+ outline: none;
1475
+ }
1476
+
1477
+ .instaraw-rpg-repeat-input:hover {
1478
+ background: rgba(255, 255, 255, 0.08);
1479
+ border-color: #818cf8;
1480
+ }
1481
+
1482
+ .instaraw-rpg-repeat-input:focus {
1483
+ border-color: #a78bfa;
1484
+ outline: none;
1485
+ box-shadow: 0 0 0 1px #a78bfa;
1486
+ }
1487
+
1488
+ .instaraw-rpg-seed-input:focus {
1489
+ border-color: #a78bfa !important;
1490
+ outline: none !important;
1491
+ box-shadow: 0 0 0 1px #a78bfa !important;
1492
+ }
1493
+
1494
+ .instaraw-rpg-random-count-input:focus {
1495
+ border-color: #a78bfa !important;
1496
+ outline: none !important;
1497
+ box-shadow: 0 0 0 1px #a78bfa !important;
1498
+ }
1499
+
1500
+ .instaraw-rpg-batch-item-tags {
1501
+ display: flex;
1502
+ flex-wrap: wrap;
1503
+ gap: 4px;
1504
+ margin-top: 4px;
1505
+ }
1506
+
1507
+ /* === Image Preview Section === */
1508
+ .instaraw-rpg-image-preview-section {
1509
+ background: rgba(255, 255, 255, 0.03);
1510
+ border: 1px solid rgba(255, 255, 255, 0.06);
1511
+ border-radius: 4px;
1512
+ padding: 16px;
1513
+ margin-bottom: 12px;
1514
+ }
1515
+
1516
+ .instaraw-rpg-image-preview-empty {
1517
+ text-align: center;
1518
+ padding: 32px 16px;
1519
+ padding-top: 10px;
1520
+ }
1521
+
1522
+ .instaraw-rpg-image-preview-empty p {
1523
+ margin: 4px 0;
1524
+ font-size: 13px;
1525
+ color: rgba(249, 250, 251, 0.6);
1526
+ }
1527
+
1528
+ .instaraw-rpg-image-preview-header {
1529
+ display: flex;
1530
+ justify-content: space-between;
1531
+ margin-bottom: 10px;
1532
+ font-size: 12px;
1533
+ color: rgba(249, 250, 251, 0.8);
1534
+ }
1535
+
1536
+ .instaraw-rpg-validation-badge {
1537
+ padding: 6px 12px;
1538
+ border-radius: 4px;
1539
+ font-weight: 600;
1540
+ }
1541
+
1542
+ .instaraw-rpg-validation-match {
1543
+ background: rgba(16, 185, 129, 0.2);
1544
+ color: #10b981;
1545
+ }
1546
+
1547
+ .instaraw-rpg-validation-mismatch {
1548
+ background: rgba(251, 191, 36, 0.2);
1549
+ color: #fbbf24;
1550
+ }
1551
+
1552
+ .instaraw-rpg-image-preview-grid {
1553
+ display: grid;
1554
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
1555
+ gap: 12px; /* Increased from 8px for better spacing */
1556
+ }
1557
+
1558
+ .instaraw-rpg-preview-thumb {
1559
+ position: relative; /* For index badge positioning */
1560
+ width: 100%;
1561
+ height: 120px; /* Fixed height container */
1562
+ border-radius: 4px;
1563
+ overflow: hidden;
1564
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a78bfa 100%); /* Purple gradient like latents */
1565
+ border: 2px solid rgba(139, 92, 246, 0.4);
1566
+ display: flex;
1567
+ align-items: center;
1568
+ justify-content: center;
1569
+ }
1570
+
1571
+ /* Aspect ratio box inside preview thumb (for IMG2IMG) */
1572
+ .instaraw-rpg-preview-thumb .instaraw-rpg-preview-aspect-box {
1573
+ width: 90px;
1574
+ max-height: 105px; /* Prevent tall portraits from overflowing */
1575
+ border-radius: 4px;
1576
+ overflow: hidden;
1577
+ display: flex;
1578
+ align-items: center;
1579
+ justify-content: center;
1580
+ }
1581
+
1582
+ .instaraw-rpg-preview-thumb img {
1583
+ width: 100%;
1584
+ height: 100%;
1585
+ object-fit: cover; /* Fill the aspect ratio box */
1586
+ display: block;
1587
+ }
1588
+
1589
+ .instaraw-rpg-preview-more {
1590
+ display: flex;
1591
+ align-items: center;
1592
+ justify-content: center;
1593
+ background: rgba(167, 139, 250, 0.2);
1594
+ color: #a78bfa;
1595
+ font-size: 11px;
1596
+ font-weight: 600;
1597
+ border-radius: 4px;
1598
+ }
1599
+
1600
+ /* === Footer Stats === */
1601
+ .instaraw-rpg-footer {
1602
+ background: rgba(0, 0, 0, 0.2);
1603
+ padding: 12px 16px;
1604
+ border-radius: 4px;
1605
+ border: 1px solid rgba(255, 255, 255, 0.06);
1606
+ }
1607
+
1608
+ .instaraw-rpg-stats {
1609
+ display: flex;
1610
+ align-items: center;
1611
+ gap: 16px;
1612
+ flex-wrap: wrap;
1613
+ }
1614
+
1615
+ .instaraw-rpg-stat-badge {
1616
+ background: rgba(167, 139, 250, 0.2);
1617
+ color: #c4b5fd;
1618
+ padding: 6px 14px;
1619
+ border-radius: 4px;
1620
+ font-size: 12px;
1621
+ font-weight: 600;
1622
+ white-space: nowrap;
1623
+ }
1624
+
1625
+ .instaraw-rpg-stat-label {
1626
+ font-size: 11px;
1627
+ color: rgba(249, 250, 251, 0.7);
1628
+ font-weight: 500;
1629
+ white-space: nowrap;
1630
+ }
1631
+
1632
+ /* === Scrollbar Styling === */
1633
+ .instaraw-rpg-main-panel::-webkit-scrollbar,
1634
+ .instaraw-rpg-batch-panel::-webkit-scrollbar,
1635
+ .instaraw-rpg-inspiration-list::-webkit-scrollbar,
1636
+ .instaraw-rpg-creative-preview-list::-webkit-scrollbar,
1637
+ .instaraw-rpg-character-preview-list::-webkit-scrollbar {
1638
+ width: 8px;
1639
+ }
1640
+
1641
+ .instaraw-rpg-main-panel::-webkit-scrollbar-track,
1642
+ .instaraw-rpg-batch-panel::-webkit-scrollbar-track,
1643
+ .instaraw-rpg-inspiration-list::-webkit-scrollbar-track,
1644
+ .instaraw-rpg-creative-preview-list::-webkit-scrollbar-track,
1645
+ .instaraw-rpg-character-preview-list::-webkit-scrollbar-track {
1646
+ background: rgba(0, 0, 0, 0.2);
1647
+ border-radius: 4px;
1648
+ }
1649
+
1650
+ .instaraw-rpg-main-panel::-webkit-scrollbar-thumb,
1651
+ .instaraw-rpg-batch-panel::-webkit-scrollbar-thumb,
1652
+ .instaraw-rpg-inspiration-list::-webkit-scrollbar-thumb,
1653
+ .instaraw-rpg-creative-preview-list::-webkit-scrollbar-thumb,
1654
+ .instaraw-rpg-character-preview-list::-webkit-scrollbar-thumb {
1655
+ background: rgba(167, 139, 250, 0.3);
1656
+ border-radius: 4px;
1657
+ }
1658
+
1659
+ .instaraw-rpg-main-panel::-webkit-scrollbar-thumb:hover,
1660
+ .instaraw-rpg-batch-panel::-webkit-scrollbar-thumb:hover,
1661
+ .instaraw-rpg-inspiration-list::-webkit-scrollbar-thumb:hover,
1662
+ .instaraw-rpg-creative-preview-list::-webkit-scrollbar-thumb:hover,
1663
+ .instaraw-rpg-character-preview-list::-webkit-scrollbar-thumb:hover {
1664
+ background: rgba(167, 139, 250, 0.5);
1665
+ }
1666
+
1667
+ /* === Unified Generate Tab Styles === */
1668
+ .instaraw-rpg-generate-unified {
1669
+ display: flex;
1670
+ flex-direction: column;
1671
+ gap: 16px;
1672
+ }
1673
+
1674
+ .instaraw-rpg-section {
1675
+ background: rgba(255, 255, 255, 0.02);
1676
+ border: 1px solid rgba(255, 255, 255, 0.08);
1677
+ border-radius: 4px;
1678
+ padding: 14px;
1679
+ }
1680
+
1681
+ .instaraw-rpg-section-header {
1682
+ display: flex;
1683
+ align-items: center;
1684
+ justify-content: space-between;
1685
+ margin-bottom: 12px;
1686
+ gap: 10px;
1687
+ }
1688
+ .instaraw-rpg-section-header-no-margin-bottom {
1689
+ margin-bottom: 0px;
1690
+ }
1691
+
1692
+ .instaraw-rpg-section-label {
1693
+ font-size: 13px;
1694
+ font-weight: 600;
1695
+ color: #e5e7eb;
1696
+ text-transform: uppercase;
1697
+ letter-spacing: 0.5px;
1698
+ }
1699
+
1700
+ .instaraw-rpg-mode-badge {
1701
+ display: inline-flex;
1702
+ align-items: center;
1703
+ gap: 6px;
1704
+ padding: 6px 14px;
1705
+ font-size: 12px;
1706
+ font-weight: 700;
1707
+ border-radius: 4px;
1708
+ text-transform: uppercase;
1709
+ letter-spacing: 0.5px;
1710
+ }
1711
+
1712
+ .instaraw-rpg-mode-img2img {
1713
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
1714
+ border: 2px solid #60a5fa;
1715
+ color: #ffffff;
1716
+ }
1717
+
1718
+ .instaraw-rpg-mode-txt2img {
1719
+ background: linear-gradient(135deg, #7c3aed 0%, #6d28d9 100%);
1720
+ border: 2px solid #a78bfa;
1721
+ color: #ffffff;
1722
+ }
1723
+
1724
+ .instaraw-rpg-hint-badge {
1725
+ padding: 4px 10px;
1726
+ background: rgba(255, 255, 255, 0.06);
1727
+ border: 1px solid rgba(255, 255, 255, 0.1);
1728
+ border-radius: 4px;
1729
+ font-size: 11px;
1730
+ color: #d1d5db;
1731
+ }
1732
+
1733
+ .instaraw-rpg-hint-text {
1734
+ font-size: 11px;
1735
+ color: #9ca3af;
1736
+ font-style: italic;
1737
+ }
1738
+
1739
+ .instaraw-rpg-character-section {
1740
+ margin-top: 12px;
1741
+ padding-top: 12px;
1742
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
1743
+ }
1744
+
1745
+ .instaraw-rpg-character-actions {
1746
+ display: flex;
1747
+ gap: 10px;
1748
+ margin-top: 10px;
1749
+ }
1750
+
1751
+ .instaraw-rpg-character-preview {
1752
+ margin-top: 12px;
1753
+ padding: 10px;
1754
+ background: rgba(139, 92, 246, 0.1);
1755
+ border: 1px solid rgba(139, 92, 246, 0.3);
1756
+ border-radius: 4px;
1757
+ }
1758
+
1759
+ .instaraw-rpg-character-desc-text {
1760
+ margin: 6px 0 0 0;
1761
+ font-size: 12px;
1762
+ color: #d1d5db;
1763
+ line-height: 1.42;
1764
+ }
1765
+
1766
+ .instaraw-rpg-checkbox-grid {
1767
+ display: grid;
1768
+ grid-template-columns: repeat(2, 1fr);
1769
+ gap: 10px;
1770
+ margin-top: 10px;
1771
+ }
1772
+
1773
+ .instaraw-rpg-checkbox-label {
1774
+ display: flex;
1775
+ align-items: center;
1776
+ gap: 8px;
1777
+ cursor: pointer;
1778
+ padding: 8px;
1779
+ background: rgba(255, 255, 255, 0.03);
1780
+ border: 1px solid rgba(255, 255, 255, 0.08);
1781
+ border-radius: 4px;
1782
+ }
1783
+
1784
+ .instaraw-rpg-checkbox-label:hover {
1785
+ background: rgba(255, 255, 255, 0.06);
1786
+ border-color: rgba(139, 92, 246, 0.4);
1787
+ }
1788
+
1789
+ .instaraw-rpg-checkbox-label input[type="checkbox"]:checked + span {
1790
+ color: #a78bfa;
1791
+ font-weight: 600;
1792
+ }
1793
+
1794
+ .instaraw-rpg-checkbox-row {
1795
+ display: flex;
1796
+ gap: 16px;
1797
+ flex-wrap: wrap;
1798
+ margin-top: 10px;
1799
+ }
1800
+
1801
+ .instaraw-rpg-txt2img-settings {
1802
+ margin-top: 12px;
1803
+ }
1804
+
1805
+ .instaraw-rpg-user-text-input {
1806
+ width: 100%;
1807
+ background: rgba(255, 255, 255, 0.06);
1808
+ border: 1px solid rgba(255, 255, 255, 0.1);
1809
+ color: #f9fafb;
1810
+ padding: 10px 12px;
1811
+ border-radius: 4px;
1812
+ font-size: 13px;
1813
+ font-family: 'Inter', sans-serif;
1814
+ outline: none;
1815
+ resize: vertical;
1816
+ line-height: 1.42;
1817
+ }
1818
+
1819
+ .instaraw-rpg-user-text-input:focus {
1820
+ border-color: #a78bfa;
1821
+ background: rgba(255, 255, 255, 0.08);
1822
+ }
1823
+
1824
+ .instaraw-rpg-library-controls {
1825
+ margin-top: 12px;
1826
+ }
1827
+
1828
+ .instaraw-rpg-control-row {
1829
+ display: flex;
1830
+ align-items: center;
1831
+ gap: 10px;
1832
+ margin-top: 8px;
1833
+ }
1834
+
1835
+ .instaraw-rpg-control-row span {
1836
+ font-size: 13px;
1837
+ color: #d1d5db;
1838
+ }
1839
+
1840
+ .instaraw-rpg-btn-text {
1841
+ background: transparent;
1842
+ border: 1px solid rgba(139, 92, 246, 0.4);
1843
+ color: #a78bfa;
1844
+ padding: 6px 12px;
1845
+ font-size: 12px;
1846
+ border-radius: 4px;
1847
+ cursor: pointer;
1848
+ }
1849
+
1850
+ .instaraw-rpg-btn-text:hover {
1851
+ background: rgba(139, 92, 246, 0.2);
1852
+ border-color: #a78bfa;
1853
+ color: #c4b5fd;
1854
+ }
1855
+
1856
+ .instaraw-rpg-generate-unified-btn {
1857
+ width: 100%;
1858
+ margin-top: 8px;
1859
+ }
1860
+
1861
+ /* === Generation Progress Section === */
1862
+ .instaraw-rpg-generation-progress {
1863
+ margin-top: 12px;
1864
+ padding: 14px;
1865
+ background: rgba(99, 102, 241, 0.08);
1866
+ border: 1px solid rgba(99, 102, 241, 0.3);
1867
+ border-radius: 4px;
1868
+ }
1869
+
1870
+ .instaraw-rpg-progress-header {
1871
+ display: flex;
1872
+ justify-content: space-between;
1873
+ align-items: center;
1874
+ margin-bottom: 16px;
1875
+ }
1876
+
1877
+ .instaraw-rpg-progress-header h4 {
1878
+ margin: 0;
1879
+ color: #e5e7eb;
1880
+ font-size: 14px;
1881
+ font-weight: 600;
1882
+ }
1883
+
1884
+ .instaraw-rpg-progress-items {
1885
+ display: flex;
1886
+ flex-direction: column;
1887
+ gap: 12px;
1888
+ }
1889
+
1890
+ .instaraw-rpg-progress-item {
1891
+ background: rgba(255, 255, 255, 0.04);
1892
+ border: 1px solid rgba(255, 255, 255, 0.1);
1893
+ border-radius: 4px;
1894
+ padding: 12px;
1895
+ }
1896
+
1897
+ .instaraw-rpg-progress-item.success {
1898
+ border-color: rgba(34, 197, 94, 0.4);
1899
+ background: rgba(34, 197, 94, 0.08);
1900
+ }
1901
+
1902
+ .instaraw-rpg-progress-item.error {
1903
+ border-color: rgba(239, 68, 68, 0.4);
1904
+ background: rgba(239, 68, 68, 0.08);
1905
+ }
1906
+
1907
+ .instaraw-rpg-progress-item.in-progress {
1908
+ border-color: rgba(99, 102, 241, 0.5);
1909
+ background: rgba(99, 102, 241, 0.1);
1910
+ }
1911
+
1912
+ .instaraw-rpg-progress-item-header {
1913
+ display: flex;
1914
+ justify-content: space-between;
1915
+ align-items: center;
1916
+ margin-bottom: 8px;
1917
+ gap: 8px;
1918
+ }
1919
+
1920
+ .instaraw-rpg-progress-images {
1921
+ display: flex;
1922
+ gap: 4px;
1923
+ flex: 1;
1924
+ }
1925
+
1926
+ .instaraw-rpg-progress-item-label {
1927
+ font-size: 12px;
1928
+ font-weight: 600;
1929
+ color: #e5e7eb;
1930
+ }
1931
+
1932
+ .instaraw-rpg-progress-item-status {
1933
+ font-size: 11px;
1934
+ padding: 3px 8px;
1935
+ border-radius: 4px;
1936
+ font-weight: 600;
1937
+ }
1938
+
1939
+ .instaraw-rpg-progress-item-status.pending {
1940
+ background: rgba(156, 163, 175, 0.2);
1941
+ color: #9ca3af;
1942
+ }
1943
+
1944
+ .instaraw-rpg-progress-item-status.in-progress {
1945
+ background: rgba(99, 102, 241, 0.3);
1946
+ color: #818cf8;
1947
+ animation: instaraw-rpg-pulse 2s ease-in-out infinite;
1948
+ }
1949
+
1950
+ .instaraw-rpg-progress-item-status.success {
1951
+ background: rgba(34, 197, 94, 0.3);
1952
+ color: #22c55e;
1953
+ }
1954
+
1955
+ .instaraw-rpg-progress-item-status.error {
1956
+ background: rgba(239, 68, 68, 0.3);
1957
+ color: #ef4444;
1958
+ }
1959
+
1960
+ .instaraw-rpg-progress-item-status.retrying {
1961
+ background: rgba(245, 158, 11, 0.3);
1962
+ color: #fbbf24;
1963
+ animation: instaraw-rpg-pulse 1.5s ease-in-out infinite;
1964
+ }
1965
+
1966
+ .instaraw-rpg-progress-item-bar {
1967
+ width: 100%;
1968
+ height: 4px;
1969
+ background: rgba(255, 255, 255, 0.08);
1970
+ border-radius: 4px;
1971
+ overflow: hidden;
1972
+ margin-top: 10px;
1973
+ position: relative;
1974
+ }
1975
+
1976
+ .instaraw-rpg-progress-item-fill {
1977
+ height: 100%;
1978
+ background: linear-gradient(90deg, #6366f1, #a78bfa);
1979
+ position: relative;
1980
+ overflow: hidden;
1981
+ }
1982
+
1983
+ .instaraw-rpg-progress-item-fill.animating {
1984
+ background: linear-gradient(90deg, #a78bfa, #818cf8, #6366f1, #818cf8, #a78bfa);
1985
+ background-size: 300% 100%;
1986
+ animation: instaraw-rpg-progress-shimmer 2s ease-in-out infinite;
1987
+ border-radius: 4px;
1988
+ box-shadow: 0 0 12px rgba(139, 92, 246, 0.5), 0 0 24px rgba(99, 102, 241, 0.2);
1989
+ position: relative;
1990
+ }
1991
+
1992
+ .instaraw-rpg-progress-item-fill.animating::after {
1993
+ content: '';
1994
+ position: absolute;
1995
+ top: 0;
1996
+ left: -100%;
1997
+ width: 100%;
1998
+ height: 100%;
1999
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
2000
+ animation: instaraw-rpg-progress-shine 2.5s ease-in-out infinite;
2001
+ }
2002
+
2003
+ @keyframes instaraw-rpg-progress-shine {
2004
+ 0% { left: -100%; }
2005
+ 50%, 100% { left: 200%; }
2006
+ }
2007
+
2008
+ .instaraw-rpg-progress-item-message {
2009
+ font-size: 11px;
2010
+ color: rgba(249, 250, 251, 0.7);
2011
+ margin-top: 6px;
2012
+ font-style: italic;
2013
+ }
2014
+
2015
+ .instaraw-rpg-progress-item-message.error {
2016
+ color: #fca5a5;
2017
+ }
2018
+
2019
+ @keyframes instaraw-rpg-pulse {
2020
+ 0%, 100% { opacity: 1; }
2021
+ 50% { opacity: 0.6; }
2022
+ }
2023
+
2024
+ @keyframes instaraw-rpg-progress-shimmer {
2025
+ 0% { background-position: 200% 0; }
2026
+ 100% { background-position: -200% 0; }
2027
+ }
2028
+
2029
+ .instaraw-rpg-generate-preview {
2030
+ margin-top: 16px;
2031
+ padding: 14px;
2032
+ background: rgba(139, 92, 246, 0.1);
2033
+ border: 1px solid rgba(139, 92, 246, 0.3);
2034
+ border-radius: 4px;
2035
+ }
2036
+
2037
+ .instaraw-rpg-generate-preview h4 {
2038
+ margin: 0 0 12px 0;
2039
+ color: #e5e7eb;
2040
+ font-size: 14px;
2041
+ }
2042
+
2043
+ .instaraw-rpg-generate-preview-list {
2044
+ max-height: 300px;
2045
+ overflow-y: auto;
2046
+ margin-bottom: 12px;
2047
+ }
2048
+
2049
+ .instaraw-rpg-generate-preview button {
2050
+ margin-right: 10px;
2051
+ }
2052
+
2053
+ /* === Mode Display Updates === */
2054
+ .instaraw-rpg-mode-indicator-container {
2055
+ display: flex;
2056
+ align-items: center;
2057
+ gap: 8px;
2058
+ flex: 1;
2059
+ }
2060
+
2061
+ .instaraw-rpg-mode-source {
2062
+ font-size: 11px;
2063
+ color: #9ca3af;
2064
+ font-style: italic;
2065
+ padding: 4px 10px;
2066
+ background: rgba(255, 255, 255, 0.05);
2067
+ border-radius: 4px;
2068
+ }
2069
+
2070
+ /* Reuse mode badges from Generate tab */
2071
+ .instaraw-rpg-mode-card .instaraw-rpg-mode-badge {
2072
+ flex: 0 0 auto;
2073
+ }
2074
+
2075
+ /* === Latent Preview Styles === */
2076
+ .instaraw-rpg-preview-latent {
2077
+ position: relative;
2078
+ width: 100%;
2079
+ height: 120px; /* Fixed height container (same as AIL) */
2080
+ background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a78bfa 100%);
2081
+ border-radius: 4px;
2082
+ display: flex;
2083
+ align-items: center;
2084
+ justify-content: center;
2085
+ overflow: visible; /* Changed from hidden to show badges */
2086
+ border: 2px solid rgba(139, 92, 246, 0.4);
2087
+ }
2088
+
2089
+ /* Aspect ratio preview box inside latent card */
2090
+ .instaraw-rpg-preview-aspect-box {
2091
+ width: 80px;
2092
+ max-height: 105px; /* Prevent tall portraits from overflowing */
2093
+ background: rgba(255, 255, 255, 0.1);
2094
+ border: 2px solid rgba(255, 255, 255, 0.3);
2095
+ border-radius: 4px;
2096
+ display: flex;
2097
+ align-items: center;
2098
+ justify-content: center;
2099
+ padding: 6px;
2100
+ }
2101
+
2102
+ .instaraw-rpg-preview-aspect-content {
2103
+ text-align: center;
2104
+ color: #ffffff;
2105
+ display: flex;
2106
+ flex-direction: column;
2107
+ align-items: center;
2108
+ gap: 2px;
2109
+ }
2110
+
2111
+ .instaraw-rpg-latent-content {
2112
+ display: flex;
2113
+ flex-direction: column;
2114
+ align-items: center;
2115
+ justify-content: center;
2116
+ color: #ffffff;
2117
+ text-align: center;
2118
+ padding: 4px;
2119
+ width: 100%;
2120
+ height: 100%;
2121
+ }
2122
+
2123
+ .instaraw-rpg-preview-repeat {
2124
+ position: absolute;
2125
+ top: 4px;
2126
+ right: 4px;
2127
+ background: rgba(0, 0, 0, 0.85);
2128
+ color: #ffffff;
2129
+ padding: 2px 6px;
2130
+ border-radius: 4px;
2131
+ font-size: 10px;
2132
+ font-weight: 700;
2133
+ border: 1px solid rgba(255, 255, 255, 0.3);
2134
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
2135
+ }
2136
+
2137
+ /* === Index Number Badges (Preview Section) === */
2138
+ .instaraw-rpg-preview-index {
2139
+ position: absolute;
2140
+ top: 4px;
2141
+ left: 4px;
2142
+ background: rgba(0, 0, 0, 0.85);
2143
+ color: #ffffff;
2144
+ padding: 3px 7px;
2145
+ border-radius: 4px;
2146
+ font-size: 11px;
2147
+ font-weight: 700;
2148
+ border: 1px solid rgba(255, 255, 255, 0.3);
2149
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
2150
+ z-index: 10;
2151
+ }
2152
+
2153
+ .instaraw-rpg-preview-aspect-label {
2154
+ position: absolute;
2155
+ bottom: 4px;
2156
+ right: 4px;
2157
+ background: rgba(0, 0, 0, 0.85);
2158
+ color: #ffffff;
2159
+ padding: 3px 7px;
2160
+ border-radius: 4px;
2161
+ font-size: 10px;
2162
+ font-weight: 600;
2163
+ border: 1px solid rgba(255, 255, 255, 0.2);
2164
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
2165
+ z-index: 10;
2166
+ }
2167
+
2168
+ /* === Generation Modal === */
2169
+ .instaraw-rpg-generation-modal-overlay {
2170
+ position: fixed;
2171
+ top: 0;
2172
+ left: 0;
2173
+ right: 0;
2174
+ bottom: 0;
2175
+ background: rgba(0, 0, 0, 0.8);
2176
+ display: flex;
2177
+ align-items: center;
2178
+ justify-content: center;
2179
+ z-index: 10000;
2180
+ padding: 20px;
2181
+ }
2182
+
2183
+ .instaraw-rpg-generation-modal {
2184
+ background: #0d0f12;
2185
+ border: 1px solid rgba(255, 255, 255, 0.12);
2186
+ border-radius: 8px;
2187
+ padding: 24px;
2188
+ max-width: 600px;
2189
+ width: 100%;
2190
+ max-height: 80vh;
2191
+ overflow-y: auto;
2192
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
2193
+ }
2194
+
2195
+ .instaraw-rpg-generation-modal-header {
2196
+ display: flex;
2197
+ justify-content: space-between;
2198
+ align-items: center;
2199
+ margin-bottom: 20px;
2200
+ padding-bottom: 16px;
2201
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
2202
+ }
2203
+
2204
+ .instaraw-rpg-generation-modal-title {
2205
+ font-size: 18px;
2206
+ font-weight: 600;
2207
+ color: #f9fafb;
2208
+ margin: 0;
2209
+ }
2210
+
2211
+ .instaraw-rpg-generation-modal-close {
2212
+ background: none;
2213
+ border: none;
2214
+ color: rgba(249, 250, 251, 0.6);
2215
+ font-size: 24px;
2216
+ cursor: pointer;
2217
+ padding: 0;
2218
+ width: 32px;
2219
+ height: 32px;
2220
+ display: flex;
2221
+ align-items: center;
2222
+ justify-content: center;
2223
+ border-radius: 4px;
2224
+ }
2225
+
2226
+ .instaraw-rpg-generation-modal-close:hover {
2227
+ background: rgba(255, 255, 255, 0.08);
2228
+ color: #f9fafb;
2229
+ }
2230
+
2231
+ .instaraw-rpg-generation-modal-overall {
2232
+ margin-bottom: 20px;
2233
+ padding: 16px;
2234
+ background: rgba(255, 255, 255, 0.03);
2235
+ border-radius: 6px;
2236
+ border: 1px solid rgba(255, 255, 255, 0.08);
2237
+ }
2238
+
2239
+ .instaraw-rpg-generation-modal-overall-text {
2240
+ font-size: 14px;
2241
+ color: rgba(249, 250, 251, 0.8);
2242
+ margin-bottom: 8px;
2243
+ }
2244
+
2245
+ .instaraw-rpg-generation-modal-overall-bar {
2246
+ width: 100%;
2247
+ height: 8px;
2248
+ background: rgba(255, 255, 255, 0.08);
2249
+ border-radius: 4px;
2250
+ overflow: hidden;
2251
+ }
2252
+
2253
+ .instaraw-rpg-generation-modal-overall-fill {
2254
+ height: 100%;
2255
+ background: linear-gradient(90deg, #8b5cf6, #a78bfa);
2256
+ border-radius: 4px;
2257
+ }
2258
+
2259
+ .instaraw-rpg-generation-modal-list {
2260
+ display: flex;
2261
+ flex-direction: column;
2262
+ gap: 12px;
2263
+ }
2264
+
2265
+ .instaraw-rpg-generation-item {
2266
+ display: flex;
2267
+ align-items: center;
2268
+ gap: 12px;
2269
+ padding: 12px;
2270
+ background: rgba(255, 255, 255, 0.03);
2271
+ border-radius: 6px;
2272
+ border: 1px solid rgba(255, 255, 255, 0.08);
2273
+ }
2274
+
2275
+ .instaraw-rpg-generation-item-index {
2276
+ font-size: 14px;
2277
+ font-weight: 600;
2278
+ color: rgba(249, 250, 251, 0.6);
2279
+ min-width: 40px;
2280
+ }
2281
+
2282
+ .instaraw-rpg-generation-item-status {
2283
+ flex: 1;
2284
+ display: flex;
2285
+ align-items: center;
2286
+ gap: 8px;
2287
+ }
2288
+
2289
+ .instaraw-rpg-generation-item-icon {
2290
+ font-size: 16px;
2291
+ line-height: 1;
2292
+ }
2293
+
2294
+ .instaraw-rpg-generation-item-text {
2295
+ font-size: 13px;
2296
+ color: rgba(249, 250, 251, 0.8);
2297
+ }
2298
+
2299
+ .instaraw-rpg-generation-item.status-pending {
2300
+ border-color: rgba(156, 163, 175, 0.3);
2301
+ }
2302
+
2303
+ .instaraw-rpg-generation-item.status-pending .instaraw-rpg-generation-item-icon {
2304
+ color: #9ca3af;
2305
+ }
2306
+
2307
+ .instaraw-rpg-generation-item.status-generating {
2308
+ border-color: rgba(139, 92, 246, 0.5);
2309
+ background: rgba(139, 92, 246, 0.08);
2310
+ }
2311
+
2312
+ .instaraw-rpg-generation-item.status-generating .instaraw-rpg-generation-item-icon {
2313
+ color: #a78bfa;
2314
+ animation: spin 1s linear infinite;
2315
+ }
2316
+
2317
+ .instaraw-rpg-generation-item.status-success {
2318
+ border-color: rgba(34, 197, 94, 0.5);
2319
+ background: rgba(34, 197, 94, 0.08);
2320
+ }
2321
+
2322
+ .instaraw-rpg-generation-item.status-success .instaraw-rpg-generation-item-icon {
2323
+ color: #22c55e;
2324
+ }
2325
+
2326
+ .instaraw-rpg-generation-item.status-throttled {
2327
+ border-color: rgba(251, 191, 36, 0.5);
2328
+ background: rgba(251, 191, 36, 0.08);
2329
+ }
2330
+
2331
+ .instaraw-rpg-generation-item.status-throttled .instaraw-rpg-generation-item-icon {
2332
+ color: #fbbf24;
2333
+ }
2334
+
2335
+ .instaraw-rpg-generation-item.status-error {
2336
+ border-color: rgba(239, 68, 68, 0.5);
2337
+ background: rgba(239, 68, 68, 0.08);
2338
+ }
2339
+
2340
+ .instaraw-rpg-generation-item.status-error .instaraw-rpg-generation-item-icon {
2341
+ color: #ef4444;
2342
+ }
2343
+
2344
+ @keyframes spin {
2345
+ from {
2346
+ transform: rotate(0deg);
2347
+ }
2348
+ to {
2349
+ transform: rotate(360deg);
2350
+ }
2351
+ }
2352
+
2353
+ .instaraw-rpg-generation-modal-footer {
2354
+ margin-top: 20px;
2355
+ padding-top: 16px;
2356
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
2357
+ display: flex;
2358
+ justify-content: flex-end;
2359
+ gap: 12px;
2360
+ }
2361
+
2362
+ .instaraw-rpg-generation-modal-btn {
2363
+ padding: 8px 16px;
2364
+ border-radius: 4px;
2365
+ font-size: 13px;
2366
+ font-weight: 500;
2367
+ cursor: pointer;
2368
+ border: none;
2369
+ }
2370
+
2371
+ .instaraw-rpg-generation-modal-btn-primary {
2372
+ background: #8b5cf6;
2373
+ color: white;
2374
+ }
2375
+
2376
+ .instaraw-rpg-generation-modal-btn-primary:hover:not(:disabled) {
2377
+ background: #7c3aed;
2378
+ }
2379
+
2380
+ .instaraw-rpg-generation-modal-btn-primary:disabled {
2381
+ opacity: 0.5;
2382
+ cursor: not-allowed;
2383
+ }
2384
+
2385
+ .instaraw-rpg-generation-modal-btn-secondary {
2386
+ background: rgba(255, 255, 255, 0.08);
2387
+ color: rgba(249, 250, 251, 0.9);
2388
+ }
2389
+
2390
+ .instaraw-rpg-generation-modal-btn-secondary:hover {
2391
+ background: rgba(255, 255, 255, 0.12);
2392
+ }
2393
+
2394
+ /* Generated prompts badge */
2395
+ .instaraw-rpg-source-badge.generated {
2396
+ background: rgba(139, 92, 246, 0.2);
2397
+ color: #a78bfa;
2398
+ }
2399
+
2400
+ /* === Expression Control Styles === */
2401
+ .instaraw-rpg-expressions-section {
2402
+ margin-top: 12px;
2403
+ }
2404
+
2405
+ .instaraw-rpg-expressions-grid {
2406
+ display: grid;
2407
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
2408
+ gap: 8px;
2409
+ margin-bottom: 8px;
2410
+ }
2411
+
2412
+ .instaraw-rpg-expression-toggle {
2413
+ display: flex !important;
2414
+ align-items: center;
2415
+ gap: 8px;
2416
+ padding: 8px 12px;
2417
+ background: rgba(255, 255, 255, 0.03);
2418
+ border: 1px solid rgba(255, 255, 255, 0.1);
2419
+ border-radius: 4px;
2420
+ cursor: pointer;
2421
+ user-select: none;
2422
+ }
2423
+
2424
+ .instaraw-rpg-expression-toggle:hover {
2425
+ background: rgba(255, 255, 255, 0.06);
2426
+ border-color: rgba(139, 92, 246, 0.3);
2427
+ }
2428
+
2429
+ .instaraw-rpg-expression-checkbox {
2430
+ cursor: pointer;
2431
+ width: 16px;
2432
+ height: 16px;
2433
+ margin: 0;
2434
+ flex-shrink: 0;
2435
+ vertical-align: middle;
2436
+ }
2437
+
2438
+ .instaraw-rpg-expression-label {
2439
+ font-size: 12px;
2440
+ color: #e5e7eb;
2441
+ flex: 1;
2442
+ line-height: 16px;
2443
+ }
2444
+
2445
+ .instaraw-rpg-expression-toggle:has(.instaraw-rpg-expression-checkbox:checked) {
2446
+ background: rgba(139, 92, 246, 0.15);
2447
+ border-color: rgba(139, 92, 246, 0.5);
2448
+ }
2449
+
2450
+ .instaraw-rpg-expression-toggle:has(.instaraw-rpg-expression-checkbox:checked) .instaraw-rpg-expression-label {
2451
+ color: #c4b5fd;
2452
+ font-weight: 500;
2453
+ }
2454
+
2455
+ .instaraw-rpg-default-expression-select {
2456
+ width: 100%;
2457
+ background: rgba(0, 0, 0, 0.3);
2458
+ border: 1px solid rgba(255, 255, 255, 0.1);
2459
+ color: #e5e7eb;
2460
+ padding: 8px 12px;
2461
+ border-radius: 4px;
2462
+ font-size: 12px;
2463
+ cursor: pointer;
2464
+ }
2465
+
2466
+ .instaraw-rpg-default-expression-select:focus {
2467
+ outline: none;
2468
+ border-color: rgba(139, 92, 246, 0.5);
2469
+ }
2470
+
2471
+ .instaraw-rpg-default-mix-slider {
2472
+ width: 100%;
2473
+ height: 6px;
2474
+ border-radius: 3px;
2475
+ background: rgba(255, 255, 255, 0.1);
2476
+ outline: none;
2477
+ cursor: pointer;
2478
+ }
2479
+
2480
+ .instaraw-rpg-default-mix-slider::-webkit-slider-thumb {
2481
+ -webkit-appearance: none;
2482
+ appearance: none;
2483
+ width: 16px;
2484
+ height: 16px;
2485
+ border-radius: 50%;
2486
+ background: #8b5cf6;
2487
+ cursor: pointer;
2488
+ }
2489
+
2490
+ .instaraw-rpg-default-mix-slider::-moz-range-thumb {
2491
+ width: 16px;
2492
+ height: 16px;
2493
+ border-radius: 50%;
2494
+ background: #8b5cf6;
2495
+ cursor: pointer;
2496
+ border: none;
2497
+ }
2498
+
2499
+ .instaraw-rpg-default-mix-value {
2500
+ font-weight: 600;
2501
+ color: #c4b5fd;
2502
+ }
2503
+
2504
+ /* === Custom Tab Styles === */
2505
+ .instaraw-rpg-custom-tab {
2506
+ display: flex;
2507
+ flex-direction: column;
2508
+ gap: 16px;
2509
+ }
2510
+
2511
+ .instaraw-rpg-custom-template-textarea {
2512
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace !important;
2513
+ min-height: 100px;
2514
+ max-height: 250px;
2515
+ overflow-y: auto !important;
2516
+ }
2517
+
2518
+ /* Model instructions textarea - allow unlimited expansion */
2519
+ .instaraw-rpg-model-instructions {
2520
+ min-height: 80px;
2521
+ max-height: none;
2522
+ }
2523
+
2524
+ .instaraw-rpg-variables-grid {
2525
+ display: grid;
2526
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
2527
+ gap: 12px;
2528
+ }
2529
+
2530
+ .instaraw-rpg-variable-item {
2531
+ background: rgba(255, 255, 255, 0.03);
2532
+ border: 1px solid rgba(255, 255, 255, 0.1);
2533
+ border-radius: 4px;
2534
+ padding: 10px;
2535
+ }
2536
+
2537
+ .instaraw-rpg-variable-item:hover {
2538
+ background: rgba(255, 255, 255, 0.06);
2539
+ border-color: rgba(139, 92, 246, 0.3);
2540
+ }
2541
+
2542
+ .instaraw-rpg-variable-insert-btn {
2543
+ width: 100%;
2544
+ background: transparent;
2545
+ border: none;
2546
+ padding: 4px 0;
2547
+ cursor: pointer;
2548
+ text-align: left;
2549
+ color: inherit;
2550
+ font-family: inherit;
2551
+ }
2552
+
2553
+ .instaraw-rpg-variable-name {
2554
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
2555
+ font-size: 12px;
2556
+ color: #8b5cf6;
2557
+ font-weight: 600;
2558
+ display: block;
2559
+ margin-bottom: 4px;
2560
+ }
2561
+
2562
+ .instaraw-rpg-variable-desc {
2563
+ font-size: 11px;
2564
+ color: #9ca3af;
2565
+ line-height: 1.4;
2566
+ }
2567
+
2568
+ .instaraw-rpg-variable-insert-btn:hover .instaraw-rpg-variable-name {
2569
+ color: #a78bfa;
2570
+ }
2571
+
2572
+ /* === Preview Modal Styles === */
2573
+ .instaraw-rpg-preview-modal-overlay {
2574
+ position: fixed;
2575
+ top: 0;
2576
+ left: 0;
2577
+ right: 0;
2578
+ bottom: 0;
2579
+ background: rgba(0, 0, 0, 0.8);
2580
+ backdrop-filter: blur(4px);
2581
+ display: flex;
2582
+ align-items: center;
2583
+ justify-content: center;
2584
+ z-index: 10000;
2585
+ padding: 20px;
2586
+ }
2587
+
2588
+ .instaraw-rpg-preview-modal {
2589
+ background: #1a1d23;
2590
+ border: 1px solid rgba(139, 92, 246, 0.3);
2591
+ border-radius: 8px;
2592
+ max-width: 900px;
2593
+ width: 100%;
2594
+ max-height: 90vh;
2595
+ display: flex;
2596
+ flex-direction: column;
2597
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
2598
+ }
2599
+
2600
+ .instaraw-rpg-preview-modal-header {
2601
+ padding: 20px 24px;
2602
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
2603
+ display: flex;
2604
+ justify-content: space-between;
2605
+ align-items: center;
2606
+ }
2607
+
2608
+ .instaraw-rpg-preview-modal-header h3 {
2609
+ margin: 0;
2610
+ font-size: 18px;
2611
+ font-weight: 600;
2612
+ color: #e5e7eb;
2613
+ }
2614
+
2615
+ .instaraw-rpg-preview-modal-close {
2616
+ background: transparent;
2617
+ border: none;
2618
+ color: #9ca3af;
2619
+ font-size: 24px;
2620
+ cursor: pointer;
2621
+ padding: 0;
2622
+ width: 32px;
2623
+ height: 32px;
2624
+ display: flex;
2625
+ align-items: center;
2626
+ justify-content: center;
2627
+ border-radius: 4px;
2628
+ }
2629
+
2630
+ .instaraw-rpg-preview-modal-close:hover {
2631
+ background: rgba(255, 255, 255, 0.1);
2632
+ color: #e5e7eb;
2633
+ }
2634
+
2635
+ .instaraw-rpg-preview-modal-body {
2636
+ padding: 24px;
2637
+ overflow-y: auto;
2638
+ flex: 1;
2639
+ }
2640
+
2641
+ .instaraw-rpg-preview-section {
2642
+ margin-bottom: 24px;
2643
+ }
2644
+
2645
+ .instaraw-rpg-preview-section:last-child {
2646
+ margin-bottom: 0;
2647
+ }
2648
+
2649
+ .instaraw-rpg-preview-section h4 {
2650
+ margin: 0 0 12px 0;
2651
+ font-size: 14px;
2652
+ font-weight: 600;
2653
+ color: #c4b5fd;
2654
+ }
2655
+
2656
+ .instaraw-rpg-preview-settings {
2657
+ background: rgba(255, 255, 255, 0.03);
2658
+ border: 1px solid rgba(255, 255, 255, 0.1);
2659
+ border-radius: 6px;
2660
+ padding: 16px;
2661
+ display: grid;
2662
+ gap: 8px;
2663
+ }
2664
+
2665
+ .instaraw-rpg-preview-settings div {
2666
+ font-size: 13px;
2667
+ color: #d1d5db;
2668
+ line-height: 1.6;
2669
+ }
2670
+
2671
+ .instaraw-rpg-preview-settings strong {
2672
+ color: #a78bfa;
2673
+ font-weight: 600;
2674
+ margin-right: 8px;
2675
+ }
2676
+
2677
+ .instaraw-rpg-preview-prompt {
2678
+ background: rgba(0, 0, 0, 0.4);
2679
+ border: 1px solid rgba(255, 255, 255, 0.1);
2680
+ border-radius: 6px;
2681
+ padding: 16px;
2682
+ max-height: 400px;
2683
+ overflow-y: auto;
2684
+ }
2685
+
2686
+ .instaraw-rpg-preview-prompt pre {
2687
+ margin: 0;
2688
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
2689
+ font-size: 12px;
2690
+ line-height: 1.6;
2691
+ color: #e5e7eb;
2692
+ white-space: pre-wrap;
2693
+ word-wrap: break-word;
2694
+ }
2695
+
2696
+ .instaraw-rpg-preview-modal-footer {
2697
+ padding: 16px 24px;
2698
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
2699
+ display: flex;
2700
+ justify-content: flex-end;
2701
+ gap: 12px;
2702
+ }
2703
+
2704
+ /* === Multi-Image Combination Cards === */
2705
+ /* Compact flowing rectangles showing image combinations */
2706
+
2707
+ .instaraw-combo-grid {
2708
+ display: flex;
2709
+ flex-wrap: wrap;
2710
+ gap: 4px;
2711
+ }
2712
+
2713
+ .instaraw-combo-card {
2714
+ display: flex;
2715
+ align-items: center;
2716
+ gap: 4px;
2717
+ background: rgba(255, 255, 255, 0.03);
2718
+ border: 1px solid rgba(255, 255, 255, 0.08);
2719
+ border-radius: 4px;
2720
+ padding: 3px 6px;
2721
+ }
2722
+
2723
+ .instaraw-combo-card:hover {
2724
+ border-color: rgba(139, 92, 246, 0.3);
2725
+ background: rgba(255, 255, 255, 0.05);
2726
+ }
2727
+
2728
+ .instaraw-combo-header {
2729
+ display: flex;
2730
+ align-items: center;
2731
+ }
2732
+
2733
+ .instaraw-combo-number {
2734
+ font-size: 10px;
2735
+ font-weight: 600;
2736
+ color: rgba(255, 255, 255, 0.5);
2737
+ min-width: 20px;
2738
+ }
2739
+
2740
+ .instaraw-combo-slots {
2741
+ display: flex;
2742
+ gap: 3px;
2743
+ }
2744
+
2745
+ .instaraw-combo-slot {
2746
+ display: flex;
2747
+ align-items: center;
2748
+ }
2749
+
2750
+ .instaraw-combo-slot-image {
2751
+ width: 42px;
2752
+ height: 42px;
2753
+ border-radius: 3px;
2754
+ overflow: hidden;
2755
+ background: rgba(0, 0, 0, 0.3);
2756
+ border: 1px solid rgba(255, 255, 255, 0.1);
2757
+ display: flex;
2758
+ align-items: center;
2759
+ justify-content: center;
2760
+ }
2761
+
2762
+ .instaraw-combo-slot-image img {
2763
+ width: 100%;
2764
+ height: 100%;
2765
+ object-fit: cover;
2766
+ }
2767
+
2768
+ .instaraw-combo-slot-image.empty {
2769
+ border-style: dashed;
2770
+ border-color: rgba(255, 255, 255, 0.15);
2771
+ }
2772
+
2773
+ .instaraw-combo-slot-image.empty::after {
2774
+ content: '—';
2775
+ color: rgba(255, 255, 255, 0.3);
2776
+ font-size: 10px;
2777
+ }
2778
+
2779
+ .instaraw-combo-slot-label {
2780
+ display: none;
2781
+ }
2782
+
2783
+ /* Simple grid fallback for single input mode */
2784
+ .instaraw-simple-grid {
2785
+ display: grid;
2786
+ grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
2787
+ gap: 8px;
2788
+ }
2789
+
2790
+ .instaraw-simple-grid-item {
2791
+ position: relative;
2792
+ width: 100%;
2793
+ aspect-ratio: 1;
2794
+ border-radius: 4px;
2795
+ overflow: hidden;
2796
+ background: rgba(0, 0, 0, 0.3);
2797
+ border: 1px solid rgba(255, 255, 255, 0.1);
2798
+ }
2799
+
2800
+ .instaraw-simple-grid-item img {
2801
+ width: 100%;
2802
+ height: 100%;
2803
+ object-fit: cover;
2804
+ }
2805
+
2806
+ .instaraw-simple-grid-item-index {
2807
+ position: absolute;
2808
+ top: 3px;
2809
+ left: 3px;
2810
+ background: rgba(0, 0, 0, 0.8);
2811
+ color: #ffffff;
2812
+ padding: 2px 5px;
2813
+ border-radius: 3px;
2814
+ font-size: 10px;
2815
+ font-weight: 600;
2816
+ border: 1px solid rgba(255, 255, 255, 0.2);
2817
+ }
2818
+
2819
+ /* Multi-image header indicator */
2820
+ .instaraw-multi-image-indicator {
2821
+ display: flex;
2822
+ align-items: center;
2823
+ gap: 6px;
2824
+ padding: 6px 10px;
2825
+ background: rgba(139, 92, 246, 0.1);
2826
+ border: 1px solid rgba(139, 92, 246, 0.2);
2827
+ border-radius: 4px;
2828
+ font-size: 11px;
2829
+ color: #c4b5fd;
2830
+ }
2831
+
2832
+ .instaraw-multi-image-indicator-dot {
2833
+ width: 6px;
2834
+ height: 6px;
2835
+ border-radius: 50%;
2836
+ background: #a78bfa;
2837
+ }
2838
+
2839
+ .instaraw-multi-image-count {
2840
+ font-weight: 600;
2841
+ }
custom_nodes/ComfyUI_INSTARAW/js/reality_prompt_generator.js ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/js/shared/combo-cards.js ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Shared combo card renderer for multi-image display
3
+ * Used by: reality_prompt_generator.js, batch_image_generator.js
4
+ */
5
+
6
+ /**
7
+ * Render combo cards HTML for multi-image mode
8
+ * @param {Array} inputsWithImages - Array of {label, images/urls} objects
9
+ * @param {Object} options - { getImageUrl: function to extract URL from item }
10
+ * @returns {string} HTML string for combo cards
11
+ */
12
+ export function renderComboCards(inputsWithImages, options = {}) {
13
+ const getItems = (input) => input.images || input.urls || [];
14
+ const maxLen = Math.max(...inputsWithImages.map(i => getItems(i).length));
15
+
16
+ let html = '';
17
+ for (let idx = 0; idx < maxLen; idx++) {
18
+ const slots = inputsWithImages.map(input => {
19
+ const items = getItems(input);
20
+ const item = items[idx];
21
+ const url = options.getImageUrl ? options.getImageUrl(item) : (item?.url || item);
22
+
23
+ if (url) {
24
+ return `
25
+ <div class="instaraw-combo-slot">
26
+ <div class="instaraw-combo-slot-image">
27
+ <img src="${url}" alt="${input.label}" style="background: rgba(0,0,0,0.3);" onerror="this.style.opacity='0.3'; this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 24 24%22%3E%3Cpath fill=%22%239ca3af%22 d=%22M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z%22/%3E%3C/svg%3E';" />
28
+ </div>
29
+ <span class="instaraw-combo-slot-label">${input.label}</span>
30
+ </div>
31
+ `;
32
+ } else {
33
+ return `
34
+ <div class="instaraw-combo-slot">
35
+ <div class="instaraw-combo-slot-image empty"></div>
36
+ <span class="instaraw-combo-slot-label">${input.label}</span>
37
+ </div>
38
+ `;
39
+ }
40
+ }).join('');
41
+
42
+ html += `
43
+ <div class="instaraw-combo-card">
44
+ <div class="instaraw-combo-header">
45
+ <span class="instaraw-combo-number">#${idx + 1}</span>
46
+ </div>
47
+ <div class="instaraw-combo-slots">
48
+ ${slots}
49
+ </div>
50
+ </div>
51
+ `;
52
+ }
53
+ return html;
54
+ }
55
+
56
+ /**
57
+ * Get the maximum length across all inputs
58
+ * @param {Array} inputsWithImages - Array of {label, images/urls} objects
59
+ * @returns {number} Maximum number of items
60
+ */
61
+ export function getComboMaxLength(inputsWithImages) {
62
+ const getItems = (input) => input.images || input.urls || [];
63
+ return Math.max(...inputsWithImages.map(i => getItems(i).length));
64
+ }
custom_nodes/ComfyUI_INSTARAW/js/utils.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ const _aq = !!true;
2
+
3
+ export function create( tag, clss, parent, properties ) {
4
+ const nd = document.createElement(tag);
5
+ if (clss) clss.split(" ").forEach((s) => nd.classList.add(s))
6
+ if (parent) parent.appendChild(nd);
7
+ if (properties) Object.assign(nd, properties);
8
+ return nd;
9
+ }
custom_nodes/ComfyUI_INSTARAW/js/zoomed.css ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .instaraw_popup .zoomed {
3
+ position: absolute;
4
+ width: 100%;
5
+ left: 0;
6
+ height: 100%;
7
+ right: 0;
8
+ display: flex;
9
+ justify-content: space-evenly;
10
+ align-items: center;
11
+ z-index: 120000;
12
+ background-color: rgba(29, 29, 29, 0.886);
13
+ }
14
+
15
+ .instaraw_popup .zoomed_prev, .zoomed_next {
16
+ flex-grow: 1;
17
+
18
+ }
19
+
20
+ .instaraw_popup .zoomed_prev {
21
+ text-align: right;
22
+ }
23
+
24
+ .instaraw_popup .zoomed_arrow {
25
+ font-size: 48px;
26
+ border-radius: 40px;
27
+ padding: 0px 8px 4px 8px;
28
+ margin: 6px;
29
+ border: 1px solid rgb(128, 128, 128, 1.0);
30
+ }
31
+
32
+ .instaraw_popup .zoomed_arrow:hover {
33
+ border: 1px solid rgb(255, 128, 128, 1.0);
34
+ }
35
+
36
+
37
+ .instaraw_popup .zoomed_image {
38
+ max-width: calc(100% - 140px);
39
+ height: 100%;
40
+ object-fit: contain;
41
+ border: 1px solid white;
42
+ padding: 4px;
43
+ }
44
+
45
+ .instaraw_popup .zoomed_number {
46
+ position: absolute;
47
+ top: 12px;
48
+ margin-left: 12px;
49
+ color: white;
50
+ padding: 10px 8px 8px 8px;
51
+ border-radius: 4px;
52
+ z-index: 110000;
53
+ background-color: rgba(128, 128, 128, 0.5);
54
+ border: 1px solid rgba(128, 128, 128, 1.0);
55
+ }
56
+
57
+ .instaraw_popup .zoomed.highlighted .zoomed_image {
58
+ border: 3px solid green;
59
+ padding: 2px;
60
+ }
61
+
custom_nodes/ComfyUI_INSTARAW/modules/__init__.py ADDED
File without changes
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_12.icc ADDED
Binary file (536 Bytes). View file
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_12.json ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_12.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:500f432f11396d9b3eb82dd9ea6c117f809be53abdf8b7d1f24e204117a15509
3
+ size 477647
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_13_pro.icc ADDED
Binary file (536 Bytes). View file
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_13_pro.json ADDED
@@ -0,0 +1,1095 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "File:FileType": "JPEG",
4
+ "File:FileTypeExtension": "JPG",
5
+ "File:MIMEType": "image/jpeg",
6
+ "File:ImageWidth": 4032,
7
+ "File:ImageHeight": 3024,
8
+ "File:EncodingProcess": 0,
9
+ "File:BitsPerSample": 8,
10
+ "File:ColorComponents": 3,
11
+ "File:YCbCrSubSampling": "1 1",
12
+ "JFIF:JFIFVersion": "1 1",
13
+ "JFIF:ResolutionUnit": 0,
14
+ "JFIF:XResolution": 0,
15
+ "JFIF:YResolution": 0,
16
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
17
+ "XMP:CreateDate": "2025:09:08 11:00:37",
18
+ "XMP:CreatorTool": 18.5,
19
+ "XMP:ModifyDate": "2025:09:08 11:00:37",
20
+ "XMP:RegionAreaY": "0.42702451954937037",
21
+ "XMP:RegionAreaW": "0.026242544731610362",
22
+ "XMP:RegionAreaX": "0.66737176938369791",
23
+ "XMP:RegionAreaH": "0.035069582504970165",
24
+ "XMP:RegionAreaUnit": "normalized",
25
+ "XMP:RegionType": "Face",
26
+ "XMP:RegionExtensionsAngleInfoYaw": 12,
27
+ "XMP:RegionExtensionsAngleInfoRoll": 23,
28
+ "XMP:RegionExtensionsConfidenceLevel": 1,
29
+ "XMP:RegionExtensionsFaceID": 3,
30
+ "XMP:RegionAppliedToDimensionsH": 3024,
31
+ "XMP:RegionAppliedToDimensionsW": 4032,
32
+ "XMP:RegionAppliedToDimensionsUnit": "pixel",
33
+ "XMP:DateCreated": "2025:09:08 11:00:37",
34
+ "Composite:ImageSize": "4032 3024",
35
+ "Composite:Megapixels": 12.192768,
36
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
37
+ },
38
+ {
39
+ "File:FileType": "JPEG",
40
+ "File:FileTypeExtension": "JPG",
41
+ "File:MIMEType": "image/jpeg",
42
+ "File:ImageWidth": 3024,
43
+ "File:ImageHeight": 4032,
44
+ "File:EncodingProcess": 0,
45
+ "File:BitsPerSample": 8,
46
+ "File:ColorComponents": 3,
47
+ "File:YCbCrSubSampling": "1 1",
48
+ "JFIF:JFIFVersion": "1 1",
49
+ "JFIF:ResolutionUnit": 0,
50
+ "JFIF:XResolution": 0,
51
+ "JFIF:YResolution": 0,
52
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
53
+ "XMP:CreateDate": "2025:09:08 11:06:18",
54
+ "XMP:CreatorTool": 18.5,
55
+ "XMP:ModifyDate": "2025:09:08 11:06:18",
56
+ "XMP:RegionAreaY": "0.87999999999999989",
57
+ "XMP:RegionAreaW": "0.048190476190476172",
58
+ "XMP:RegionAreaX": "0.33866666666666667",
59
+ "XMP:RegionAreaH": "0.063999999999999946",
60
+ "XMP:RegionAreaUnit": "normalized",
61
+ "XMP:RegionType": "Face",
62
+ "XMP:RegionExtensionsAngleInfoYaw": 321,
63
+ "XMP:RegionExtensionsAngleInfoRoll": 247,
64
+ "XMP:RegionExtensionsConfidenceLevel": 1,
65
+ "XMP:RegionExtensionsFaceID": 3,
66
+ "XMP:RegionAppliedToDimensionsH": 3024,
67
+ "XMP:RegionAppliedToDimensionsW": 4032,
68
+ "XMP:RegionAppliedToDimensionsUnit": "pixel",
69
+ "XMP:DateCreated": "2025:09:08 11:06:18",
70
+ "Composite:ImageSize": "3024 4032",
71
+ "Composite:Megapixels": 12.192768,
72
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
73
+ },
74
+ {
75
+ "File:FileType": "JPEG",
76
+ "File:FileTypeExtension": "JPG",
77
+ "File:MIMEType": "image/jpeg",
78
+ "File:ImageWidth": 4032,
79
+ "File:ImageHeight": 3024,
80
+ "File:EncodingProcess": 0,
81
+ "File:BitsPerSample": 8,
82
+ "File:ColorComponents": 3,
83
+ "File:YCbCrSubSampling": "1 1",
84
+ "JFIF:JFIFVersion": "1 1",
85
+ "JFIF:ResolutionUnit": 0,
86
+ "JFIF:XResolution": 0,
87
+ "JFIF:YResolution": 0,
88
+ "Composite:ImageSize": "4032 3024",
89
+ "Composite:Megapixels": 12.192768,
90
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
91
+ },
92
+ {
93
+ "File:FileType": "JPEG",
94
+ "File:FileTypeExtension": "JPG",
95
+ "File:MIMEType": "image/jpeg",
96
+ "File:ImageWidth": 4032,
97
+ "File:ImageHeight": 3024,
98
+ "File:EncodingProcess": 0,
99
+ "File:BitsPerSample": 8,
100
+ "File:ColorComponents": 3,
101
+ "File:YCbCrSubSampling": "1 1",
102
+ "JFIF:JFIFVersion": "1 1",
103
+ "JFIF:ResolutionUnit": 0,
104
+ "JFIF:XResolution": 0,
105
+ "JFIF:YResolution": 0,
106
+ "Composite:ImageSize": "4032 3024",
107
+ "Composite:Megapixels": 12.192768,
108
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
109
+ },
110
+ {
111
+ "File:FileType": "JPEG",
112
+ "File:FileTypeExtension": "JPG",
113
+ "File:MIMEType": "image/jpeg",
114
+ "File:ImageWidth": 3024,
115
+ "File:ImageHeight": 4032,
116
+ "File:EncodingProcess": 0,
117
+ "File:BitsPerSample": 8,
118
+ "File:ColorComponents": 3,
119
+ "File:YCbCrSubSampling": "1 1",
120
+ "JFIF:JFIFVersion": "1 1",
121
+ "JFIF:ResolutionUnit": 0,
122
+ "JFIF:XResolution": 0,
123
+ "JFIF:YResolution": 0,
124
+ "Composite:ImageSize": "3024 4032",
125
+ "Composite:Megapixels": 12.192768,
126
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
127
+ },
128
+ {
129
+ "File:FileType": "JPEG",
130
+ "File:FileTypeExtension": "JPG",
131
+ "File:MIMEType": "image/jpeg",
132
+ "File:ImageWidth": 3024,
133
+ "File:ImageHeight": 4032,
134
+ "File:EncodingProcess": 0,
135
+ "File:BitsPerSample": 8,
136
+ "File:ColorComponents": 3,
137
+ "File:YCbCrSubSampling": "1 1",
138
+ "JFIF:JFIFVersion": "1 1",
139
+ "JFIF:ResolutionUnit": 0,
140
+ "JFIF:XResolution": 0,
141
+ "JFIF:YResolution": 0,
142
+ "Composite:ImageSize": "3024 4032",
143
+ "Composite:Megapixels": 12.192768,
144
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
145
+ },
146
+ {
147
+ "File:FileType": "JPEG",
148
+ "File:FileTypeExtension": "JPG",
149
+ "File:MIMEType": "image/jpeg",
150
+ "File:ImageWidth": 4032,
151
+ "File:ImageHeight": 3024,
152
+ "File:EncodingProcess": 0,
153
+ "File:BitsPerSample": 8,
154
+ "File:ColorComponents": 3,
155
+ "File:YCbCrSubSampling": "1 1",
156
+ "JFIF:JFIFVersion": "1 1",
157
+ "JFIF:ResolutionUnit": 0,
158
+ "JFIF:XResolution": 0,
159
+ "JFIF:YResolution": 0,
160
+ "Composite:ImageSize": "4032 3024",
161
+ "Composite:Megapixels": 12.192768,
162
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
163
+ },
164
+ {
165
+ "File:FileType": "JPEG",
166
+ "File:FileTypeExtension": "JPG",
167
+ "File:MIMEType": "image/jpeg",
168
+ "File:ImageWidth": 4032,
169
+ "File:ImageHeight": 3024,
170
+ "File:EncodingProcess": 0,
171
+ "File:BitsPerSample": 8,
172
+ "File:ColorComponents": 3,
173
+ "File:YCbCrSubSampling": "1 1",
174
+ "JFIF:JFIFVersion": "1 1",
175
+ "JFIF:ResolutionUnit": 0,
176
+ "JFIF:XResolution": 0,
177
+ "JFIF:YResolution": 0,
178
+ "Composite:ImageSize": "4032 3024",
179
+ "Composite:Megapixels": 12.192768,
180
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
181
+ },
182
+ {
183
+ "File:FileType": "JPEG",
184
+ "File:FileTypeExtension": "JPG",
185
+ "File:MIMEType": "image/jpeg",
186
+ "File:ImageWidth": 4032,
187
+ "File:ImageHeight": 3024,
188
+ "File:EncodingProcess": 0,
189
+ "File:BitsPerSample": 8,
190
+ "File:ColorComponents": 3,
191
+ "File:YCbCrSubSampling": "1 1",
192
+ "JFIF:JFIFVersion": "1 1",
193
+ "JFIF:ResolutionUnit": 0,
194
+ "JFIF:XResolution": 0,
195
+ "JFIF:YResolution": 0,
196
+ "Composite:ImageSize": "4032 3024",
197
+ "Composite:Megapixels": 12.192768,
198
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
199
+ },
200
+ {
201
+ "File:FileType": "JPEG",
202
+ "File:FileTypeExtension": "JPG",
203
+ "File:MIMEType": "image/jpeg",
204
+ "File:ImageWidth": 3024,
205
+ "File:ImageHeight": 4032,
206
+ "File:EncodingProcess": 0,
207
+ "File:BitsPerSample": 8,
208
+ "File:ColorComponents": 3,
209
+ "File:YCbCrSubSampling": "1 1",
210
+ "JFIF:JFIFVersion": "1 1",
211
+ "JFIF:ResolutionUnit": 0,
212
+ "JFIF:XResolution": 0,
213
+ "JFIF:YResolution": 0,
214
+ "Composite:ImageSize": "3024 4032",
215
+ "Composite:Megapixels": 12.192768,
216
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
217
+ },
218
+ {
219
+ "File:FileType": "JPEG",
220
+ "File:FileTypeExtension": "JPG",
221
+ "File:MIMEType": "image/jpeg",
222
+ "File:ImageWidth": 4032,
223
+ "File:ImageHeight": 3024,
224
+ "File:EncodingProcess": 0,
225
+ "File:BitsPerSample": 8,
226
+ "File:ColorComponents": 3,
227
+ "File:YCbCrSubSampling": "1 1",
228
+ "JFIF:JFIFVersion": "1 1",
229
+ "JFIF:ResolutionUnit": 0,
230
+ "JFIF:XResolution": 0,
231
+ "JFIF:YResolution": 0,
232
+ "Composite:ImageSize": "4032 3024",
233
+ "Composite:Megapixels": 12.192768,
234
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
235
+ },
236
+ {
237
+ "File:FileType": "JPEG",
238
+ "File:FileTypeExtension": "JPG",
239
+ "File:MIMEType": "image/jpeg",
240
+ "File:ImageWidth": 4032,
241
+ "File:ImageHeight": 3024,
242
+ "File:EncodingProcess": 0,
243
+ "File:BitsPerSample": 8,
244
+ "File:ColorComponents": 3,
245
+ "File:YCbCrSubSampling": "1 1",
246
+ "JFIF:JFIFVersion": "1 1",
247
+ "JFIF:ResolutionUnit": 0,
248
+ "JFIF:XResolution": 0,
249
+ "JFIF:YResolution": 0,
250
+ "Composite:ImageSize": "4032 3024",
251
+ "Composite:Megapixels": 12.192768,
252
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
253
+ },
254
+ {
255
+ "File:FileType": "JPEG",
256
+ "File:FileTypeExtension": "JPG",
257
+ "File:MIMEType": "image/jpeg",
258
+ "File:ImageWidth": 4032,
259
+ "File:ImageHeight": 3024,
260
+ "File:EncodingProcess": 0,
261
+ "File:BitsPerSample": 8,
262
+ "File:ColorComponents": 3,
263
+ "File:YCbCrSubSampling": "1 1",
264
+ "JFIF:JFIFVersion": "1 1",
265
+ "JFIF:ResolutionUnit": 0,
266
+ "JFIF:XResolution": 0,
267
+ "JFIF:YResolution": 0,
268
+ "Composite:ImageSize": "4032 3024",
269
+ "Composite:Megapixels": 12.192768,
270
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
271
+ },
272
+ {
273
+ "File:FileType": "JPEG",
274
+ "File:FileTypeExtension": "JPG",
275
+ "File:MIMEType": "image/jpeg",
276
+ "File:ImageWidth": 4032,
277
+ "File:ImageHeight": 3024,
278
+ "File:EncodingProcess": 0,
279
+ "File:BitsPerSample": 8,
280
+ "File:ColorComponents": 3,
281
+ "File:YCbCrSubSampling": "1 1",
282
+ "JFIF:JFIFVersion": "1 1",
283
+ "JFIF:ResolutionUnit": 0,
284
+ "JFIF:XResolution": 0,
285
+ "JFIF:YResolution": 0,
286
+ "Composite:ImageSize": "4032 3024",
287
+ "Composite:Megapixels": 12.192768,
288
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
289
+ },
290
+ {
291
+ "File:FileType": "JPEG",
292
+ "File:FileTypeExtension": "JPG",
293
+ "File:MIMEType": "image/jpeg",
294
+ "File:ImageWidth": 3024,
295
+ "File:ImageHeight": 4032,
296
+ "File:EncodingProcess": 0,
297
+ "File:BitsPerSample": 8,
298
+ "File:ColorComponents": 3,
299
+ "File:YCbCrSubSampling": "1 1",
300
+ "JFIF:JFIFVersion": "1 1",
301
+ "JFIF:ResolutionUnit": 0,
302
+ "JFIF:XResolution": 0,
303
+ "JFIF:YResolution": 0,
304
+ "Composite:ImageSize": "3024 4032",
305
+ "Composite:Megapixels": 12.192768,
306
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
307
+ },
308
+ {
309
+ "File:FileType": "JPEG",
310
+ "File:FileTypeExtension": "JPG",
311
+ "File:MIMEType": "image/jpeg",
312
+ "File:ImageWidth": 4032,
313
+ "File:ImageHeight": 3024,
314
+ "File:EncodingProcess": 0,
315
+ "File:BitsPerSample": 8,
316
+ "File:ColorComponents": 3,
317
+ "File:YCbCrSubSampling": "1 1",
318
+ "JFIF:JFIFVersion": "1 1",
319
+ "JFIF:ResolutionUnit": 0,
320
+ "JFIF:XResolution": 0,
321
+ "JFIF:YResolution": 0,
322
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
323
+ "XMP:CreateDate": "2025:09:09 10:38:38",
324
+ "XMP:CreatorTool": 18.5,
325
+ "XMP:ModifyDate": "2025:09:09 10:38:38",
326
+ "XMP:DateCreated": "2025:09:09 10:38:38",
327
+ "Composite:ImageSize": "4032 3024",
328
+ "Composite:Megapixels": 12.192768,
329
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
330
+ },
331
+ {
332
+ "File:FileType": "JPEG",
333
+ "File:FileTypeExtension": "JPG",
334
+ "File:MIMEType": "image/jpeg",
335
+ "File:ImageWidth": 4032,
336
+ "File:ImageHeight": 3024,
337
+ "File:EncodingProcess": 0,
338
+ "File:BitsPerSample": 8,
339
+ "File:ColorComponents": 3,
340
+ "File:YCbCrSubSampling": "1 1",
341
+ "JFIF:JFIFVersion": "1 1",
342
+ "JFIF:ResolutionUnit": 0,
343
+ "JFIF:XResolution": 0,
344
+ "JFIF:YResolution": 0,
345
+ "Composite:ImageSize": "4032 3024",
346
+ "Composite:Megapixels": 12.192768,
347
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
348
+ },
349
+ {
350
+ "File:FileType": "JPEG",
351
+ "File:FileTypeExtension": "JPG",
352
+ "File:MIMEType": "image/jpeg",
353
+ "File:ImageWidth": 3024,
354
+ "File:ImageHeight": 4032,
355
+ "File:EncodingProcess": 0,
356
+ "File:BitsPerSample": 8,
357
+ "File:ColorComponents": 3,
358
+ "File:YCbCrSubSampling": "1 1",
359
+ "JFIF:JFIFVersion": "1 1",
360
+ "JFIF:ResolutionUnit": 0,
361
+ "JFIF:XResolution": 0,
362
+ "JFIF:YResolution": 0,
363
+ "Composite:ImageSize": "3024 4032",
364
+ "Composite:Megapixels": 12.192768,
365
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
366
+ },
367
+ {
368
+ "File:FileType": "JPEG",
369
+ "File:FileTypeExtension": "JPG",
370
+ "File:MIMEType": "image/jpeg",
371
+ "File:ImageWidth": 4032,
372
+ "File:ImageHeight": 3024,
373
+ "File:EncodingProcess": 0,
374
+ "File:BitsPerSample": 8,
375
+ "File:ColorComponents": 3,
376
+ "File:YCbCrSubSampling": "1 1",
377
+ "JFIF:JFIFVersion": "1 1",
378
+ "JFIF:ResolutionUnit": 0,
379
+ "JFIF:XResolution": 0,
380
+ "JFIF:YResolution": 0,
381
+ "Composite:ImageSize": "4032 3024",
382
+ "Composite:Megapixels": 12.192768,
383
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
384
+ },
385
+ {
386
+ "File:FileType": "JPEG",
387
+ "File:FileTypeExtension": "JPG",
388
+ "File:MIMEType": "image/jpeg",
389
+ "File:ImageWidth": 4032,
390
+ "File:ImageHeight": 3024,
391
+ "File:EncodingProcess": 0,
392
+ "File:BitsPerSample": 8,
393
+ "File:ColorComponents": 3,
394
+ "File:YCbCrSubSampling": "1 1",
395
+ "JFIF:JFIFVersion": "1 1",
396
+ "JFIF:ResolutionUnit": 0,
397
+ "JFIF:XResolution": 0,
398
+ "JFIF:YResolution": 0,
399
+ "Composite:ImageSize": "4032 3024",
400
+ "Composite:Megapixels": 12.192768,
401
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
402
+ },
403
+ {
404
+ "File:FileType": "JPEG",
405
+ "File:FileTypeExtension": "JPG",
406
+ "File:MIMEType": "image/jpeg",
407
+ "File:ImageWidth": 4032,
408
+ "File:ImageHeight": 3024,
409
+ "File:EncodingProcess": 0,
410
+ "File:BitsPerSample": 8,
411
+ "File:ColorComponents": 3,
412
+ "File:YCbCrSubSampling": "1 1",
413
+ "JFIF:JFIFVersion": "1 1",
414
+ "JFIF:ResolutionUnit": 0,
415
+ "JFIF:XResolution": 0,
416
+ "JFIF:YResolution": 0,
417
+ "Composite:ImageSize": "4032 3024",
418
+ "Composite:Megapixels": 12.192768,
419
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
420
+ },
421
+ {
422
+ "File:FileType": "JPEG",
423
+ "File:FileTypeExtension": "JPG",
424
+ "File:MIMEType": "image/jpeg",
425
+ "File:ImageWidth": 4032,
426
+ "File:ImageHeight": 3024,
427
+ "File:EncodingProcess": 0,
428
+ "File:BitsPerSample": 8,
429
+ "File:ColorComponents": 3,
430
+ "File:YCbCrSubSampling": "1 1",
431
+ "JFIF:JFIFVersion": "1 1",
432
+ "JFIF:ResolutionUnit": 0,
433
+ "JFIF:XResolution": 0,
434
+ "JFIF:YResolution": 0,
435
+ "Composite:ImageSize": "4032 3024",
436
+ "Composite:Megapixels": 12.192768,
437
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
438
+ },
439
+ {
440
+ "File:FileType": "JPEG",
441
+ "File:FileTypeExtension": "JPG",
442
+ "File:MIMEType": "image/jpeg",
443
+ "File:ImageWidth": 4032,
444
+ "File:ImageHeight": 3024,
445
+ "File:EncodingProcess": 0,
446
+ "File:BitsPerSample": 8,
447
+ "File:ColorComponents": 3,
448
+ "File:YCbCrSubSampling": "1 1",
449
+ "JFIF:JFIFVersion": "1 1",
450
+ "JFIF:ResolutionUnit": 0,
451
+ "JFIF:XResolution": 0,
452
+ "JFIF:YResolution": 0,
453
+ "Composite:ImageSize": "4032 3024",
454
+ "Composite:Megapixels": 12.192768,
455
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
456
+ },
457
+ {
458
+ "File:FileType": "JPEG",
459
+ "File:FileTypeExtension": "JPG",
460
+ "File:MIMEType": "image/jpeg",
461
+ "File:ImageWidth": 4032,
462
+ "File:ImageHeight": 3024,
463
+ "File:EncodingProcess": 0,
464
+ "File:BitsPerSample": 8,
465
+ "File:ColorComponents": 3,
466
+ "File:YCbCrSubSampling": "1 1",
467
+ "JFIF:JFIFVersion": "1 1",
468
+ "JFIF:ResolutionUnit": 0,
469
+ "JFIF:XResolution": 0,
470
+ "JFIF:YResolution": 0,
471
+ "Composite:ImageSize": "4032 3024",
472
+ "Composite:Megapixels": 12.192768,
473
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
474
+ },
475
+ {
476
+ "File:FileType": "JPEG",
477
+ "File:FileTypeExtension": "JPG",
478
+ "File:MIMEType": "image/jpeg",
479
+ "File:ImageWidth": 3024,
480
+ "File:ImageHeight": 4032,
481
+ "File:EncodingProcess": 0,
482
+ "File:BitsPerSample": 8,
483
+ "File:ColorComponents": 3,
484
+ "File:YCbCrSubSampling": "1 1",
485
+ "JFIF:JFIFVersion": "1 1",
486
+ "JFIF:ResolutionUnit": 0,
487
+ "JFIF:XResolution": 0,
488
+ "JFIF:YResolution": 0,
489
+ "Composite:ImageSize": "3024 4032",
490
+ "Composite:Megapixels": 12.192768,
491
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
492
+ },
493
+ {
494
+ "File:FileType": "JPEG",
495
+ "File:FileTypeExtension": "JPG",
496
+ "File:MIMEType": "image/jpeg",
497
+ "File:ImageWidth": 4032,
498
+ "File:ImageHeight": 3024,
499
+ "File:EncodingProcess": 0,
500
+ "File:BitsPerSample": 8,
501
+ "File:ColorComponents": 3,
502
+ "File:YCbCrSubSampling": "1 1",
503
+ "JFIF:JFIFVersion": "1 1",
504
+ "JFIF:ResolutionUnit": 0,
505
+ "JFIF:XResolution": 0,
506
+ "JFIF:YResolution": 0,
507
+ "Composite:ImageSize": "4032 3024",
508
+ "Composite:Megapixels": 12.192768,
509
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
510
+ },
511
+ {
512
+ "File:FileType": "JPEG",
513
+ "File:FileTypeExtension": "JPG",
514
+ "File:MIMEType": "image/jpeg",
515
+ "File:ImageWidth": 3024,
516
+ "File:ImageHeight": 4032,
517
+ "File:EncodingProcess": 0,
518
+ "File:BitsPerSample": 8,
519
+ "File:ColorComponents": 3,
520
+ "File:YCbCrSubSampling": "1 1",
521
+ "JFIF:JFIFVersion": "1 1",
522
+ "JFIF:ResolutionUnit": 0,
523
+ "JFIF:XResolution": 0,
524
+ "JFIF:YResolution": 0,
525
+ "Composite:ImageSize": "3024 4032",
526
+ "Composite:Megapixels": 12.192768,
527
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
528
+ },
529
+ {
530
+ "File:FileType": "JPEG",
531
+ "File:FileTypeExtension": "JPG",
532
+ "File:MIMEType": "image/jpeg",
533
+ "File:ImageWidth": 4032,
534
+ "File:ImageHeight": 3024,
535
+ "File:EncodingProcess": 0,
536
+ "File:BitsPerSample": 8,
537
+ "File:ColorComponents": 3,
538
+ "File:YCbCrSubSampling": "1 1",
539
+ "JFIF:JFIFVersion": "1 1",
540
+ "JFIF:ResolutionUnit": 0,
541
+ "JFIF:XResolution": 0,
542
+ "JFIF:YResolution": 0,
543
+ "Composite:ImageSize": "4032 3024",
544
+ "Composite:Megapixels": 12.192768,
545
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
546
+ },
547
+ {
548
+ "File:FileType": "JPEG",
549
+ "File:FileTypeExtension": "JPG",
550
+ "File:MIMEType": "image/jpeg",
551
+ "File:ImageWidth": 4032,
552
+ "File:ImageHeight": 3024,
553
+ "File:EncodingProcess": 0,
554
+ "File:BitsPerSample": 8,
555
+ "File:ColorComponents": 3,
556
+ "File:YCbCrSubSampling": "1 1",
557
+ "JFIF:JFIFVersion": "1 1",
558
+ "JFIF:ResolutionUnit": 0,
559
+ "JFIF:XResolution": 0,
560
+ "JFIF:YResolution": 0,
561
+ "Composite:ImageSize": "4032 3024",
562
+ "Composite:Megapixels": 12.192768,
563
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
564
+ },
565
+ {
566
+ "File:FileType": "JPEG",
567
+ "File:FileTypeExtension": "JPG",
568
+ "File:MIMEType": "image/jpeg",
569
+ "File:ImageWidth": 4032,
570
+ "File:ImageHeight": 3024,
571
+ "File:EncodingProcess": 0,
572
+ "File:BitsPerSample": 8,
573
+ "File:ColorComponents": 3,
574
+ "File:YCbCrSubSampling": "1 1",
575
+ "JFIF:JFIFVersion": "1 1",
576
+ "JFIF:ResolutionUnit": 0,
577
+ "JFIF:XResolution": 0,
578
+ "JFIF:YResolution": 0,
579
+ "Composite:ImageSize": "4032 3024",
580
+ "Composite:Megapixels": 12.192768,
581
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
582
+ },
583
+ {
584
+ "File:FileType": "JPEG",
585
+ "File:FileTypeExtension": "JPG",
586
+ "File:MIMEType": "image/jpeg",
587
+ "File:ImageWidth": 4032,
588
+ "File:ImageHeight": 3024,
589
+ "File:EncodingProcess": 0,
590
+ "File:BitsPerSample": 8,
591
+ "File:ColorComponents": 3,
592
+ "File:YCbCrSubSampling": "1 1",
593
+ "JFIF:JFIFVersion": "1 1",
594
+ "JFIF:ResolutionUnit": 0,
595
+ "JFIF:XResolution": 0,
596
+ "JFIF:YResolution": 0,
597
+ "Composite:ImageSize": "4032 3024",
598
+ "Composite:Megapixels": 12.192768,
599
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
600
+ },
601
+ {
602
+ "File:FileType": "JPEG",
603
+ "File:FileTypeExtension": "JPG",
604
+ "File:MIMEType": "image/jpeg",
605
+ "File:ImageWidth": 4032,
606
+ "File:ImageHeight": 3024,
607
+ "File:EncodingProcess": 0,
608
+ "File:BitsPerSample": 8,
609
+ "File:ColorComponents": 3,
610
+ "File:YCbCrSubSampling": "1 1",
611
+ "JFIF:JFIFVersion": "1 1",
612
+ "JFIF:ResolutionUnit": 0,
613
+ "JFIF:XResolution": 0,
614
+ "JFIF:YResolution": 0,
615
+ "Composite:ImageSize": "4032 3024",
616
+ "Composite:Megapixels": 12.192768,
617
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
618
+ },
619
+ {
620
+ "File:FileType": "JPEG",
621
+ "File:FileTypeExtension": "JPG",
622
+ "File:MIMEType": "image/jpeg",
623
+ "File:ImageWidth": 4032,
624
+ "File:ImageHeight": 3024,
625
+ "File:EncodingProcess": 0,
626
+ "File:BitsPerSample": 8,
627
+ "File:ColorComponents": 3,
628
+ "File:YCbCrSubSampling": "1 1",
629
+ "JFIF:JFIFVersion": "1 1",
630
+ "JFIF:ResolutionUnit": 0,
631
+ "JFIF:XResolution": 0,
632
+ "JFIF:YResolution": 0,
633
+ "Composite:ImageSize": "4032 3024",
634
+ "Composite:Megapixels": 12.192768,
635
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
636
+ },
637
+ {
638
+ "File:FileType": "JPEG",
639
+ "File:FileTypeExtension": "JPG",
640
+ "File:MIMEType": "image/jpeg",
641
+ "File:ImageWidth": 4032,
642
+ "File:ImageHeight": 3024,
643
+ "File:EncodingProcess": 0,
644
+ "File:BitsPerSample": 8,
645
+ "File:ColorComponents": 3,
646
+ "File:YCbCrSubSampling": "1 1",
647
+ "JFIF:JFIFVersion": "1 1",
648
+ "JFIF:ResolutionUnit": 0,
649
+ "JFIF:XResolution": 0,
650
+ "JFIF:YResolution": 0,
651
+ "Composite:ImageSize": "4032 3024",
652
+ "Composite:Megapixels": 12.192768,
653
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
654
+ },
655
+ {
656
+ "File:FileType": "JPEG",
657
+ "File:FileTypeExtension": "JPG",
658
+ "File:MIMEType": "image/jpeg",
659
+ "File:ImageWidth": 4032,
660
+ "File:ImageHeight": 3024,
661
+ "File:EncodingProcess": 0,
662
+ "File:BitsPerSample": 8,
663
+ "File:ColorComponents": 3,
664
+ "File:YCbCrSubSampling": "1 1",
665
+ "JFIF:JFIFVersion": "1 1",
666
+ "JFIF:ResolutionUnit": 0,
667
+ "JFIF:XResolution": 0,
668
+ "JFIF:YResolution": 0,
669
+ "Composite:ImageSize": "4032 3024",
670
+ "Composite:Megapixels": 12.192768,
671
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
672
+ },
673
+ {
674
+ "File:FileType": "JPEG",
675
+ "File:FileTypeExtension": "JPG",
676
+ "File:MIMEType": "image/jpeg",
677
+ "File:ImageWidth": 4032,
678
+ "File:ImageHeight": 3024,
679
+ "File:EncodingProcess": 0,
680
+ "File:BitsPerSample": 8,
681
+ "File:ColorComponents": 3,
682
+ "File:YCbCrSubSampling": "1 1",
683
+ "JFIF:JFIFVersion": "1 1",
684
+ "JFIF:ResolutionUnit": 0,
685
+ "JFIF:XResolution": 0,
686
+ "JFIF:YResolution": 0,
687
+ "Composite:ImageSize": "4032 3024",
688
+ "Composite:Megapixels": 12.192768,
689
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
690
+ },
691
+ {
692
+ "File:FileType": "JPEG",
693
+ "File:FileTypeExtension": "JPG",
694
+ "File:MIMEType": "image/jpeg",
695
+ "File:ImageWidth": 4032,
696
+ "File:ImageHeight": 3024,
697
+ "File:EncodingProcess": 0,
698
+ "File:BitsPerSample": 8,
699
+ "File:ColorComponents": 3,
700
+ "File:YCbCrSubSampling": "1 1",
701
+ "JFIF:JFIFVersion": "1 1",
702
+ "JFIF:ResolutionUnit": 0,
703
+ "JFIF:XResolution": 0,
704
+ "JFIF:YResolution": 0,
705
+ "Composite:ImageSize": "4032 3024",
706
+ "Composite:Megapixels": 12.192768,
707
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
708
+ },
709
+ {
710
+ "File:FileType": "JPEG",
711
+ "File:FileTypeExtension": "JPG",
712
+ "File:MIMEType": "image/jpeg",
713
+ "File:ImageWidth": 4032,
714
+ "File:ImageHeight": 3024,
715
+ "File:EncodingProcess": 0,
716
+ "File:BitsPerSample": 8,
717
+ "File:ColorComponents": 3,
718
+ "File:YCbCrSubSampling": "1 1",
719
+ "JFIF:JFIFVersion": "1 1",
720
+ "JFIF:ResolutionUnit": 0,
721
+ "JFIF:XResolution": 0,
722
+ "JFIF:YResolution": 0,
723
+ "Composite:ImageSize": "4032 3024",
724
+ "Composite:Megapixels": 12.192768,
725
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
726
+ },
727
+ {
728
+ "File:FileType": "JPEG",
729
+ "File:FileTypeExtension": "JPG",
730
+ "File:MIMEType": "image/jpeg",
731
+ "File:ImageWidth": 4032,
732
+ "File:ImageHeight": 3024,
733
+ "File:EncodingProcess": 0,
734
+ "File:BitsPerSample": 8,
735
+ "File:ColorComponents": 3,
736
+ "File:YCbCrSubSampling": "1 1",
737
+ "JFIF:JFIFVersion": "1 1",
738
+ "JFIF:ResolutionUnit": 0,
739
+ "JFIF:XResolution": 0,
740
+ "JFIF:YResolution": 0,
741
+ "Composite:ImageSize": "4032 3024",
742
+ "Composite:Megapixels": 12.192768,
743
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
744
+ },
745
+ {
746
+ "File:FileType": "JPEG",
747
+ "File:FileTypeExtension": "JPG",
748
+ "File:MIMEType": "image/jpeg",
749
+ "File:ImageWidth": 4032,
750
+ "File:ImageHeight": 3024,
751
+ "File:EncodingProcess": 0,
752
+ "File:BitsPerSample": 8,
753
+ "File:ColorComponents": 3,
754
+ "File:YCbCrSubSampling": "1 1",
755
+ "JFIF:JFIFVersion": "1 1",
756
+ "JFIF:ResolutionUnit": 0,
757
+ "JFIF:XResolution": 0,
758
+ "JFIF:YResolution": 0,
759
+ "Composite:ImageSize": "4032 3024",
760
+ "Composite:Megapixels": 12.192768,
761
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
762
+ },
763
+ {
764
+ "File:FileType": "JPEG",
765
+ "File:FileTypeExtension": "JPG",
766
+ "File:MIMEType": "image/jpeg",
767
+ "File:ImageWidth": 3024,
768
+ "File:ImageHeight": 4032,
769
+ "File:EncodingProcess": 0,
770
+ "File:BitsPerSample": 8,
771
+ "File:ColorComponents": 3,
772
+ "File:YCbCrSubSampling": "1 1",
773
+ "JFIF:JFIFVersion": "1 1",
774
+ "JFIF:ResolutionUnit": 0,
775
+ "JFIF:XResolution": 0,
776
+ "JFIF:YResolution": 0,
777
+ "Composite:ImageSize": "3024 4032",
778
+ "Composite:Megapixels": 12.192768,
779
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
780
+ },
781
+ {
782
+ "File:FileType": "JPEG",
783
+ "File:FileTypeExtension": "JPG",
784
+ "File:MIMEType": "image/jpeg",
785
+ "File:ImageWidth": 3024,
786
+ "File:ImageHeight": 4032,
787
+ "File:EncodingProcess": 0,
788
+ "File:BitsPerSample": 8,
789
+ "File:ColorComponents": 3,
790
+ "File:YCbCrSubSampling": "1 1",
791
+ "JFIF:JFIFVersion": "1 1",
792
+ "JFIF:ResolutionUnit": 0,
793
+ "JFIF:XResolution": 0,
794
+ "JFIF:YResolution": 0,
795
+ "Composite:ImageSize": "3024 4032",
796
+ "Composite:Megapixels": 12.192768,
797
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
798
+ },
799
+ {
800
+ "File:FileType": "JPEG",
801
+ "File:FileTypeExtension": "JPG",
802
+ "File:MIMEType": "image/jpeg",
803
+ "File:ImageWidth": 3024,
804
+ "File:ImageHeight": 4032,
805
+ "File:EncodingProcess": 0,
806
+ "File:BitsPerSample": 8,
807
+ "File:ColorComponents": 3,
808
+ "File:YCbCrSubSampling": "1 1",
809
+ "JFIF:JFIFVersion": "1 1",
810
+ "JFIF:ResolutionUnit": 0,
811
+ "JFIF:XResolution": 0,
812
+ "JFIF:YResolution": 0,
813
+ "Composite:ImageSize": "3024 4032",
814
+ "Composite:Megapixels": 12.192768,
815
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
816
+ },
817
+ {
818
+ "File:FileType": "JPEG",
819
+ "File:FileTypeExtension": "JPG",
820
+ "File:MIMEType": "image/jpeg",
821
+ "File:ImageWidth": 4032,
822
+ "File:ImageHeight": 3024,
823
+ "File:EncodingProcess": 0,
824
+ "File:BitsPerSample": 8,
825
+ "File:ColorComponents": 3,
826
+ "File:YCbCrSubSampling": "1 1",
827
+ "JFIF:JFIFVersion": "1 1",
828
+ "JFIF:ResolutionUnit": 0,
829
+ "JFIF:XResolution": 0,
830
+ "JFIF:YResolution": 0,
831
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
832
+ "XMP:CreateDate": "2025:09:09 16:04:09",
833
+ "XMP:CreatorTool": 18.5,
834
+ "XMP:ModifyDate": "2025:09:09 16:04:09",
835
+ "XMP:DateCreated": "2025:09:09 16:04:09",
836
+ "Composite:ImageSize": "4032 3024",
837
+ "Composite:Megapixels": 12.192768,
838
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
839
+ },
840
+ {
841
+ "File:FileType": "JPEG",
842
+ "File:FileTypeExtension": "JPG",
843
+ "File:MIMEType": "image/jpeg",
844
+ "File:ImageWidth": 4032,
845
+ "File:ImageHeight": 3024,
846
+ "File:EncodingProcess": 0,
847
+ "File:BitsPerSample": 8,
848
+ "File:ColorComponents": 3,
849
+ "File:YCbCrSubSampling": "1 1",
850
+ "JFIF:JFIFVersion": "1 1",
851
+ "JFIF:ResolutionUnit": 0,
852
+ "JFIF:XResolution": 0,
853
+ "JFIF:YResolution": 0,
854
+ "Composite:ImageSize": "4032 3024",
855
+ "Composite:Megapixels": 12.192768,
856
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
857
+ },
858
+ {
859
+ "File:FileType": "JPEG",
860
+ "File:FileTypeExtension": "JPG",
861
+ "File:MIMEType": "image/jpeg",
862
+ "File:ImageWidth": 3024,
863
+ "File:ImageHeight": 4032,
864
+ "File:EncodingProcess": 0,
865
+ "File:BitsPerSample": 8,
866
+ "File:ColorComponents": 3,
867
+ "File:YCbCrSubSampling": "1 1",
868
+ "JFIF:JFIFVersion": "1 1",
869
+ "JFIF:ResolutionUnit": 0,
870
+ "JFIF:XResolution": 0,
871
+ "JFIF:YResolution": 0,
872
+ "Composite:ImageSize": "3024 4032",
873
+ "Composite:Megapixels": 12.192768,
874
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
875
+ },
876
+ {
877
+ "File:FileType": "JPEG",
878
+ "File:FileTypeExtension": "JPG",
879
+ "File:MIMEType": "image/jpeg",
880
+ "File:ImageWidth": 3024,
881
+ "File:ImageHeight": 4032,
882
+ "File:EncodingProcess": 0,
883
+ "File:BitsPerSample": 8,
884
+ "File:ColorComponents": 3,
885
+ "File:YCbCrSubSampling": "1 1",
886
+ "JFIF:JFIFVersion": "1 1",
887
+ "JFIF:ResolutionUnit": 0,
888
+ "JFIF:XResolution": 0,
889
+ "JFIF:YResolution": 0,
890
+ "Composite:ImageSize": "3024 4032",
891
+ "Composite:Megapixels": 12.192768,
892
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
893
+ },
894
+ {
895
+ "File:FileType": "JPEG",
896
+ "File:FileTypeExtension": "JPG",
897
+ "File:MIMEType": "image/jpeg",
898
+ "File:ImageWidth": 4032,
899
+ "File:ImageHeight": 3024,
900
+ "File:EncodingProcess": 0,
901
+ "File:BitsPerSample": 8,
902
+ "File:ColorComponents": 3,
903
+ "File:YCbCrSubSampling": "1 1",
904
+ "JFIF:JFIFVersion": "1 1",
905
+ "JFIF:ResolutionUnit": 0,
906
+ "JFIF:XResolution": 0,
907
+ "JFIF:YResolution": 0,
908
+ "Composite:ImageSize": "4032 3024",
909
+ "Composite:Megapixels": 12.192768,
910
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
911
+ },
912
+ {
913
+ "File:FileType": "JPEG",
914
+ "File:FileTypeExtension": "JPG",
915
+ "File:MIMEType": "image/jpeg",
916
+ "File:ImageWidth": 4032,
917
+ "File:ImageHeight": 3024,
918
+ "File:EncodingProcess": 0,
919
+ "File:BitsPerSample": 8,
920
+ "File:ColorComponents": 3,
921
+ "File:YCbCrSubSampling": "1 1",
922
+ "JFIF:JFIFVersion": "1 1",
923
+ "JFIF:ResolutionUnit": 0,
924
+ "JFIF:XResolution": 0,
925
+ "JFIF:YResolution": 0,
926
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
927
+ "XMP:SemanticSegmentationMatteVersion": 65536,
928
+ "XMP:AuxiliaryImageType": "urn:com:apple:photo:2019:aux:semanticskinmatte",
929
+ "Composite:ImageSize": "4032 3024",
930
+ "Composite:Megapixels": 12.192768,
931
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
932
+ },
933
+ {
934
+ "File:FileType": "JPEG",
935
+ "File:FileTypeExtension": "JPG",
936
+ "File:MIMEType": "image/jpeg",
937
+ "File:ImageWidth": 4032,
938
+ "File:ImageHeight": 3024,
939
+ "File:EncodingProcess": 0,
940
+ "File:BitsPerSample": 8,
941
+ "File:ColorComponents": 3,
942
+ "File:YCbCrSubSampling": "1 1",
943
+ "JFIF:JFIFVersion": "1 1",
944
+ "JFIF:ResolutionUnit": 0,
945
+ "JFIF:XResolution": 0,
946
+ "JFIF:YResolution": 0,
947
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
948
+ "XMP:SemanticSegmentationMatteVersion": 65536,
949
+ "XMP:AuxiliaryImageType": "urn:com:apple:photo:2020:aux:semanticskymatte",
950
+ "Composite:ImageSize": "4032 3024",
951
+ "Composite:Megapixels": 12.192768,
952
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
953
+ },
954
+ {
955
+ "File:FileType": "JPEG",
956
+ "File:FileTypeExtension": "JPG",
957
+ "File:MIMEType": "image/jpeg",
958
+ "File:ImageWidth": 4032,
959
+ "File:ImageHeight": 3024,
960
+ "File:EncodingProcess": 0,
961
+ "File:BitsPerSample": 8,
962
+ "File:ColorComponents": 3,
963
+ "File:YCbCrSubSampling": "1 1",
964
+ "JFIF:JFIFVersion": "1 1",
965
+ "JFIF:ResolutionUnit": 0,
966
+ "JFIF:XResolution": 0,
967
+ "JFIF:YResolution": 0,
968
+ "Composite:ImageSize": "4032 3024",
969
+ "Composite:Megapixels": 12.192768,
970
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
971
+ },
972
+ {
973
+ "File:FileType": "JPEG",
974
+ "File:FileTypeExtension": "JPG",
975
+ "File:MIMEType": "image/jpeg",
976
+ "File:ImageWidth": 4032,
977
+ "File:ImageHeight": 3024,
978
+ "File:EncodingProcess": 0,
979
+ "File:BitsPerSample": 8,
980
+ "File:ColorComponents": 3,
981
+ "File:YCbCrSubSampling": "1 1",
982
+ "JFIF:JFIFVersion": "1 1",
983
+ "JFIF:ResolutionUnit": 0,
984
+ "JFIF:XResolution": 0,
985
+ "JFIF:YResolution": 0,
986
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
987
+ "XMP:SemanticSegmentationMatteVersion": 65536,
988
+ "XMP:AuxiliaryImageType": "urn:com:apple:photo:2019:aux:semanticskinmatte",
989
+ "Composite:ImageSize": "4032 3024",
990
+ "Composite:Megapixels": 12.192768,
991
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
992
+ },
993
+ {
994
+ "File:FileType": "JPEG",
995
+ "File:FileTypeExtension": "JPG",
996
+ "File:MIMEType": "image/jpeg",
997
+ "File:ImageWidth": 4032,
998
+ "File:ImageHeight": 3024,
999
+ "File:EncodingProcess": 0,
1000
+ "File:BitsPerSample": 8,
1001
+ "File:ColorComponents": 3,
1002
+ "File:YCbCrSubSampling": "1 1",
1003
+ "JFIF:JFIFVersion": "1 1",
1004
+ "JFIF:ResolutionUnit": 0,
1005
+ "JFIF:XResolution": 0,
1006
+ "JFIF:YResolution": 0,
1007
+ "Composite:ImageSize": "4032 3024",
1008
+ "Composite:Megapixels": 12.192768,
1009
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
1010
+ },
1011
+ {
1012
+ "File:FileType": "JPEG",
1013
+ "File:FileTypeExtension": "JPG",
1014
+ "File:MIMEType": "image/jpeg",
1015
+ "File:ImageWidth": 4032,
1016
+ "File:ImageHeight": 3024,
1017
+ "File:EncodingProcess": 0,
1018
+ "File:BitsPerSample": 8,
1019
+ "File:ColorComponents": 3,
1020
+ "File:YCbCrSubSampling": "1 1",
1021
+ "JFIF:JFIFVersion": "1 1",
1022
+ "JFIF:ResolutionUnit": 0,
1023
+ "JFIF:XResolution": 0,
1024
+ "JFIF:YResolution": 0,
1025
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
1026
+ "XMP:SemanticSegmentationMatteVersion": 65536,
1027
+ "XMP:AuxiliaryImageType": "urn:com:apple:photo:2019:aux:semanticskinmatte",
1028
+ "Composite:ImageSize": "4032 3024",
1029
+ "Composite:Megapixels": 12.192768,
1030
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
1031
+ },
1032
+ {
1033
+ "File:FileType": "JPEG",
1034
+ "File:FileTypeExtension": "JPG",
1035
+ "File:MIMEType": "image/jpeg",
1036
+ "File:ImageWidth": 4032,
1037
+ "File:ImageHeight": 3024,
1038
+ "File:EncodingProcess": 0,
1039
+ "File:BitsPerSample": 8,
1040
+ "File:ColorComponents": 3,
1041
+ "File:YCbCrSubSampling": "1 1",
1042
+ "JFIF:JFIFVersion": "1 1",
1043
+ "JFIF:ResolutionUnit": 0,
1044
+ "JFIF:XResolution": 0,
1045
+ "JFIF:YResolution": 0,
1046
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
1047
+ "XMP:SemanticSegmentationMatteVersion": 65536,
1048
+ "XMP:AuxiliaryImageType": "urn:com:apple:photo:2019:aux:semanticskinmatte",
1049
+ "Composite:ImageSize": "4032 3024",
1050
+ "Composite:Megapixels": 12.192768,
1051
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
1052
+ },
1053
+ {
1054
+ "File:FileType": "JPEG",
1055
+ "File:FileTypeExtension": "JPG",
1056
+ "File:MIMEType": "image/jpeg",
1057
+ "File:ImageWidth": 4032,
1058
+ "File:ImageHeight": 3024,
1059
+ "File:EncodingProcess": 0,
1060
+ "File:BitsPerSample": 8,
1061
+ "File:ColorComponents": 3,
1062
+ "File:YCbCrSubSampling": "1 1",
1063
+ "JFIF:JFIFVersion": "1 1",
1064
+ "JFIF:ResolutionUnit": 0,
1065
+ "JFIF:XResolution": 0,
1066
+ "JFIF:YResolution": 0,
1067
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
1068
+ "XMP:SemanticSegmentationMatteVersion": 65536,
1069
+ "XMP:AuxiliaryImageType": "urn:com:apple:photo:2020:aux:semanticskymatte",
1070
+ "Composite:ImageSize": "4032 3024",
1071
+ "Composite:Megapixels": 12.192768,
1072
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
1073
+ },
1074
+ {
1075
+ "File:FileType": "JPEG",
1076
+ "File:FileTypeExtension": "JPG",
1077
+ "File:MIMEType": "image/jpeg",
1078
+ "File:ImageWidth": 4032,
1079
+ "File:ImageHeight": 3024,
1080
+ "File:EncodingProcess": 0,
1081
+ "File:BitsPerSample": 8,
1082
+ "File:ColorComponents": 3,
1083
+ "File:YCbCrSubSampling": "1 1",
1084
+ "JFIF:JFIFVersion": "1 1",
1085
+ "JFIF:ResolutionUnit": 0,
1086
+ "JFIF:XResolution": 0,
1087
+ "JFIF:YResolution": 0,
1088
+ "XMP:XMPToolkit": "XMP Core 6.0.0",
1089
+ "XMP:SemanticSegmentationMatteVersion": 65536,
1090
+ "XMP:AuxiliaryImageType": "urn:com:apple:photo:2020:aux:semanticskymatte",
1091
+ "Composite:ImageSize": "4032 3024",
1092
+ "Composite:Megapixels": 12.192768,
1093
+ "_instaraw_icc_profile_file": "iphone_13_pro.icc"
1094
+ }
1095
+ ]
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_13_pro.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:175c1792ab5d4489d5e77e2bc1273d20726b26d7fe2f0a107bf8ab1105c67d54
3
+ size 333807
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_plus.icc ADDED
Binary file (536 Bytes). View file
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_plus.json ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_plus.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0d9f9c8fde2c54043f4e7610f93ce2673a40b674384781279ff8e379a6831d38
3
+ size 1006038
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_pro_max.icc ADDED
Binary file (536 Bytes). View file
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_pro_max.json ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_14_pro_max.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f3a7fbeb41d4db2f9a73d800abf5a29ec523e6b8f006d1cf5189fb20afc88927
3
+ size 1014665
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_15_pro_max.icc ADDED
Binary file (536 Bytes). View file
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_15_pro_max.json ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/modules/authenticity_profiles/iphone_15_pro_max.npz ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3223228cd26112bc9bd06d1587bce91f604537aab10e7bfb643ccc39291c3fa5
3
+ size 563974
custom_nodes/ComfyUI_INSTARAW/modules/color_profiles/sRGB_IEC61966-2-1_no_black_scaling.icc ADDED
Binary file (3.05 kB). View file
 
custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/__init__.py ADDED
File without changes
custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/DJI_O3AirUnit_CineonFilmLog_minimal_natural_sunrise_snow_forest_glow_1.DJI_0064_stabilized_4.cube ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/KORNEV_LUT_HLG_3.cube ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/LOOK1.cube ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/LOW_SAT_-_Braw_Vivid_Ludeman_LUT.cube ADDED
The diff for this file is too large to render. See raw diff
 
custom_nodes/ComfyUI_INSTARAW/modules/detection_bypass/_luts/Shibuya_01.cube ADDED
The diff for this file is too large to render. See raw diff