@cloudinary/asset-management-mcp 0.8.1 → 0.9.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +2 -2
  2. package/bin/mcp-server.js +4849 -305
  3. package/bin/mcp-server.js.map +25 -21
  4. package/esm/landing-page.js +1 -1
  5. package/esm/lib/config.d.ts +3 -3
  6. package/esm/lib/config.js +3 -3
  7. package/esm/lib/config.js.map +1 -1
  8. package/esm/lib/encodings.d.ts +1 -0
  9. package/esm/lib/encodings.d.ts.map +1 -1
  10. package/esm/lib/encodings.js +26 -5
  11. package/esm/lib/encodings.js.map +1 -1
  12. package/esm/lib/security.d.ts +1 -1
  13. package/esm/lib/security.d.ts.map +1 -1
  14. package/esm/lib/security.js +29 -17
  15. package/esm/lib/security.js.map +1 -1
  16. package/esm/mcp-server/mcp-server.js +1 -1
  17. package/esm/mcp-server/mcp-server.js.map +1 -1
  18. package/esm/mcp-server/server.extensions.d.ts.map +1 -1
  19. package/esm/mcp-server/server.extensions.js +84 -0
  20. package/esm/mcp-server/server.extensions.js.map +1 -1
  21. package/esm/mcp-server/server.js +1 -1
  22. package/esm/mcp-server/server.js.map +1 -1
  23. package/esm/mcp-server/tools/assetsGetResourceByAssetId.d.ts.map +1 -1
  24. package/esm/mcp-server/tools/assetsGetResourceByAssetId.js +4 -0
  25. package/esm/mcp-server/tools/assetsGetResourceByAssetId.js.map +1 -1
  26. package/esm/mcp-server/tools/assetsListImages.d.ts +1 -1
  27. package/esm/mcp-server/tools/assetsListImages.d.ts.map +1 -1
  28. package/esm/mcp-server/tools/assetsListImages.js +4 -0
  29. package/esm/mcp-server/tools/assetsListImages.js.map +1 -1
  30. package/esm/mcp-server/tools/assetsListRawFiles.d.ts +1 -1
  31. package/esm/mcp-server/tools/assetsListRawFiles.d.ts.map +1 -1
  32. package/esm/mcp-server/tools/assetsListRawFiles.js +4 -0
  33. package/esm/mcp-server/tools/assetsListRawFiles.js.map +1 -1
  34. package/esm/mcp-server/tools/assetsListVideos.d.ts +1 -1
  35. package/esm/mcp-server/tools/assetsListVideos.d.ts.map +1 -1
  36. package/esm/mcp-server/tools/assetsListVideos.js +4 -0
  37. package/esm/mcp-server/tools/assetsListVideos.js.map +1 -1
  38. package/esm/mcp-server/tools/searchSearchAssets.d.ts.map +1 -1
  39. package/esm/mcp-server/tools/searchSearchAssets.js +4 -0
  40. package/esm/mcp-server/tools/searchSearchAssets.js.map +1 -1
  41. package/esm/mcp-server/tools/uploadUpload.d.ts.map +1 -1
  42. package/esm/mcp-server/tools/uploadUpload.js +4 -0
  43. package/esm/mcp-server/tools/uploadUpload.js.map +1 -1
  44. package/esm/mcp-server/tools.d.ts +2 -0
  45. package/esm/mcp-server/tools.d.ts.map +1 -1
  46. package/esm/mcp-server/tools.js +2 -0
  47. package/esm/mcp-server/tools.js.map +1 -1
  48. package/esm/mcp-server/widgets/asset-details-widget.d.ts +3 -0
  49. package/esm/mcp-server/widgets/asset-details-widget.d.ts.map +1 -0
  50. package/esm/mcp-server/widgets/asset-details-widget.js +299 -0
  51. package/esm/mcp-server/widgets/asset-details-widget.js.map +1 -0
  52. package/esm/mcp-server/widgets/asset-gallery-widget.d.ts +4 -0
  53. package/esm/mcp-server/widgets/asset-gallery-widget.d.ts.map +1 -0
  54. package/esm/mcp-server/widgets/asset-gallery-widget.js +1063 -0
  55. package/esm/mcp-server/widgets/asset-gallery-widget.js.map +1 -0
  56. package/esm/mcp-server/widgets/asset-upload-widget.d.ts +3 -0
  57. package/esm/mcp-server/widgets/asset-upload-widget.d.ts.map +1 -0
  58. package/esm/mcp-server/widgets/asset-upload-widget.js +1093 -0
  59. package/esm/mcp-server/widgets/asset-upload-widget.js.map +1 -0
  60. package/esm/mcp-server/widgets/widget-shared.d.ts +9 -0
  61. package/esm/mcp-server/widgets/widget-shared.d.ts.map +1 -0
  62. package/esm/mcp-server/widgets/widget-shared.js +2019 -0
  63. package/esm/mcp-server/widgets/widget-shared.js.map +1 -0
  64. package/esm/models/fieldsspec.d.ts +1 -1
  65. package/package.json +1 -1
  66. package/src/landing-page.ts +1 -1
  67. package/src/lib/config.ts +3 -3
  68. package/src/lib/encodings.ts +32 -4
  69. package/src/lib/security.ts +14 -2
  70. package/src/mcp-server/mcp-server.ts +1 -1
  71. package/src/mcp-server/server.extensions.ts +97 -0
  72. package/src/mcp-server/server.ts +1 -1
  73. package/src/mcp-server/tools/assetsGetResourceByAssetId.ts +4 -0
  74. package/src/mcp-server/tools/assetsListImages.ts +4 -0
  75. package/src/mcp-server/tools/assetsListRawFiles.ts +4 -0
  76. package/src/mcp-server/tools/assetsListVideos.ts +4 -0
  77. package/src/mcp-server/tools/searchSearchAssets.ts +4 -0
  78. package/src/mcp-server/tools/uploadUpload.ts +4 -0
  79. package/src/mcp-server/tools.ts +4 -0
  80. package/src/mcp-server/widgets/asset-details-widget.ts +313 -0
  81. package/src/mcp-server/widgets/asset-gallery-widget.ts +1077 -0
  82. package/src/mcp-server/widgets/asset-upload-widget.ts +1115 -0
  83. package/src/mcp-server/widgets/widget-shared.ts +2030 -0
@@ -0,0 +1,2019 @@
1
+ /*
2
+ * Shared building blocks for MCP App widgets (gallery + details).
3
+ * Each export is a raw string fragment to be interpolated into the
4
+ * final HTML template literal of each widget.
5
+ */
6
+ import { toJSONSchema } from "zod";
7
+ import { Info$zodSchema } from "../../models/info.js";
8
+ import { UploadResponse$zodSchema } from "../../models/uploadresponse.js";
9
+ // ── Build-time tooltip map from Zod schema descriptions ─────────────
10
+ function extractSchemaDescriptions(schema) {
11
+ const props = schema
12
+ .properties || {};
13
+ const result = {};
14
+ for (const [key, prop] of Object.entries(props)) {
15
+ const p = prop;
16
+ if (p?.description)
17
+ result[key] = p.description;
18
+ }
19
+ return result;
20
+ }
21
+ const _infoSchema = toJSONSchema(Info$zodSchema, {
22
+ unrepresentable: "any",
23
+ });
24
+ const _uploadSchema = toJSONSchema(UploadResponse$zodSchema, {
25
+ unrepresentable: "any",
26
+ });
27
+ const TOOLTIP_MAP_JSON = JSON.stringify({
28
+ ...extractSchemaDescriptions(_infoSchema),
29
+ ...extractSchemaDescriptions(_uploadSchema),
30
+ });
31
+ // ── CSS: CLDS Design Tokens (light + dark) ──────────────────────────
32
+ export const SHARED_CSS_TOKENS = /* css */ `
33
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
34
+
35
+ :root {
36
+ --cld-primary: #3448c5;
37
+ --cld-primary-light: #4c64d7;
38
+ --cld-bg: #ffffff;
39
+ --cld-bg2: #f9fafb;
40
+ --cld-bg3: #f3f4f7;
41
+ --cld-bg4: #edeef3;
42
+ --cld-text: #0a0c0f;
43
+ --cld-text2: #333b4c;
44
+ --cld-text3: #90a0b3;
45
+ --cld-border: #d1d6e0;
46
+ --cld-border2: #c2c9d6;
47
+ --cld-accent: #3448c5;
48
+ --cld-accent-bg: #f1f2f9;
49
+ --cld-error: #CE190D;
50
+ --cld-warning: #ff620c;
51
+ --cld-success: #22AA00;
52
+ --cld-radius: 8px;
53
+ --cld-radius-sm: 4px;
54
+ --cld-radius-lg: 16px;
55
+ --cld-shadow-sm: 0 2px 4px 0 rgba(0,0,0,0.25);
56
+ --cld-shadow-md: 0 4px 5px 0 rgba(0,0,0,0.2), 0 3px 14px 3px rgba(0,0,0,0.12), 0 8px 10px 1px rgba(0,0,0,0.14);
57
+ --cld-shadow-lg: 0 24px 24px 0 rgba(0,0,0,0.3), 0 0 24px 0 rgba(0,0,0,0.22);
58
+ --cld-sp-xxs: 0.25rem;
59
+ --cld-sp-xs: 0.5rem;
60
+ --cld-sp-sm: 0.75rem;
61
+ --cld-sp-md: 1rem;
62
+ --cld-sp-lg: 1.25rem;
63
+ --cld-sp-xl: 2rem;
64
+ --cld-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
65
+ --cld-font-xxs: 0.75rem;
66
+ --cld-font-xs: 0.875rem;
67
+ --cld-font-sm: 1rem;
68
+ --cld-chip-tag-bg: #f1f2f9;
69
+ --cld-chip-tag-fg: #3448c5;
70
+ --cld-chip-set-bg: #e6faf6;
71
+ --cld-chip-set-fg: #13a5aa;
72
+ --cld-chip-set-border: #b2e8e9;
73
+ --cld-chip-date-bg: #fff8eb;
74
+ --cld-chip-date-fg: #a16207;
75
+ --cld-chip-date-border: #fde68a;
76
+ --cld-chip-int-bg: #f5f0ff;
77
+ --cld-chip-int-fg: #7c3aed;
78
+ --cld-chip-int-border: #e9d5ff;
79
+ }
80
+
81
+ [data-theme="dark"], .dark {
82
+ --cld-primary: #0D9AFF;
83
+ --cld-primary-light: #51a3ff;
84
+ --cld-bg: #1f242e;
85
+ --cld-bg2: #14181e;
86
+ --cld-bg3: #090c0f;
87
+ --cld-bg4: #000000;
88
+ --cld-text: #ffffff;
89
+ --cld-text2: #d1d6e0;
90
+ --cld-text3: #90a0b3;
91
+ --cld-border: #3d475c;
92
+ --cld-border2: #535f7a;
93
+ --cld-accent: #0D9AFF;
94
+ --cld-accent-bg: rgba(13,154,255,0.12);
95
+ --cld-error: #ff5959;
96
+ --cld-warning: #ffa359;
97
+ --cld-success: #9affa6;
98
+ --cld-chip-tag-bg: rgba(13,154,255,0.15);
99
+ --cld-chip-tag-fg: #0D9AFF;
100
+ --cld-chip-set-bg: rgba(72,208,216,0.15);
101
+ --cld-chip-set-fg: #7dedff;
102
+ --cld-chip-set-border: rgba(72,208,216,0.3);
103
+ --cld-chip-date-bg: rgba(255,196,121,0.15);
104
+ --cld-chip-date-fg: #ffc479;
105
+ --cld-chip-date-border: rgba(255,196,121,0.3);
106
+ --cld-chip-int-bg: rgba(167,111,255,0.15);
107
+ --cld-chip-int-fg: #a76fff;
108
+ --cld-chip-int-border: rgba(167,111,255,0.3);
109
+ }
110
+ [data-theme="dark"] .status-ok, .dark .status-ok { background: #166534; color: #bbf7d0; }
111
+ [data-theme="dark"] .status-warn, .dark .status-warn { background: #854d0e; color: #fef08a; }
112
+ [data-theme="dark"] .status-err, .dark .status-err { background: #991b1b; color: #fecaca; }
113
+
114
+ body {
115
+ font-family: var(--cld-font);
116
+ background: var(--cld-bg);
117
+ color: var(--cld-text);
118
+ padding: var(--cld-sp-md);
119
+ line-height: 1.5;
120
+ font-size: var(--cld-font-xs);
121
+ position: relative;
122
+ }
123
+ .theme-btn {
124
+ position: absolute; top: 4px; right: 4px; z-index: 900;
125
+ width: 22px; height: 22px; border-radius: 50%;
126
+ border: 1px solid transparent; background: transparent;
127
+ color: var(--cld-text3); cursor: pointer;
128
+ display: flex; align-items: center; justify-content: center;
129
+ padding: 0; transition: background 0.15s, color 0.15s, border-color 0.15s;
130
+ opacity: 0.5;
131
+ }
132
+ .theme-btn:hover { background: var(--cld-bg3); color: var(--cld-text); border-color: var(--cld-border); opacity: 1; }
133
+ .theme-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
134
+ `;
135
+ // ── CSS: Shared component styles ────────────────────────────────────
136
+ export const SHARED_CSS_COMPONENTS = /* css */ `
137
+ .link { cursor: pointer; }
138
+ .link:hover { color: var(--cld-accent); text-decoration: underline; }
139
+
140
+ /* Modal */
141
+ .modal-overlay {
142
+ position: fixed; inset: 0;
143
+ background: rgba(0,0,0,0.45);
144
+ display: flex; align-items: center; justify-content: center;
145
+ z-index: 1000; backdrop-filter: blur(3px); padding: 24px;
146
+ }
147
+ .modal {
148
+ background: var(--cld-bg); border: 1px solid var(--cld-border);
149
+ border-radius: var(--cld-radius); width: 100%; max-width: 620px;
150
+ max-height: 85vh; display: flex; flex-direction: column;
151
+ box-shadow: var(--cld-shadow-lg); animation: modalIn 0.15s ease-out;
152
+ }
153
+ @keyframes modalIn {
154
+ from { opacity: 0; transform: scale(0.96) translateY(8px); }
155
+ to { opacity: 1; transform: scale(1) translateY(0); }
156
+ }
157
+ .modal-header {
158
+ display: flex; align-items: center; gap: 12px;
159
+ padding: 16px 20px; border-bottom: 1px solid var(--cld-border); flex-shrink: 0;
160
+ }
161
+ .modal-header-thumb {
162
+ width: 40px; height: 40px; border-radius: 6px;
163
+ object-fit: cover; background: var(--cld-bg3); flex-shrink: 0;
164
+ }
165
+ .modal-header-info { flex: 1; min-width: 0; }
166
+ .modal-header-info h2 {
167
+ font-size: 14px; font-weight: 600;
168
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
169
+ }
170
+ .modal-header-sub { font-size: 11px; color: var(--cld-text3); margin-top: 2px; }
171
+ .modal-close {
172
+ background: var(--cld-bg3); border: 1px solid var(--cld-border);
173
+ width: 28px; height: 28px; border-radius: 6px; cursor: pointer;
174
+ display: flex; align-items: center; justify-content: center;
175
+ font-size: 16px; color: var(--cld-text2); flex-shrink: 0; font-family: inherit;
176
+ }
177
+ .modal-close:hover { background: var(--cld-border); }
178
+ .modal-body { overflow-y: auto; padding: 0; }
179
+ .modal-hero {
180
+ width: 100%; max-height: 220px; object-fit: contain;
181
+ background: var(--cld-bg3); display: block;
182
+ }
183
+ .modal-loading { text-align: center; padding: 48px 20px; color: var(--cld-text2); font-size: 13px; }
184
+ .modal-loading .spinner {
185
+ display: inline-block; width: 24px; height: 24px;
186
+ border: 2.5px solid var(--cld-border); border-top-color: var(--cld-accent);
187
+ border-radius: 50%; animation: spin 0.6s linear infinite; margin-bottom: 10px;
188
+ }
189
+ @keyframes spin { to { transform: rotate(360deg); } }
190
+
191
+ /* Detail sections */
192
+ .detail-section { padding: 14px 20px; border-bottom: 1px solid var(--cld-bg3); }
193
+ .detail-section:last-child { border-bottom: none; }
194
+ .detail-section-title {
195
+ font-size: 10px; font-weight: 700; text-transform: uppercase;
196
+ letter-spacing: 0.8px; color: var(--cld-text3); margin-bottom: 10px;
197
+ display: flex; align-items: center; gap: 6px;
198
+ }
199
+ .detail-section-title .count {
200
+ background: var(--cld-bg3); padding: 1px 6px; border-radius: 8px;
201
+ font-size: 10px; font-weight: 600; color: var(--cld-text2);
202
+ }
203
+ details.detail-section > summary.detail-section-title {
204
+ cursor: pointer; list-style: none; user-select: none;
205
+ }
206
+ details.detail-section > summary.detail-section-title::before {
207
+ content: "\\25B6"; display: inline-block; width: 14px; font-size: 9px;
208
+ transition: transform 0.15s ease; margin-right: 4px;
209
+ }
210
+ details.detail-section[open] > summary.detail-section-title::before {
211
+ transform: rotate(90deg);
212
+ }
213
+ details.detail-section > summary.detail-section-title::-webkit-details-marker { display: none; }
214
+ .detail-grid {
215
+ display: grid; grid-template-columns: 1fr 1fr; gap: 1px;
216
+ background: var(--cld-bg3); border-radius: var(--cld-radius-sm); overflow: hidden;
217
+ }
218
+ .detail-cell { background: var(--cld-bg); padding: 8px 12px; }
219
+ .detail-cell-key { font-size: 10px; color: var(--cld-text3); font-weight: 500; margin-bottom: 2px; }
220
+ .detail-cell-val { font-size: 12px; color: var(--cld-text); font-weight: 500; word-break: break-all; }
221
+ .detail-cell-val.link-val { color: var(--cld-accent); cursor: pointer; }
222
+ .detail-cell-val.link-val:hover { text-decoration: underline; }
223
+ .detail-cell.full-width { grid-column: 1 / -1; }
224
+
225
+ /* Chips */
226
+ .chip-list { display: flex; flex-wrap: wrap; gap: 5px; }
227
+ .chip { font-size: 11px; padding: 3px 10px; border-radius: 12px; font-weight: 500; white-space: nowrap; }
228
+ .chip-tag { background: var(--cld-chip-tag-bg); color: var(--cld-chip-tag-fg); }
229
+ .chip-set { background: var(--cld-chip-set-bg); color: var(--cld-chip-set-fg); border: 1px solid var(--cld-chip-set-border); }
230
+ .chip-date { background: var(--cld-chip-date-bg); color: var(--cld-chip-date-fg); border: 1px solid var(--cld-chip-date-border); }
231
+ .chip-int { background: var(--cld-chip-int-bg); color: var(--cld-chip-int-fg); border: 1px solid var(--cld-chip-int-border); }
232
+
233
+ /* Meta rows */
234
+ .meta-row {
235
+ display: flex; align-items: baseline; padding: 6px 0;
236
+ border-bottom: 1px solid var(--cld-bg3); gap: 8px; font-size: 12px;
237
+ }
238
+ .meta-row:last-child { border-bottom: none; }
239
+ .meta-key {
240
+ color: var(--cld-text2); min-width: 0; flex-shrink: 0; max-width: 45%;
241
+ font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px;
242
+ }
243
+ .meta-val { color: var(--cld-text); flex: 1; min-width: 0; text-align: right; }
244
+
245
+ /* Derived assets */
246
+ .derived-card {
247
+ display: flex; align-items: center; gap: 10px; padding: 8px 0;
248
+ border-bottom: 1px solid var(--cld-bg3); font-size: 11px;
249
+ }
250
+ .derived-card:last-child { border-bottom: none; }
251
+ .derived-thumb {
252
+ width: 48px; height: 36px; border-radius: 4px;
253
+ object-fit: cover; background: var(--cld-bg3); flex-shrink: 0;
254
+ }
255
+ .derived-info { flex: 1; min-width: 0; }
256
+ .derived-tx { color: var(--cld-text); font-family: monospace; font-size: 10px; word-break: break-all; }
257
+ .derived-meta { color: var(--cld-text3); font-size: 10px; margin-top: 2px; }
258
+ .derived-open { color: var(--cld-accent); cursor: pointer; font-weight: 500; white-space: nowrap; font-size: 11px; }
259
+ .derived-open:hover { text-decoration: underline; }
260
+
261
+ /* Error */
262
+ .modal-error { text-align: center; padding: 32px 20px; color: var(--cld-text2); font-size: 13px; }
263
+
264
+ /* Status / loading */
265
+ .status { text-align: center; padding: 48px 16px; color: var(--cld-text2); font-size: 14px; }
266
+ .status .icon { font-size: 32px; margin-bottom: 8px; }
267
+
268
+ /* Fetch prompt */
269
+ .prompt { text-align: center; padding: 48px 24px; color: var(--cld-text2); }
270
+ .prompt-icon { font-size: 36px; margin-bottom: 12px; }
271
+ .prompt-title { font-size: 15px; font-weight: 600; color: var(--cld-text); margin-bottom: 6px; }
272
+ .prompt-desc { font-size: 13px; max-width: 420px; margin: 0 auto 20px; line-height: 1.5; }
273
+ .prompt-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
274
+ .prompt-btn {
275
+ padding: 8px 20px; border-radius: var(--cld-radius); font-size: 13px; font-weight: 500;
276
+ cursor: pointer; border: 1px solid var(--cld-border); background: var(--cld-bg2);
277
+ color: var(--cld-text); font-family: inherit; transition: background 0.15s, border-color 0.15s;
278
+ }
279
+ .prompt-btn:hover { background: var(--cld-bg3); border-color: var(--cld-border2); }
280
+ .prompt-btn-primary { background: var(--cld-primary); color: #fff; border-color: var(--cld-primary); }
281
+ .prompt-btn-primary:hover { background: var(--cld-primary-light); border-color: var(--cld-primary-light); }
282
+
283
+ /* Error toast */
284
+ .error-toast {
285
+ position: fixed; bottom: 16px; left: 16px; right: 16px;
286
+ background: var(--cld-error); color: #fff; padding: 12px 16px;
287
+ border-radius: var(--cld-radius); box-shadow: var(--cld-shadow-md);
288
+ font-size: 13px; z-index: 2000; display: flex; align-items: flex-start;
289
+ gap: 10px; animation: toastIn 0.2s ease-out; max-width: 600px; margin: 0 auto;
290
+ }
291
+ .error-toast-icon { font-size: 18px; flex-shrink: 0; line-height: 1; }
292
+ .error-toast-body { flex: 1; min-width: 0; }
293
+ .error-toast-title { font-weight: 600; margin-bottom: 2px; }
294
+ .error-toast-msg { font-size: 12px; opacity: 0.9; word-break: break-word; }
295
+ .error-toast-close {
296
+ background: none; border: none; color: #fff; cursor: pointer;
297
+ font-size: 16px; opacity: 0.8; padding: 0 2px; flex-shrink: 0; font-family: inherit;
298
+ }
299
+ .error-toast-close:hover { opacity: 1; }
300
+ @keyframes toastIn {
301
+ from { opacity: 0; transform: translateY(12px); }
302
+ to { opacity: 1; transform: translateY(0); }
303
+ }
304
+
305
+ /* Thumb overlays */
306
+ .thumb-overlay {
307
+ position: absolute; inset: 0; display: flex;
308
+ align-items: center; justify-content: center; pointer-events: none;
309
+ }
310
+ .play-icon {
311
+ width: 40px; height: 40px; background: rgba(0,0,0,0.55);
312
+ border-radius: 50%; display: flex; align-items: center; justify-content: center;
313
+ }
314
+ .play-icon::after {
315
+ content: ""; display: block; width: 0; height: 0;
316
+ border-style: solid; border-width: 8px 0 8px 14px;
317
+ border-color: transparent transparent transparent #fff; margin-left: 3px;
318
+ }
319
+ .audio-icon {
320
+ width: 40px; height: 40px; background: rgba(0,0,0,0.55);
321
+ border-radius: 50%; display: flex; align-items: center; justify-content: center;
322
+ color: #fff; font-size: 18px;
323
+ }
324
+ .duration-badge {
325
+ position: absolute; bottom: 6px; right: 6px;
326
+ background: rgba(0,0,0,0.7); color: #fff; font-size: 10px;
327
+ font-weight: 600; padding: 2px 6px; border-radius: 4px;
328
+ font-variant-numeric: tabular-nums; backdrop-filter: blur(4px);
329
+ }
330
+ .status-badge {
331
+ display: inline-block; font-size: 11px; font-weight: 600;
332
+ padding: 1px 8px; border-radius: 10px; text-transform: capitalize;
333
+ }
334
+ .status-ok { background: #dcfce7; color: #166534; }
335
+ .status-warn { background: #fef9c3; color: #854d0e; }
336
+ .status-err { background: #fee2e2; color: #991b1b; }
337
+ .file-icon {
338
+ display: flex; flex-direction: column; align-items: center;
339
+ justify-content: center; gap: 4px; color: var(--cld-text3);
340
+ }
341
+ .file-icon svg { width: 36px; height: 36px; }
342
+ .file-icon-label { font-size: 10px; font-weight: 600; text-transform: uppercase; }
343
+
344
+ /* Native media players */
345
+ .hero-video {
346
+ width: 100%; max-height: 300px; display: block;
347
+ background: #000; border-radius: 0;
348
+ }
349
+ .hero-audio-wrap {
350
+ position: relative; padding: 20px;
351
+ background: var(--cld-bg3); display: flex; flex-direction: column;
352
+ align-items: center; gap: 12px;
353
+ }
354
+ .hero-audio-waveform {
355
+ width: 100%; max-height: 120px; object-fit: contain; display: block;
356
+ opacity: 0.6; border-radius: var(--cld-radius-sm);
357
+ }
358
+ .hero-audio-wrap audio { width: 100%; max-width: 500px; }
359
+ .hero-audio-note {
360
+ font-size: 28px; color: var(--cld-text3); margin-bottom: 4px;
361
+ }
362
+ .media-modal-video {
363
+ width: 100%; display: block; background: #000;
364
+ max-height: 60vh;
365
+ }
366
+ .media-modal-audio-wrap {
367
+ padding: 24px 20px; background: var(--cld-bg3);
368
+ display: flex; flex-direction: column; align-items: center; gap: 12px;
369
+ }
370
+ .media-modal-audio-wrap img {
371
+ width: 100%; max-height: 100px; object-fit: contain; opacity: 0.6;
372
+ border-radius: var(--cld-radius-sm);
373
+ }
374
+ .media-modal-audio-wrap audio { width: 100%; max-width: 480px; }
375
+ .thumb-overlay.playable { pointer-events: auto; cursor: pointer; }
376
+
377
+ /* Upload widget */
378
+ .upload-zone {
379
+ border: 2px dashed var(--cld-border2); border-radius: var(--cld-radius);
380
+ padding: 40px 24px; text-align: center; cursor: pointer;
381
+ transition: border-color 0.2s, background 0.2s;
382
+ background: var(--cld-bg2);
383
+ }
384
+ .upload-zone:hover { border-color: var(--cld-accent); background: var(--cld-accent-bg); }
385
+ .upload-zone.dragover { border-color: var(--cld-accent); background: var(--cld-accent-bg); }
386
+ .upload-zone-icon { font-size: 36px; margin-bottom: 8px; color: var(--cld-text3); }
387
+ .upload-zone-text { font-size: 14px; color: var(--cld-text2); margin-bottom: 4px; }
388
+ .upload-zone-hint { font-size: 12px; color: var(--cld-text3); }
389
+ .upload-zone-btn {
390
+ display: inline-block; margin-top: 14px; padding: 8px 22px;
391
+ border-radius: var(--cld-radius); font-size: 13px; font-weight: 500;
392
+ cursor: pointer; border: 1px solid var(--cld-accent); background: var(--cld-accent);
393
+ color: #fff; font-family: inherit; transition: background 0.15s;
394
+ }
395
+ .upload-zone-btn:hover { background: var(--cld-primary-light); border-color: var(--cld-primary-light); }
396
+ .upload-or { margin: 16px 0; font-size: 12px; color: var(--cld-text3); display: flex; align-items: center; gap: 10px; }
397
+ .upload-or::before, .upload-or::after { content: ""; flex: 1; height: 1px; background: var(--cld-border); }
398
+ .upload-url-row { display: flex; gap: 8px; }
399
+ .upload-url-input {
400
+ flex: 1; padding: 8px 12px; border-radius: var(--cld-radius-sm);
401
+ border: 1px solid var(--cld-border); background: var(--cld-bg);
402
+ color: var(--cld-text); font-size: 13px; font-family: inherit; outline: none;
403
+ }
404
+ .upload-url-input:focus { border-color: var(--cld-accent); }
405
+ .upload-url-input::placeholder { color: var(--cld-text3); }
406
+ .upload-url-btn {
407
+ padding: 8px 16px; border-radius: var(--cld-radius-sm);
408
+ font-size: 13px; font-weight: 500; cursor: pointer;
409
+ border: 1px solid var(--cld-accent); background: transparent;
410
+ color: var(--cld-accent); font-family: inherit; transition: background 0.15s;
411
+ white-space: nowrap;
412
+ }
413
+ .upload-url-btn:hover { background: var(--cld-accent-bg); }
414
+ .upload-params {
415
+ margin-top: 16px; padding: 12px 16px; background: var(--cld-bg3);
416
+ border-radius: var(--cld-radius-sm); font-size: 12px; color: var(--cld-text2);
417
+ }
418
+ .upload-params-title { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; color: var(--cld-text3); margin-bottom: 6px; }
419
+ .upload-params .chip { margin-right: 4px; margin-bottom: 4px; }
420
+ .upload-preview {
421
+ display: flex; align-items: center; gap: 14px; padding: 16px;
422
+ background: var(--cld-bg2); border: 1px solid var(--cld-border);
423
+ border-radius: var(--cld-radius); margin-bottom: 16px;
424
+ }
425
+ .upload-preview-thumb {
426
+ width: 56px; height: 56px; border-radius: var(--cld-radius-sm);
427
+ object-fit: cover; background: var(--cld-bg3); flex-shrink: 0;
428
+ }
429
+ .upload-preview-icon {
430
+ width: 56px; height: 56px; border-radius: var(--cld-radius-sm);
431
+ background: var(--cld-bg3); flex-shrink: 0; display: flex;
432
+ align-items: center; justify-content: center; font-size: 24px; color: var(--cld-text3);
433
+ }
434
+ .upload-preview-info { flex: 1; min-width: 0; }
435
+ .upload-preview-name { font-size: 13px; font-weight: 600; color: var(--cld-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
436
+ .upload-preview-meta { font-size: 11px; color: var(--cld-text3); margin-top: 2px; }
437
+ .upload-progress-wrap { margin-top: 8px; }
438
+ .upload-progress-bar {
439
+ height: 6px; border-radius: 3px; background: var(--cld-bg3); overflow: hidden;
440
+ }
441
+ .upload-progress-fill {
442
+ height: 100%; background: var(--cld-accent); border-radius: 3px;
443
+ transition: width 0.3s ease; width: 0%;
444
+ }
445
+ .upload-progress-text { font-size: 11px; color: var(--cld-text3); margin-top: 4px; text-align: center; }
446
+ .upload-error-msg {
447
+ background: #fef2f2; border: 1px solid #fecaca; border-radius: var(--cld-radius);
448
+ color: #991b1b; padding: 10px 14px; font-size: 12px; margin-top: 8px;
449
+ word-break: break-word; line-height: 1.5;
450
+ }
451
+ .upload-result {
452
+ border: 1px solid var(--cld-border); border-radius: var(--cld-radius);
453
+ overflow: hidden;
454
+ }
455
+ .upload-result-hero {
456
+ position: relative; background: var(--cld-bg3);
457
+ display: flex; align-items: center; justify-content: center; min-height: 120px;
458
+ }
459
+ .upload-result-hero img { width: 100%; max-height: 260px; object-fit: contain; display: block; }
460
+ .upload-result-hero .file-icon { padding: 30px 20px; }
461
+ .upload-result-body { padding: 16px; }
462
+ .upload-result-title {
463
+ font-size: 15px; font-weight: 600; color: var(--cld-text); margin-bottom: 12px;
464
+ display: flex; align-items: center; gap: 8px;
465
+ }
466
+ .upload-result-title .success-icon { color: var(--cld-success); font-size: 18px; }
467
+ .upload-actions { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
468
+ .upload-actions .prompt-btn { font-size: 12px; padding: 6px 16px; }
469
+
470
+ /* Upload form fields */
471
+ .upload-form {
472
+ margin-top: 12px; display: grid; grid-template-columns: 1fr 1fr;
473
+ gap: 10px;
474
+ }
475
+ .upload-field { display: flex; flex-direction: column; gap: 3px; }
476
+ .upload-field.full-width { grid-column: 1 / -1; }
477
+ .upload-field label {
478
+ font-size: 10px; font-weight: 700; text-transform: uppercase;
479
+ letter-spacing: 0.6px; color: var(--cld-text3);
480
+ }
481
+ .upload-field input[type="text"],
482
+ .upload-field input[type="number"],
483
+ .upload-field select,
484
+ .upload-field textarea {
485
+ padding: 7px 10px; border-radius: var(--cld-radius-sm);
486
+ border: 1px solid var(--cld-border); background: var(--cld-bg);
487
+ color: var(--cld-text); font-size: 13px; font-family: inherit; outline: none;
488
+ width: 100%;
489
+ }
490
+ .upload-field input[type="text"]:focus,
491
+ .upload-field input[type="number"]:focus,
492
+ .upload-field select:focus,
493
+ .upload-field textarea:focus { border-color: var(--cld-accent); }
494
+ .upload-field input::placeholder,
495
+ .upload-field textarea::placeholder { color: var(--cld-text3); }
496
+ .upload-field textarea { resize: vertical; min-height: 36px; }
497
+ .upload-field select { cursor: pointer; appearance: auto; }
498
+
499
+ /* Help icon ("?") with inline bubble */
500
+ .help-toggle {
501
+ display: inline-flex; align-items: center; justify-content: center;
502
+ width: 14px; height: 14px; border-radius: 50%;
503
+ background: var(--cld-border); color: var(--cld-text2);
504
+ font-size: 9px; font-weight: 700; cursor: pointer;
505
+ position: relative; vertical-align: middle; margin-left: 4px;
506
+ user-select: none; line-height: 1; flex-shrink: 0;
507
+ }
508
+ .help-toggle:hover, .help-toggle:focus { background: var(--cld-accent); color: #fff; }
509
+ .help-bubble {
510
+ display: none; position: absolute; left: 50%; bottom: calc(100% + 6px);
511
+ transform: translateX(-50%); width: 220px; padding: 8px 10px;
512
+ background: var(--cld-text); color: var(--cld-bg); font-size: 11px;
513
+ font-weight: 400; line-height: 1.4; border-radius: var(--cld-radius-sm);
514
+ box-shadow: var(--cld-shadow-sm); z-index: 200; text-transform: none;
515
+ letter-spacing: 0; white-space: normal; pointer-events: none;
516
+ }
517
+ .help-bubble::after {
518
+ content: ""; position: absolute; top: 100%; left: 50%;
519
+ transform: translateX(-50%); border: 5px solid transparent;
520
+ border-top-color: var(--cld-text);
521
+ }
522
+ .help-toggle:hover .help-bubble,
523
+ .help-toggle:focus .help-bubble { display: block; }
524
+ .detail-grid .help-toggle .help-bubble,
525
+ .detail-section-title .help-toggle .help-bubble {
526
+ left: -4px; transform: none;
527
+ }
528
+ .detail-grid .help-toggle .help-bubble::after,
529
+ .detail-section-title .help-toggle .help-bubble::after {
530
+ left: 10px; transform: none;
531
+ }
532
+ .color-swatch {
533
+ display: inline-flex; flex-direction: column; align-items: center; gap: 2px; text-align: center;
534
+ }
535
+ .color-swatch-box {
536
+ width: 28px; height: 28px; border-radius: 4px; border: 1px solid var(--cld-border);
537
+ }
538
+ .color-swatch-label { font-size: 9px; color: var(--cld-text3); }
539
+ .color-swatch-pct { font-size: 9px; color: var(--cld-text2); }
540
+ .color-group-title {
541
+ font-size: 10px; font-weight: 600; color: var(--cld-text3);
542
+ text-transform: uppercase; margin: 6px 0 4px;
543
+ }
544
+ .color-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 4px; }
545
+
546
+ /* Checkbox field variant */
547
+ .upload-field-check {
548
+ flex-direction: row; align-items: center; gap: 6px;
549
+ }
550
+ .upload-field-check label {
551
+ display: flex; align-items: center; gap: 6px; cursor: pointer;
552
+ text-transform: none; font-weight: 500; font-size: 12px; color: var(--cld-text2);
553
+ }
554
+ .upload-field-check input[type="checkbox"] {
555
+ width: 15px; height: 15px; cursor: pointer; accent-color: var(--cld-accent);
556
+ }
557
+
558
+ /* Collapsible sections */
559
+ details.upload-section {
560
+ margin-top: 14px; border: 1px solid var(--cld-border);
561
+ border-radius: var(--cld-radius-sm); overflow: hidden;
562
+ }
563
+ details.upload-section summary {
564
+ padding: 8px 12px; font-size: 11px; font-weight: 700;
565
+ text-transform: uppercase; letter-spacing: 0.6px; color: var(--cld-text3);
566
+ cursor: pointer; list-style: none; display: flex; align-items: center; gap: 6px;
567
+ background: var(--cld-bg2); user-select: none;
568
+ }
569
+ details.upload-section summary::before {
570
+ content: "\\25B6"; font-size: 8px; transition: transform 0.15s;
571
+ }
572
+ details.upload-section[open] summary::before { transform: rotate(90deg); }
573
+ details.upload-section summary::-webkit-details-marker { display: none; }
574
+ details.upload-section > .upload-form { margin: 0; padding: 10px 12px; }
575
+
576
+ /* Staged file preview */
577
+ .upload-staged {
578
+ display: flex; align-items: center; gap: 14px; padding: 14px 16px;
579
+ background: var(--cld-accent-bg); border: 1px solid var(--cld-accent);
580
+ border-radius: var(--cld-radius); margin-bottom: 4px; position: relative;
581
+ }
582
+ .upload-staged-icon { font-size: 24px; flex-shrink: 0; }
583
+ .upload-staged-info { flex: 1; min-width: 0; }
584
+ .upload-staged-name {
585
+ font-size: 13px; font-weight: 600; color: var(--cld-text);
586
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
587
+ }
588
+ .upload-staged-meta { font-size: 11px; color: var(--cld-text3); margin-top: 2px; }
589
+ .upload-staged-clear {
590
+ background: none; border: none; cursor: pointer; font-size: 16px;
591
+ color: var(--cld-text3); padding: 4px 6px; border-radius: var(--cld-radius-sm);
592
+ transition: color 0.15s, background 0.15s; flex-shrink: 0;
593
+ }
594
+ .upload-staged-clear:hover { color: var(--cld-error); background: rgba(206,25,13,0.08); }
595
+
596
+ /* Upload submit button */
597
+ .upload-submit {
598
+ position: sticky; bottom: 0; z-index: 10;
599
+ background: var(--cld-bg); border-top: 1px solid var(--cld-border);
600
+ padding: 12px 0; margin-top: 16px; text-align: center;
601
+ }
602
+ .upload-submit-btn {
603
+ padding: 10px 32px !important; font-size: 14px !important; font-weight: 600 !important;
604
+ }
605
+
606
+ /* Combobox (folder picker) */
607
+ .combo-wrap { position: relative; }
608
+ .combo-dropdown {
609
+ display: none; position: absolute; top: 100%; left: 0; right: 0;
610
+ max-height: 180px; overflow-y: auto; z-index: 100;
611
+ background: var(--cld-bg); border: 1px solid var(--cld-border);
612
+ border-radius: var(--cld-radius-sm); box-shadow: var(--cld-shadow-sm);
613
+ margin-top: 2px;
614
+ }
615
+ .combo-item {
616
+ padding: 7px 10px; font-size: 13px; color: var(--cld-text);
617
+ cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
618
+ }
619
+ .combo-item:hover, .combo-item-active { background: var(--cld-accent-bg); color: var(--cld-accent); }
620
+
621
+ .raw-response-pre {
622
+ margin: 0; padding: 10px 12px; overflow-x: auto;
623
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
624
+ font-size: 11px; line-height: 1.45; white-space: pre-wrap; word-break: break-all;
625
+ background: var(--cld-bg2); border-radius: 6px; color: var(--cld-text2);
626
+ max-height: 500px; overflow-y: auto;
627
+ }
628
+ .json-key { color: #881391; }
629
+ .json-str { color: #0b7285; }
630
+ .json-num { color: #c92a2a; }
631
+ .json-bool { color: #5c940d; }
632
+ .json-null { color: #868e96; }
633
+ @media (prefers-color-scheme: dark) {
634
+ .json-key { color: #da77f2; }
635
+ .json-str { color: #66d9e8; }
636
+ .json-num { color: #ff8787; }
637
+ .json-bool { color: #a9e34b; }
638
+ .json-null { color: #868e96; }
639
+ }
640
+ `;
641
+ // ── JS: MCPApp client class ─────────────────────────────────────────
642
+ export const SHARED_JS_MCP_CLIENT = /* js */ `
643
+ var RPC_TIMEOUT_MS = 15000;
644
+ var TOOL_CALL_TIMEOUT_MS = 30000;
645
+ var INIT_TIMEOUT_MS = 10000;
646
+
647
+ class MCPApp {
648
+ constructor(info) {
649
+ this.info = info;
650
+ this.ontoolresult = null;
651
+ this.ontoolinput = null;
652
+ this.ontoolcancelled = null;
653
+ this.onhostcontextchanged = null;
654
+ this._id = 1;
655
+ this._pending = new Map();
656
+ this._timers = new Map();
657
+ }
658
+ connect() {
659
+ console.log(LOG_PREFIX, "connecting…");
660
+ window.addEventListener("message", (ev) => {
661
+ const m = ev.data;
662
+ if (!m || m.jsonrpc !== "2.0") return;
663
+ if (m.method) console.log(LOG_PREFIX, "recv notification:", m.method);
664
+ else if (m.id != null) console.log(LOG_PREFIX, "recv response id=" + m.id);
665
+
666
+ if (m.method === "ui/notifications/tool-result" && this.ontoolresult)
667
+ this.ontoolresult(m.params);
668
+ else if (m.method === "ui/notifications/tool-input" && this.ontoolinput)
669
+ this.ontoolinput(m.params);
670
+ else if (m.method === "ui/notifications/tool-cancelled" && this.ontoolcancelled)
671
+ this.ontoolcancelled(m.params);
672
+ else if (m.method === "ui/notifications/host-context-changed" && this.onhostcontextchanged)
673
+ this.onhostcontextchanged(m.params);
674
+ else if (m.id != null && this._pending.has(m.id)) {
675
+ clearTimeout(this._timers.get(m.id));
676
+ this._timers.delete(m.id);
677
+ var cb = this._pending.get(m.id);
678
+ this._pending.delete(m.id);
679
+ if (m.error) {
680
+ console.warn(LOG_PREFIX, "rpc error id=" + m.id, m.error);
681
+ cb.reject(m.error);
682
+ } else {
683
+ cb.resolve(m.result);
684
+ }
685
+ }
686
+ });
687
+ return this._rpc("ui/initialize", {
688
+ protocolVersion: "2026-01-26",
689
+ appInfo: { name: this.info.name, version: this.info.version },
690
+ appCapabilities: {},
691
+ }, INIT_TIMEOUT_MS).then(function() {
692
+ console.log(LOG_PREFIX, "initialized, sending initialized notification");
693
+ window.parent.postMessage({ jsonrpc: "2.0", method: "ui/notifications/initialized" }, "*");
694
+ });
695
+ }
696
+ callServerTool(params) {
697
+ console.log(LOG_PREFIX, "calling tool:", params.name);
698
+ return this._rpc("tools/call", params, TOOL_CALL_TIMEOUT_MS);
699
+ }
700
+ _rpc(method, params, timeoutMs) {
701
+ var self = this;
702
+ var id = this._id++;
703
+ var ms = timeoutMs || RPC_TIMEOUT_MS;
704
+ console.log(LOG_PREFIX, "rpc →", method, "id=" + id, "timeout=" + ms + "ms");
705
+ return new Promise(function(resolve, reject) {
706
+ self._pending.set(id, { resolve: resolve, reject: reject });
707
+ var timer = setTimeout(function() {
708
+ if (self._pending.has(id)) {
709
+ self._pending.delete(id);
710
+ self._timers.delete(id);
711
+ var err = new Error("RPC timeout after " + ms + "ms: " + method + " (id=" + id + ")");
712
+ console.error(LOG_PREFIX, err.message);
713
+ reject(err);
714
+ }
715
+ }, ms);
716
+ self._timers.set(id, timer);
717
+ window.parent.postMessage({ jsonrpc: "2.0", id: id, method: method, params: params }, "*");
718
+ });
719
+ }
720
+ reportSize(height) {
721
+ window.parent.postMessage({
722
+ jsonrpc: "2.0", method: "ui/notifications/size-changed", params: { height: height },
723
+ }, "*");
724
+ }
725
+ }
726
+ `;
727
+ // ── JS: Helper functions ────────────────────────────────────────────
728
+ export const SHARED_JS_HELPERS = /* js */ `
729
+ function fmtBytes(b) {
730
+ if (!b) return "";
731
+ var u = ["B","KB","MB","GB"], i = Math.min(Math.floor(Math.log(b)/Math.log(1024)), 3);
732
+ var v = b / Math.pow(1024, i);
733
+ return (v < 10 ? v.toFixed(1) : Math.round(v)) + " " + u[i];
734
+ }
735
+
736
+ function fmtDate(iso) {
737
+ if (!iso) return "";
738
+ return new Date(iso).toLocaleDateString("en-US", { month:"short", day:"numeric", year:"numeric" });
739
+ }
740
+
741
+ function fmtDuration(sec) {
742
+ if (sec == null || sec <= 0) return "";
743
+ var s = Math.round(sec);
744
+ var m = Math.floor(s / 60);
745
+ s = s % 60;
746
+ return m + ":" + (s < 10 ? "0" : "") + s;
747
+ }
748
+
749
+ function isAudioResource(r) {
750
+ if (!r) return false;
751
+ return r.is_audio === true || (r.resource_type === "video" && !r.width && !r.height);
752
+ }
753
+
754
+ function insertTransformation(url, tx, resource) {
755
+ var rt = (resource && resource.resource_type) || "";
756
+ var pattern = new RegExp("/(" + (rt || "image|video|raw") + ")/([^/]+)/");
757
+ var match = url.match(pattern);
758
+ if (!match) return "";
759
+ var insertAt = match.index + match[0].length;
760
+ return url.slice(0, insertAt) + tx + "/" + url.slice(insertAt);
761
+ }
762
+
763
+ function thumbUrl(url, w, h, resource) {
764
+ if (!url) return "";
765
+ w = w || 300; h = h || 225;
766
+ var rt = (resource && resource.resource_type) || "";
767
+ var fmt = (resource && resource.format || "").toLowerCase();
768
+
769
+ if (rt === "raw") return "";
770
+
771
+ if (isAudioResource(resource)) {
772
+ var base = url.replace(/\\.[^/.]+$/, ".png");
773
+ return insertTransformation(base, "c_scale,w_" + w + ",h_" + h + "/fl_waveform,b_transparent,co_rgb:3448c5", resource);
774
+ }
775
+
776
+ if (rt === "video") {
777
+ return insertTransformation(url, "c_fill,g_auto,w_" + w + ",h_" + h + ",so_auto,f_jpg,q_auto", resource);
778
+ }
779
+
780
+ if (fmt === "pdf") {
781
+ return insertTransformation(url, "c_fill,w_" + w + ",h_" + h + ",pg_1,f_auto,q_auto", resource);
782
+ }
783
+
784
+ return insertTransformation(url, "c_fill,g_auto,w_" + w + ",h_" + h + ",f_auto,q_auto", resource);
785
+ }
786
+
787
+ function mediaUrl(url, resource) {
788
+ if (!url) return "";
789
+ var rt = (resource && resource.resource_type) || "";
790
+ if (rt !== "video") return url;
791
+ return insertTransformation(url, "q_auto", resource) || url;
792
+ }
793
+
794
+ function esc(s) {
795
+ var d = document.createElement("div");
796
+ d.textContent = s;
797
+ return d.innerHTML;
798
+ }
799
+
800
+ function prettyKey(k) {
801
+ return k.replace(/[_-]/g, " ").replace(/\\b\\w/g, function(c) { return c.toUpperCase(); });
802
+ }
803
+
804
+ var FILE_TYPE_ICONS = {
805
+ pdf: '<svg viewBox="0 0 36 36" fill="none"><rect x="6" y="2" width="24" height="32" rx="3" fill="#E8384F" opacity="0.15" stroke="#E8384F" stroke-width="1.5"/><text x="18" y="22" text-anchor="middle" fill="#E8384F" font-size="9" font-weight="700">PDF</text></svg>',
806
+ zip: '<svg viewBox="0 0 36 36" fill="none"><rect x="6" y="2" width="24" height="32" rx="3" fill="#F5A623" opacity="0.15" stroke="#F5A623" stroke-width="1.5"/><text x="18" y="22" text-anchor="middle" fill="#F5A623" font-size="9" font-weight="700">ZIP</text></svg>',
807
+ doc: '<svg viewBox="0 0 36 36" fill="none"><rect x="6" y="2" width="24" height="32" rx="3" fill="#2B7CFF" opacity="0.15" stroke="#2B7CFF" stroke-width="1.5"/><text x="18" y="22" text-anchor="middle" fill="#2B7CFF" font-size="9" font-weight="700">DOC</text></svg>',
808
+ xls: '<svg viewBox="0 0 36 36" fill="none"><rect x="6" y="2" width="24" height="32" rx="3" fill="#22AA00" opacity="0.15" stroke="#22AA00" stroke-width="1.5"/><text x="18" y="22" text-anchor="middle" fill="#22AA00" font-size="9" font-weight="700">XLS</text></svg>',
809
+ csv: '<svg viewBox="0 0 36 36" fill="none"><rect x="6" y="2" width="24" height="32" rx="3" fill="#22AA00" opacity="0.15" stroke="#22AA00" stroke-width="1.5"/><text x="18" y="22" text-anchor="middle" fill="#22AA00" font-size="9" font-weight="700">CSV</text></svg>',
810
+ json: '<svg viewBox="0 0 36 36" fill="none"><rect x="6" y="2" width="24" height="32" rx="3" fill="#7c3aed" opacity="0.15" stroke="#7c3aed" stroke-width="1.5"/><text x="18" y="22" text-anchor="middle" fill="#7c3aed" font-size="8" font-weight="700">JSON</text></svg>',
811
+ _default: '<svg viewBox="0 0 36 36" fill="none"><rect x="6" y="2" width="24" height="32" rx="3" fill="#90a0b3" opacity="0.15" stroke="#90a0b3" stroke-width="1.5"/><path d="M14 14h8M14 18h8M14 22h5" stroke="#90a0b3" stroke-width="1.2" stroke-linecap="round"/></svg>',
812
+ };
813
+ FILE_TYPE_ICONS.docx = FILE_TYPE_ICONS.doc;
814
+ FILE_TYPE_ICONS.xlsx = FILE_TYPE_ICONS.xls;
815
+ FILE_TYPE_ICONS.rar = FILE_TYPE_ICONS.zip;
816
+ FILE_TYPE_ICONS["7z"] = FILE_TYPE_ICONS.zip;
817
+ FILE_TYPE_ICONS.xml = FILE_TYPE_ICONS.json;
818
+
819
+ function fileTypeIcon(format) {
820
+ var f = (format || "").toLowerCase();
821
+ return FILE_TYPE_ICONS[f] || FILE_TYPE_ICONS._default;
822
+ }
823
+
824
+ var errorToastTimer = null;
825
+ function showError(title, msg) {
826
+ console.error(LOG_PREFIX, title, msg);
827
+ dismissError();
828
+ var h = '<div class="error-toast" id="error-toast">';
829
+ h += '<span class="error-toast-icon">\\u26A0</span>';
830
+ h += '<div class="error-toast-body">';
831
+ h += '<div class="error-toast-title">' + esc(title) + "</div>";
832
+ if (msg) h += '<div class="error-toast-msg">' + esc(msg) + "</div>";
833
+ h += "</div>";
834
+ h += '<button class="error-toast-close" onclick="dismissError()">\\u2715</button>';
835
+ h += "</div>";
836
+ document.body.insertAdjacentHTML("beforeend", h);
837
+ errorToastTimer = setTimeout(dismissError, 8000);
838
+ }
839
+
840
+ function dismissError() {
841
+ clearTimeout(errorToastTimer);
842
+ var el = document.getElementById("error-toast");
843
+ if (el) el.remove();
844
+ }
845
+
846
+ function showPersistentError(title, msg) {
847
+ var root = document.getElementById("app");
848
+ var h = '<div class="prompt">';
849
+ h += '<div class="prompt-icon">\\u26A0\\uFE0F</div>';
850
+ h += '<div class="prompt-title">' + esc(title) + '</div>';
851
+ if (msg) h += '<div class="prompt-desc">' + esc(msg) + '</div>';
852
+ h += '</div>';
853
+ root.innerHTML = h;
854
+ requestAnimationFrame(function() { app.reportSize(document.documentElement.scrollHeight); });
855
+ }
856
+
857
+ function renderParamsList(args) {
858
+ if (!args) return '';
859
+ var keys = Object.keys(args);
860
+ if (keys.length === 0) return '';
861
+ var h = '<div style="display:flex;flex-wrap:wrap;gap:6px 14px;justify-content:center;margin-top:6px">';
862
+ for (var i = 0; i < keys.length; i++) {
863
+ var k = keys[i];
864
+ var v = args[k];
865
+ var display;
866
+ if (v === null || v === undefined) display = '<span style="color:var(--cld-text3)">null</span>';
867
+ else if (typeof v === "boolean") display = '<span style="color:' + (v ? '#16a34a' : 'var(--cld-text3)') + '">' + v + '</span>';
868
+ else if (typeof v === "object") display = '<span style="font-family:monospace">' + esc(JSON.stringify(v)) + '</span>';
869
+ else display = '<span style="color:var(--cld-text)">' + esc(String(v)) + '</span>';
870
+ h += '<span style="font-size:11px"><span style="color:var(--cld-text3)">' + esc(prettyKey(k)) + '</span> ' + display + '</span>';
871
+ }
872
+ h += '</div>';
873
+ return h;
874
+ }
875
+
876
+ function showReadyPrompt(pendingCall, fetchFn) {
877
+ var root = document.getElementById("app");
878
+ var h = '<div style="text-align:center;padding:24px 16px;color:var(--cld-text2)">';
879
+ h += '<div style="font-size:13px;color:var(--cld-text2);margin-bottom:4px">Waiting for result\\u2026</div>';
880
+ h += renderParamsList(pendingCall.args);
881
+ h += '<div style="margin-top:12px"><button class="prompt-btn" id="ready-fetch-btn" style="font-size:11px;padding:4px 14px">Fetch Directly</button></div>';
882
+ h += '</div>';
883
+ root.innerHTML = h;
884
+ document.getElementById("ready-fetch-btn").addEventListener("click", function() { fetchFn(); });
885
+ requestAnimationFrame(function() { app.reportSize(document.documentElement.scrollHeight); });
886
+ }
887
+
888
+ function showCancelledPrompt(pendingCall, fetchFn) {
889
+ var root = document.getElementById("app");
890
+ var name = pendingCall.name;
891
+ var h = '<div class="prompt" style="padding:32px 24px">';
892
+ h += '<div style="font-size:14px;font-weight:600;color:var(--cld-text);margin-bottom:4px">Cancelled</div>';
893
+ if (name) h += '<div style="font-size:12px;color:var(--cld-text3);margin-bottom:8px">' + esc(prettyKey(name)) + '</div>';
894
+ h += renderParamsList(pendingCall.args);
895
+ h += '<div class="prompt-actions" style="margin-top:14px">';
896
+ h += '<button class="prompt-btn prompt-btn-primary" id="cancelled-fetch-btn">Fetch Directly</button>';
897
+ h += '</div>';
898
+ h += '</div>';
899
+ root.innerHTML = h;
900
+ document.getElementById("cancelled-fetch-btn").addEventListener("click", function() { fetchFn(); });
901
+ requestAnimationFrame(function() { app.reportSize(document.documentElement.scrollHeight); });
902
+ }
903
+
904
+ function ingestResult(params) {
905
+ try {
906
+ var payload = params.result || params;
907
+ var isErr = payload.isError === true;
908
+ var content = payload.content || [];
909
+ var text = content.find(function(c) { return c.type === "text"; });
910
+ if (!text) return null;
911
+ var raw = text.text;
912
+ if (isErr) {
913
+ console.warn(LOG_PREFIX, "ingestResult: server error:", raw);
914
+ return { _error: true, _message: raw };
915
+ }
916
+ if (typeof raw === "string" && raw.charAt(0) !== "{" && raw.charAt(0) !== "[") {
917
+ console.warn(LOG_PREFIX, "ingestResult: text is not JSON, likely truncated:", raw.substring(0, 120));
918
+ return { _truncated: true, _message: raw };
919
+ }
920
+ var parsed = JSON.parse(raw);
921
+ if (parsed && parsed.error) {
922
+ var msg = parsed.error.message || JSON.stringify(parsed.error);
923
+ console.warn(LOG_PREFIX, "ingestResult: API error:", msg);
924
+ return { _error: true, _message: msg };
925
+ }
926
+ return parsed;
927
+ } catch (e) {
928
+ console.warn(LOG_PREFIX, "ingestResult parse error:", e, "raw length:", (raw || "").length);
929
+ return { _parseError: true, _message: "JSON parse failed (length " + (raw || "").length + ")" };
930
+ }
931
+ }
932
+
933
+ function renderModalError(title, detail) {
934
+ return '<div class="modal-error" style="padding:40px 20px;text-align:center;">'
935
+ + '<div style="font-size:28px;margin-bottom:8px;">\\u26A0\\uFE0F</div>'
936
+ + '<div style="font-weight:600;font-size:14px;color:var(--cld-text);margin-bottom:6px;">' + esc(title) + "</div>"
937
+ + '<div style="font-size:12px;color:var(--cld-text3);max-width:400px;margin:0 auto;">' + esc(detail) + "</div>"
938
+ + "</div>";
939
+ }
940
+ `;
941
+ // ── JS: Modal system ────────────────────────────────────────────────
942
+ export const SHARED_JS_MODAL = /* js */ `
943
+ function closeModal() {
944
+ var ov = document.querySelector(".modal-overlay");
945
+ if (ov) ov.remove();
946
+ }
947
+
948
+ function openModal(headerHtml, bodyHtml) {
949
+ closeModal();
950
+ var h = '<div class="modal-overlay"><div class="modal">';
951
+ h += headerHtml;
952
+ h += '<div class="modal-body">' + bodyHtml + "</div>";
953
+ h += "</div></div>";
954
+ document.body.insertAdjacentHTML("beforeend", h);
955
+
956
+ var overlay = document.querySelector(".modal-overlay");
957
+ overlay.addEventListener("click", function(e) {
958
+ if (e.target === overlay || e.target.classList.contains("modal-close")) {
959
+ closeModal();
960
+ return;
961
+ }
962
+ var el = e.target;
963
+ while (el && el !== overlay) {
964
+ if (el.id === "load-more-derived-btn") {
965
+ loadMoreDerived(el);
966
+ return;
967
+ }
968
+ if (el.classList && el.classList.contains("link-val") && el.dataset.url) {
969
+ app._rpc("ui/open-link", { url: el.dataset.url });
970
+ return;
971
+ }
972
+ if (el.classList && el.classList.contains("derived-open") && el.dataset.url) {
973
+ app._rpc("ui/open-link", { url: el.dataset.url });
974
+ return;
975
+ }
976
+ el = el.parentElement;
977
+ }
978
+ });
979
+ document.addEventListener("keydown", function onEsc(e) {
980
+ if (e.key === "Escape") { closeModal(); document.removeEventListener("keydown", onEsc); }
981
+ });
982
+ }
983
+
984
+ function modalHeader(name, url, sub, resource) {
985
+ var h = '<div class="modal-header">';
986
+ var thumb = thumbUrl(url, 60, 60, resource);
987
+ if (thumb) h += '<img class="modal-header-thumb" src="' + esc(thumb) + '">';
988
+ h += '<div class="modal-header-info">';
989
+ h += '<h2>' + esc(name) + "</h2>";
990
+ if (sub) h += '<div class="modal-header-sub">' + esc(sub) + "</div>";
991
+ h += "</div>";
992
+ h += '<button class="modal-close" title="Close">\\u2715</button>';
993
+ h += "</div>";
994
+ return h;
995
+ }
996
+ `;
997
+ // ── JS: Detail rendering functions ──────────────────────────────────
998
+ export const SHARED_JS_DETAIL_RENDERERS = /* js */ `
999
+ function statusBadge(text) {
1000
+ if (!text) return "";
1001
+ var t = text.toLowerCase().trim();
1002
+ var cls = "status-badge";
1003
+ if (t === "complete" || t === "approved") cls += " status-ok";
1004
+ else if (t === "pending" || t === "queued") cls += " status-warn";
1005
+ else if (t === "rejected" || t === "aborted" || t === "failed" || t === "error") cls += " status-err";
1006
+ return '<span class="' + cls + '">' + esc(text) + '</span>';
1007
+ }
1008
+
1009
+ var OPEN_SECTIONS = { tags:1, context:1, timestamps:1, moderation:1, asset_info:1 };
1010
+ function sectionStart(id) {
1011
+ return '<details class="detail-section"' + (OPEN_SECTIONS[id] ? ' open' : '') + '>';
1012
+ }
1013
+
1014
+ function renderAssetGrid(r) {
1015
+ var fields = [
1016
+ ["public_id", "Public ID", r.public_id],
1017
+ ["asset_id", "Asset ID", r.asset_id],
1018
+ ["display_name", "Display Name", r.display_name],
1019
+ ["format", "Format", (r.format || "").toUpperCase()],
1020
+ ["resource_type", "Resource Type", r.resource_type],
1021
+ ["type", "Type", r.type],
1022
+ ["", "Dimensions", (r.width && r.height) ? r.width + " \\u00d7 " + r.height : ""],
1023
+ ["duration", "Duration", r.duration ? fmtDuration(r.duration) + " (" + r.duration.toFixed(2) + "s)" : ""],
1024
+ ["bytes", "File Size", r.bytes ? fmtBytes(r.bytes) + " (" + r.bytes.toLocaleString() + " bytes)" : ""],
1025
+ ["created_at", "Created", fmtDate(r.created_at)],
1026
+ ["uploaded_at", "Uploaded", fmtDate(r.uploaded_at)],
1027
+ ["access_mode", "Access Mode", r.access_mode],
1028
+ ["asset_folder", "Asset Folder", r.asset_folder || "\\u2014"],
1029
+ ["filename", "Filename", r.filename],
1030
+ ["original_filename", "Original Filename", r.original_filename],
1031
+ ["version", "Version", r.version],
1032
+ ["version_id", "Version ID", r.version_id],
1033
+ ["status", "Status", r.status],
1034
+ ["substatus", "Substatus", r.substatus],
1035
+ ["resource_subtype", "Subtype", r.resource_subtype],
1036
+ ["backup", "Backup", r.backup != null ? String(r.backup) : ""],
1037
+ ["backup_bytes", "Backup Size", r.backup_bytes ? fmtBytes(r.backup_bytes) : ""],
1038
+ ["pages", "Pages", r.pages],
1039
+ ["pixels", "Pixels", r.pixels ? r.pixels.toLocaleString() : ""],
1040
+ ["animated", "Animated", r.animated != null ? String(r.animated) : ""],
1041
+ ["placeholder", "Placeholder", r.placeholder != null ? String(r.placeholder) : ""],
1042
+ ["etag", "ETag", r.etag],
1043
+ ["illustration_score", "Illustration Score", r.illustration_score != null ? String(r.illustration_score) : ""],
1044
+ ["semi_transparent", "Semi-Transparent", r.semi_transparent != null ? String(r.semi_transparent) : ""],
1045
+ ["grayscale", "Grayscale", r.grayscale != null ? String(r.grayscale) : ""],
1046
+ ];
1047
+ if (r.is_audio) fields.push(["", "Audio", "Yes"]);
1048
+ if (r.audio_codec) fields.push(["", "Audio Codec", r.audio_codec]);
1049
+ if (r.audio_frequency) fields.push(["", "Audio Frequency", r.audio_frequency + " Hz"]);
1050
+ if (r.channels) fields.push(["", "Channels", r.channel_layout ? r.channels + " (" + r.channel_layout + ")" : String(r.channels)]);
1051
+ if (r.bit_rate) fields.push(["", "Bit Rate", Math.round(r.bit_rate / 1000) + " kbps"]);
1052
+
1053
+ var h = '<div class="detail-grid">';
1054
+ for (var i = 0; i < fields.length; i++) {
1055
+ if (!fields[i][2] && fields[i][2] !== 0) continue;
1056
+ h += '<div class="detail-cell">';
1057
+ h += '<div class="detail-cell-key">' + esc(fields[i][1]) + tip(fields[i][0]) + "</div>";
1058
+ h += '<div class="detail-cell-val">' + esc(String(fields[i][2])) + "</div>";
1059
+ h += "</div>";
1060
+ }
1061
+ h += "</div>";
1062
+
1063
+ if (r.url || r.secure_url) {
1064
+ h += '<div style="margin-top:10px">';
1065
+ if (r.url) {
1066
+ h += '<div class="meta-row"><span class="meta-key">URL</span>';
1067
+ h += '<span class="meta-val link-val detail-cell-val" data-url="' + esc(r.url) + '">' + esc(r.url) + "</span></div>";
1068
+ }
1069
+ if (r.secure_url) {
1070
+ h += '<div class="meta-row"><span class="meta-key">Secure URL</span>';
1071
+ h += '<span class="meta-val link-val detail-cell-val" data-url="' + esc(r.secure_url) + '">' + esc(r.secure_url) + "</span></div>";
1072
+ }
1073
+ if (r.playback_url) {
1074
+ h += '<div class="meta-row"><span class="meta-key">Playback URL</span>';
1075
+ h += '<span class="meta-val link-val detail-cell-val" data-url="' + esc(r.playback_url) + '">' + esc(r.playback_url) + "</span></div>";
1076
+ }
1077
+ h += "</div>";
1078
+ }
1079
+ return h;
1080
+ }
1081
+
1082
+ function renderAudioInfo(r) {
1083
+ var a = r.audio || (r.video_metadata && r.video_metadata.audio);
1084
+ var codec = (a && a.codec) || r.audio_codec || "";
1085
+ var bitRate = (a && a.bit_rate) || r.audio_bit_rate || "";
1086
+ var freq = (a && a.frequency) || r.audio_frequency || "";
1087
+ var ch = (a && a.channels) || r.channels || "";
1088
+ var layout = (a && a.channel_layout) || r.channel_layout || "";
1089
+ if (!codec && !bitRate && !freq && !ch) return "";
1090
+ var fields = [
1091
+ ["Codec", codec],
1092
+ ["Bit Rate", bitRate ? Math.round(Number(bitRate) / 1000) + " kbps" : ""],
1093
+ ["Frequency", freq ? Number(freq).toLocaleString() + " Hz" : ""],
1094
+ ["Channels", layout ? ch + " (" + layout + ")" : ch],
1095
+ ];
1096
+ var h = sectionStart("audio_info");
1097
+ h += '<summary class="detail-section-title">Audio Info</summary>';
1098
+ h += '<div class="detail-grid">';
1099
+ for (var i = 0; i < fields.length; i++) {
1100
+ if (!fields[i][1] && fields[i][1] !== 0) continue;
1101
+ h += '<div class="detail-cell">';
1102
+ h += '<div class="detail-cell-key">' + esc(fields[i][0]) + "</div>";
1103
+ h += '<div class="detail-cell-val">' + esc(String(fields[i][1])) + "</div>";
1104
+ h += "</div>";
1105
+ }
1106
+ h += "</div></details>";
1107
+ return h;
1108
+ }
1109
+
1110
+ function renderVideoInfo(r) {
1111
+ var v = r.video || (r.video_metadata && r.video_metadata.video);
1112
+ var codec = (v && v.codec) || r.codec || "";
1113
+ var profile = (v && v.profile) || r.profile || "";
1114
+ var level = (v && v.level) || r.level || "";
1115
+ var pixFmt = (v && v.pix_format) || r.pix_format || "";
1116
+ var vbr = (v && v.bit_rate) || r.video_bit_rate || "";
1117
+ var dar = (v && v.dar) || r.dar || "";
1118
+ var tb = (v && v.time_base) || r.time_base || "";
1119
+ if (!codec && !profile && !pixFmt && !vbr) return "";
1120
+ var fields = [
1121
+ ["Codec", codec],
1122
+ ["Profile", profile],
1123
+ ["Level", level],
1124
+ ["Pixel Format", pixFmt],
1125
+ ["Bit Rate", vbr ? Math.round(Number(vbr) / 1000) + " kbps" : ""],
1126
+ ["Aspect Ratio", dar],
1127
+ ["Time Base", tb],
1128
+ ["Frame Rate", r.frame_rate ? r.frame_rate + " fps" : ""],
1129
+ ["Frames", r.nb_frames],
1130
+ ["Rotation", (r.rotation != null && r.rotation !== 0) ? r.rotation + "\\u00b0" : ""],
1131
+ ];
1132
+ var h = sectionStart("video_info");
1133
+ h += '<summary class="detail-section-title">Video Info</summary>';
1134
+ h += '<div class="detail-grid">';
1135
+ for (var i = 0; i < fields.length; i++) {
1136
+ if (!fields[i][1] && fields[i][1] !== 0) continue;
1137
+ h += '<div class="detail-cell">';
1138
+ h += '<div class="detail-cell-key">' + esc(fields[i][0]) + "</div>";
1139
+ h += '<div class="detail-cell-val">' + esc(String(fields[i][1])) + "</div>";
1140
+ h += "</div>";
1141
+ }
1142
+ h += "</div></details>";
1143
+ return h;
1144
+ }
1145
+
1146
+ function renderTags(tags) {
1147
+ if (!tags || !tags.length) return "";
1148
+ var h = sectionStart("tags");
1149
+ h += '<summary class="detail-section-title">Tags' + tip("tags") + ' <span class="count">' + tags.length + "</span></summary>";
1150
+ h += '<div class="chip-list">';
1151
+ for (var i = 0; i < tags.length; i++) {
1152
+ h += '<span class="chip chip-tag">' + esc(tags[i]) + "</span>";
1153
+ }
1154
+ h += "</div></details>";
1155
+ return h;
1156
+ }
1157
+
1158
+ function classifyMetaVal(v) {
1159
+ if (Array.isArray(v)) return "set";
1160
+ if (typeof v === "number") return "int";
1161
+ if (typeof v === "string" && /^\\d{4}-\\d{2}-\\d{2}/.test(v)) return "date";
1162
+ return "string";
1163
+ }
1164
+
1165
+ function renderMetadata(meta) {
1166
+ if (!meta) return "";
1167
+ var keys = Object.keys(meta);
1168
+ if (!keys.length) return "";
1169
+
1170
+ var groups = { string: [], int: [], date: [], set: [] };
1171
+ for (var i = 0; i < keys.length; i++) {
1172
+ var t = classifyMetaVal(meta[keys[i]]);
1173
+ groups[t].push({ key: keys[i], val: meta[keys[i]] });
1174
+ }
1175
+
1176
+ var h = sectionStart("metadata");
1177
+ h += '<summary class="detail-section-title">Structured Metadata' + tip("metadata") + ' <span class="count">' + keys.length + "</span></summary>";
1178
+
1179
+ if (groups.set.length) {
1180
+ for (var s = 0; s < groups.set.length; s++) {
1181
+ var item = groups.set[s];
1182
+ h += '<div class="meta-row" style="flex-direction:column;gap:4px">';
1183
+ h += '<span class="meta-key" style="max-width:100%">' + esc(prettyKey(item.key)) + "</span>";
1184
+ h += '<div class="chip-list">';
1185
+ for (var si = 0; si < item.val.length; si++) {
1186
+ h += '<span class="chip chip-set">' + esc(String(item.val[si])) + "</span>";
1187
+ }
1188
+ h += "</div></div>";
1189
+ }
1190
+ }
1191
+ if (groups.date.length) {
1192
+ for (var d = 0; d < groups.date.length; d++) {
1193
+ h += '<div class="meta-row">';
1194
+ h += '<span class="meta-key" title="' + esc(groups.date[d].key) + '">' + esc(prettyKey(groups.date[d].key)) + "</span>";
1195
+ h += '<span class="meta-val"><span class="chip chip-date">' + esc(groups.date[d].val) + "</span></span></div>";
1196
+ }
1197
+ }
1198
+ if (groups.int.length) {
1199
+ for (var n = 0; n < groups.int.length; n++) {
1200
+ h += '<div class="meta-row">';
1201
+ h += '<span class="meta-key" title="' + esc(groups.int[n].key) + '">' + esc(prettyKey(groups.int[n].key)) + "</span>";
1202
+ h += '<span class="meta-val"><span class="chip chip-int">' + esc(String(groups.int[n].val)) + "</span></span></div>";
1203
+ }
1204
+ }
1205
+ if (groups.string.length) {
1206
+ for (var st = 0; st < groups.string.length; st++) {
1207
+ h += '<div class="meta-row">';
1208
+ h += '<span class="meta-key" title="' + esc(groups.string[st].key) + '">' + esc(prettyKey(groups.string[st].key)) + "</span>";
1209
+ h += '<span class="meta-val">' + esc(String(groups.string[st].val)) + "</span></div>";
1210
+ }
1211
+ }
1212
+
1213
+ h += "</details>";
1214
+ return h;
1215
+ }
1216
+
1217
+ function renderDerived(derived, nextCursor, assetId) {
1218
+ if (!derived || !derived.length) return "";
1219
+ var h = sectionStart("derived");
1220
+ h += '<summary class="detail-section-title">Derived Assets' + tip("derived") + ' <span class="count" id="derived-count">' + derived.length + (nextCursor ? "+" : "") + "</span></summary>";
1221
+ h += '<div id="derived-list">';
1222
+ for (var i = 0; i < derived.length; i++) {
1223
+ var d = derived[i];
1224
+ var dUrl = d.secure_url || d.url || "";
1225
+ h += '<div class="derived-card">';
1226
+ if (dUrl) h += '<img class="derived-thumb" src="' + esc(dUrl) + '">';
1227
+ h += '<div class="derived-info">';
1228
+ h += '<div class="derived-tx">' + esc(d.transformation || "") + "</div>";
1229
+ h += '<div class="derived-meta">' + (d.format || "").toUpperCase() + " &middot; " + fmtBytes(d.bytes) + "</div>";
1230
+ h += "</div>";
1231
+ if (dUrl) h += '<span class="derived-open" data-url="' + esc(dUrl) + '">Open</span>';
1232
+ h += "</div>";
1233
+ }
1234
+ h += "</div>";
1235
+ if (nextCursor && assetId) {
1236
+ h += '<div style="text-align:center;padding:8px 0;">';
1237
+ h += '<button class="prompt-btn" id="load-more-derived-btn" data-cursor="' + esc(nextCursor) + '" data-asset-id="' + esc(assetId) + '">Load More Derived</button>';
1238
+ h += "</div>";
1239
+ }
1240
+ h += "</details>";
1241
+ return h;
1242
+ }
1243
+
1244
+ function renderContext(ctx) {
1245
+ if (!ctx || typeof ctx !== "object") return "";
1246
+ var pairs = ctx.custom || ctx;
1247
+ if (typeof pairs !== "object") return "";
1248
+ var keys = Object.keys(pairs);
1249
+ if (!keys.length) return "";
1250
+ var h = sectionStart("context");
1251
+ h += '<summary class="detail-section-title">Context' + tip("context") + '</summary>';
1252
+ h += '<div class="detail-grid">';
1253
+ for (var i = 0; i < keys.length; i++) {
1254
+ var v = pairs[keys[i]];
1255
+ if (v === null || v === undefined) continue;
1256
+ var vs = typeof v === "object" ? JSON.stringify(v) : String(v);
1257
+ h += '<div class="detail-cell">';
1258
+ h += '<div class="detail-cell-key">' + esc(prettyKey(keys[i])) + "</div>";
1259
+ h += '<div class="detail-cell-val">' + esc(vs) + "</div>";
1260
+ h += "</div>";
1261
+ }
1262
+ h += "</div></details>";
1263
+ return h;
1264
+ }
1265
+
1266
+ function summarizeInfoVal(v) {
1267
+ if (typeof v !== "object" || v === null) return esc(String(v));
1268
+ if (v.status) return statusBadge(v.status);
1269
+ var parts = [];
1270
+ var keys = Object.keys(v);
1271
+ for (var i = 0; i < keys.length; i++) {
1272
+ var sv = v[keys[i]];
1273
+ if (sv && typeof sv === "object" && sv.status) {
1274
+ parts.push(esc(prettyKey(keys[i])) + ": " + statusBadge(sv.status));
1275
+ } else if (typeof sv === "string" || typeof sv === "number" || typeof sv === "boolean") {
1276
+ parts.push(esc(prettyKey(keys[i])) + ": " + esc(String(sv)));
1277
+ } else if (sv && typeof sv === "object") {
1278
+ parts.push(esc(prettyKey(keys[i])) + ": " + summarizeInfoVal(sv));
1279
+ }
1280
+ }
1281
+ return parts.length ? parts.join(", ") : esc(JSON.stringify(v));
1282
+ }
1283
+
1284
+ function renderImageMetadata(meta) {
1285
+ if (!meta || typeof meta !== "object") return "";
1286
+ var keys = Object.keys(meta);
1287
+ if (!keys.length) return "";
1288
+ var h = sectionStart("image_metadata");
1289
+ h += '<summary class="detail-section-title">Media Metadata' + tip("image_metadata") + '</summary>';
1290
+ h += '<div class="detail-grid">';
1291
+ for (var i = 0; i < keys.length; i++) {
1292
+ var v = meta[keys[i]];
1293
+ if (v === null || v === undefined) continue;
1294
+ h += '<div class="detail-cell">';
1295
+ h += '<div class="detail-cell-key">' + esc(prettyKey(keys[i])) + "</div>";
1296
+ h += '<div class="detail-cell-val">' + esc(String(v)) + "</div>";
1297
+ h += "</div>";
1298
+ }
1299
+ h += "</div></details>";
1300
+ return h;
1301
+ }
1302
+
1303
+ function renderInfo(info) {
1304
+ if (!info || typeof info !== "object") return "";
1305
+ var keys = Object.keys(info);
1306
+ if (!keys.length) return "";
1307
+ var rows = "";
1308
+ for (var i = 0; i < keys.length; i++) {
1309
+ var v = info[keys[i]];
1310
+ if (v === null || v === undefined) continue;
1311
+ var statusStr = summarizeInfoVal(v);
1312
+ if (statusStr.length > 300) statusStr = statusStr.substring(0, 297) + "...";
1313
+ rows += '<div class="detail-cell">';
1314
+ rows += '<div class="detail-cell-key">' + esc(prettyKey(keys[i])) + "</div>";
1315
+ rows += '<div class="detail-cell-val">' + statusStr + "</div>";
1316
+ rows += "</div>";
1317
+ }
1318
+ if (!rows) return "";
1319
+ var h = sectionStart("info");
1320
+ h += '<summary class="detail-section-title">Info' + tip("info") + '</summary>';
1321
+ h += '<div class="detail-grid">' + rows + "</div></details>";
1322
+ return h;
1323
+ }
1324
+
1325
+ function renderLastUpdated(lu) {
1326
+ if (!lu) return "";
1327
+ var keys = Object.keys(lu);
1328
+ if (!keys.length) return "";
1329
+ var h = sectionStart("timestamps");
1330
+ h += '<summary class="detail-section-title">Timestamps' + tip("last_updated") + '</summary>';
1331
+ h += '<div class="detail-grid">';
1332
+ for (var i = 0; i < keys.length; i++) {
1333
+ h += '<div class="detail-cell">';
1334
+ h += '<div class="detail-cell-key">' + esc(prettyKey(keys[i])) + "</div>";
1335
+ h += '<div class="detail-cell-val">' + esc(fmtDate(lu[keys[i]])) + "</div>";
1336
+ h += "</div>";
1337
+ }
1338
+ h += "</div></details>";
1339
+ return h;
1340
+ }
1341
+
1342
+ function renderHeroPreview(r) {
1343
+ var url = r.secure_url || r.url || "";
1344
+ var rt = r.resource_type || "";
1345
+ var h = "";
1346
+
1347
+ if (rt === "raw") {
1348
+ h += '<div style="text-align:center;padding:30px 20px;background:var(--cld-bg3)">';
1349
+ h += '<div class="file-icon">' + fileTypeIcon(r.format);
1350
+ h += '<div class="file-icon-label">' + esc((r.format || "FILE").toUpperCase()) + "</div></div></div>";
1351
+ return h;
1352
+ }
1353
+
1354
+ if (isAudioResource(r)) {
1355
+ var waveform = thumbUrl(url, 600, 120, r);
1356
+ h += '<div class="hero-audio-wrap">';
1357
+ if (waveform) {
1358
+ h += '<img class="hero-audio-waveform" src="' + esc(waveform) + '">';
1359
+ } else {
1360
+ h += '<div class="hero-audio-note">\\u266B</div>';
1361
+ }
1362
+ h += '<audio controls preload="metadata" src="' + esc(mediaUrl(url, r)) + '"></audio>';
1363
+ h += "</div>";
1364
+ return h;
1365
+ }
1366
+
1367
+ if (rt === "video") {
1368
+ var poster = thumbUrl(url, 600, 300, r);
1369
+ var src = mediaUrl(url, r);
1370
+ h += '<div style="position:relative">';
1371
+ h += '<video class="hero-video" controls preload="metadata"';
1372
+ if (poster) h += ' poster="' + esc(poster) + '"';
1373
+ h += '><source src="' + esc(src) + '"></video>';
1374
+ if (r.duration) {
1375
+ h += '<div class="duration-badge">' + fmtDuration(r.duration) + "</div>";
1376
+ }
1377
+ h += "</div>";
1378
+ return h;
1379
+ }
1380
+
1381
+ var thumb = thumbUrl(url, 600, 220, r);
1382
+ if (thumb) {
1383
+ h += '<img class="modal-hero" src="' + esc(thumb) + '">';
1384
+ }
1385
+ return h;
1386
+ }
1387
+
1388
+ function renderMediaModalBody(r) {
1389
+ var url = r.secure_url || r.url || "";
1390
+ var h = "";
1391
+
1392
+ if (isAudioResource(r)) {
1393
+ var waveform = thumbUrl(url, 560, 100, r);
1394
+ h += '<div class="media-modal-audio-wrap">';
1395
+ if (waveform) h += '<img src="' + esc(waveform) + '">';
1396
+ h += '<audio controls autoplay preload="metadata" src="' + esc(mediaUrl(url, r)) + '"></audio>';
1397
+ h += "</div>";
1398
+ return h;
1399
+ }
1400
+
1401
+ var poster = thumbUrl(url, 600, 340, r);
1402
+ h += '<video class="media-modal-video" controls autoplay preload="metadata"';
1403
+ if (poster) h += ' poster="' + esc(poster) + '"';
1404
+ h += '><source src="' + esc(mediaUrl(url, r)) + '"></video>';
1405
+ return h;
1406
+ }
1407
+
1408
+ function renderColors(colors, predominant) {
1409
+ var hasColors = colors && colors.length;
1410
+ var hasPred = predominant && typeof predominant === "object" && Object.keys(predominant).length;
1411
+ if (!hasColors && !hasPred) return "";
1412
+
1413
+ var h = sectionStart("colors");
1414
+ h += '<summary class="detail-section-title">Colors' + tip("colors") + '</summary>';
1415
+
1416
+ if (hasColors) {
1417
+ h += '<div class="color-row">';
1418
+ for (var i = 0; i < colors.length; i++) {
1419
+ var c = colors[i];
1420
+ var hex = c[0] || "";
1421
+ var pct = c[1] != null ? parseFloat(c[1]).toFixed(1) + "%" : "";
1422
+ h += '<div class="color-swatch">';
1423
+ h += '<div class="color-swatch-box" style="background:' + esc(hex) + '"></div>';
1424
+ h += '<div class="color-swatch-label">' + esc(hex) + '</div>';
1425
+ if (pct) h += '<div class="color-swatch-pct">' + esc(pct) + '</div>';
1426
+ h += '</div>';
1427
+ }
1428
+ h += '</div>';
1429
+ }
1430
+
1431
+ if (hasPred) {
1432
+ var pkeys = Object.keys(predominant);
1433
+ for (var p = 0; p < pkeys.length; p++) {
1434
+ var group = predominant[pkeys[p]];
1435
+ if (!group || !group.length) continue;
1436
+ h += '<div class="color-group-title">' + esc(prettyKey(pkeys[p])) + '</div>';
1437
+ h += '<div class="color-row">';
1438
+ for (var gi = 0; gi < group.length; gi++) {
1439
+ var gc = group[gi];
1440
+ var ghex = gc[0] || "";
1441
+ var gpct = gc[1] != null ? parseFloat(gc[1]).toFixed(1) + "%" : "";
1442
+ var isHex = ghex.charAt(0) === "#";
1443
+ h += '<div class="color-swatch">';
1444
+ if (isHex) h += '<div class="color-swatch-box" style="background:' + esc(ghex) + '"></div>';
1445
+ h += '<div class="color-swatch-label">' + esc(ghex) + '</div>';
1446
+ if (gpct) h += '<div class="color-swatch-pct">' + esc(gpct) + '</div>';
1447
+ h += '</div>';
1448
+ }
1449
+ h += '</div>';
1450
+ }
1451
+ }
1452
+
1453
+ h += '</details>';
1454
+ return h;
1455
+ }
1456
+
1457
+ function renderModerationSection(moderation, kind, status) {
1458
+ var hasArr = moderation && moderation.length;
1459
+ if (!hasArr && !kind && !status) return "";
1460
+
1461
+ var h = sectionStart("moderation");
1462
+ h += '<summary class="detail-section-title">Moderation' + tip("moderation") + '</summary>';
1463
+
1464
+ if (hasArr) {
1465
+ for (var i = 0; i < moderation.length; i++) {
1466
+ var m = moderation[i];
1467
+ h += '<div class="meta-row">';
1468
+ h += '<span class="meta-key">' + esc(m.kind || "unknown") + '</span>';
1469
+ h += '<span class="meta-val">' + (m.status ? statusBadge(m.status) : "");
1470
+ if (m.updated_at) h += ' \\u00b7 ' + fmtDate(m.updated_at);
1471
+ h += '</span></div>';
1472
+ }
1473
+ } else {
1474
+ h += '<div class="meta-row">';
1475
+ h += '<span class="meta-key">' + esc(kind || "unknown") + '</span>';
1476
+ h += '<span class="meta-val">' + (status ? statusBadge(status) : "") + '</span></div>';
1477
+ }
1478
+
1479
+ h += '</details>';
1480
+ return h;
1481
+ }
1482
+
1483
+ function renderAccessControl(acl) {
1484
+ if (!acl || !acl.length) return "";
1485
+
1486
+ var h = sectionStart("access_control");
1487
+ h += '<summary class="detail-section-title">Access Control' + tip("access_control") + ' <span class="count">' + acl.length + '</span></summary>';
1488
+
1489
+ for (var i = 0; i < acl.length; i++) {
1490
+ var rule = acl[i];
1491
+ h += '<div class="meta-row" style="flex-direction:column;gap:2px">';
1492
+ h += '<span class="meta-key" style="max-width:100%">' + esc(prettyKey(rule.access_type || "unknown")) + '</span>';
1493
+ var parts = [];
1494
+ if (rule.start) parts.push("Start: " + (typeof rule.start === "number" ? new Date(rule.start * 1000).toISOString() : String(rule.start)));
1495
+ if (rule.end) parts.push("End: " + (typeof rule.end === "number" ? new Date(rule.end * 1000).toISOString() : String(rule.end)));
1496
+ if (rule.key) parts.push("Key: " + rule.key);
1497
+ if (parts.length) h += '<span class="meta-val" style="text-align:left;font-size:11px;color:var(--cld-text3)">' + esc(parts.join(" \\u00b7 ")) + '</span>';
1498
+ h += '</div>';
1499
+ }
1500
+
1501
+ h += '</details>';
1502
+ return h;
1503
+ }
1504
+
1505
+ function renderVersions(versions) {
1506
+ if (!versions || !versions.length) return "";
1507
+
1508
+ var h = sectionStart("versions");
1509
+ h += '<summary class="detail-section-title">Versions <span class="count">' + versions.length + '</span></summary>';
1510
+
1511
+ for (var i = 0; i < versions.length; i++) {
1512
+ var v = versions[i];
1513
+ h += '<div class="meta-row">';
1514
+ h += '<span class="meta-key">' + esc(v.version_id || String(v.version || "#" + (i + 1))) + '</span>';
1515
+ var parts = [];
1516
+ if (v.format) parts.push(v.format.toUpperCase());
1517
+ if (v.size) parts.push(fmtBytes(v.size));
1518
+ if (v.time) parts.push(fmtDate(v.time));
1519
+ if (v.restorable != null) parts.push(v.restorable ? "Restorable" : "Not restorable");
1520
+ h += '<span class="meta-val">' + esc(parts.join(" \\u00b7 ")) + '</span>';
1521
+ h += '</div>';
1522
+ }
1523
+
1524
+ h += '</details>';
1525
+ return h;
1526
+ }
1527
+
1528
+ function renderEager(eager) {
1529
+ if (!eager || !eager.length) return "";
1530
+
1531
+ var h = sectionStart("eager");
1532
+ h += '<summary class="detail-section-title">Eager Transformations <span class="count">' + eager.length + '</span></summary>';
1533
+
1534
+ for (var i = 0; i < eager.length; i++) {
1535
+ var e = eager[i];
1536
+ var eUrl = e.secure_url || e.url || "";
1537
+ h += '<div class="derived-card">';
1538
+ if (eUrl) h += '<img class="derived-thumb" src="' + esc(eUrl) + '">';
1539
+ h += '<div class="derived-info">';
1540
+ h += '<div class="derived-tx">' + esc(e.transformation || "") + '</div>';
1541
+ var meta = [];
1542
+ if (e.format) meta.push(e.format.toUpperCase());
1543
+ if (e.width && e.height) meta.push(e.width + "\\u00d7" + e.height);
1544
+ if (e.bytes) meta.push(fmtBytes(e.bytes));
1545
+ h += '<div class="derived-meta">' + esc(meta.join(" \\u00b7 ")) + '</div>';
1546
+ h += '</div>';
1547
+ if (eUrl) h += '<span class="derived-open" data-url="' + esc(eUrl) + '">Open</span>';
1548
+ h += '</div>';
1549
+ }
1550
+
1551
+ h += '</details>';
1552
+ return h;
1553
+ }
1554
+
1555
+ function renderCoordinates(faces, coordinates) {
1556
+ var faceArr = (coordinates && coordinates.faces) || faces || [];
1557
+ var customArr = (coordinates && coordinates.custom) || [];
1558
+ if (!faceArr.length && !customArr.length) return "";
1559
+
1560
+ var h = sectionStart("coordinates");
1561
+ h += '<summary class="detail-section-title">Coordinates' + tip("coordinates") + '</summary>';
1562
+ h += '<div class="detail-grid">';
1563
+
1564
+ if (faceArr.length) {
1565
+ h += '<div class="detail-cell full-width">';
1566
+ h += '<div class="detail-cell-key">Faces' + tip("faces") + '</div>';
1567
+ h += '<div class="detail-cell-val">' + faceArr.length + ' region' + (faceArr.length !== 1 ? 's' : '');
1568
+ for (var i = 0; i < Math.min(faceArr.length, 5); i++) {
1569
+ var f = faceArr[i];
1570
+ if (f && f.length >= 4) h += ' [' + f[0] + ',' + f[1] + ',' + f[2] + ',' + f[3] + ']';
1571
+ }
1572
+ if (faceArr.length > 5) h += ' \\u2026';
1573
+ h += '</div></div>';
1574
+ }
1575
+
1576
+ if (customArr.length) {
1577
+ h += '<div class="detail-cell full-width">';
1578
+ h += '<div class="detail-cell-key">Custom Regions</div>';
1579
+ h += '<div class="detail-cell-val">' + customArr.length + ' region' + (customArr.length !== 1 ? 's' : '');
1580
+ for (var j = 0; j < Math.min(customArr.length, 5); j++) {
1581
+ var c = customArr[j];
1582
+ if (c && c.length >= 4) h += ' [' + c[0] + ',' + c[1] + ',' + c[2] + ',' + c[3] + ']';
1583
+ }
1584
+ if (customArr.length > 5) h += ' \\u2026';
1585
+ h += '</div></div>';
1586
+ }
1587
+
1588
+ h += '</div></details>';
1589
+ return h;
1590
+ }
1591
+
1592
+ function renderDerivatives(derivatives) {
1593
+ if (!derivatives || !derivatives.length) return "";
1594
+
1595
+ var h = sectionStart("derivatives");
1596
+ h += '<summary class="detail-section-title">Derivatives' + tip("derivatives") + ' <span class="count">' + derivatives.length + '</span></summary>';
1597
+
1598
+ for (var i = 0; i < derivatives.length; i++) {
1599
+ var d = derivatives[i];
1600
+ var dUrl = d.secure_url || "";
1601
+ h += '<div class="derived-card">';
1602
+ if (dUrl) h += '<img class="derived-thumb" src="' + esc(dUrl) + '">';
1603
+ h += '<div class="derived-info">';
1604
+ h += '<div class="derived-tx">' + esc(d.transformation || "") + '</div>';
1605
+ if (d.id) h += '<div class="derived-meta">ID: ' + esc(d.id) + '</div>';
1606
+ h += '</div>';
1607
+ if (dUrl) h += '<span class="derived-open" data-url="' + esc(dUrl) + '">Open</span>';
1608
+ h += '</div>';
1609
+ }
1610
+
1611
+ h += '</details>';
1612
+ return h;
1613
+ }
1614
+
1615
+ function renderQualityAnalysis(qa, score) {
1616
+ if (!qa && score == null) return "";
1617
+
1618
+ var h = sectionStart("quality_analysis");
1619
+ h += '<summary class="detail-section-title">Quality Analysis' + tip("quality_analysis") + '</summary>';
1620
+ h += '<div class="detail-grid">';
1621
+
1622
+ if (score != null) {
1623
+ h += '<div class="detail-cell">';
1624
+ h += '<div class="detail-cell-key">Overall Score</div>';
1625
+ h += '<div class="detail-cell-val">' + esc(String(score)) + '</div>';
1626
+ h += '</div>';
1627
+ }
1628
+
1629
+ if (qa && typeof qa === "object") {
1630
+ var keys = Object.keys(qa);
1631
+ for (var i = 0; i < keys.length; i++) {
1632
+ var v = qa[keys[i]];
1633
+ if (v == null) continue;
1634
+ h += '<div class="detail-cell">';
1635
+ h += '<div class="detail-cell-key">' + esc(prettyKey(keys[i])) + '</div>';
1636
+ h += '<div class="detail-cell-val">' + esc(typeof v === "number" ? v.toFixed(2) : String(v)) + '</div>';
1637
+ h += '</div>';
1638
+ }
1639
+ }
1640
+
1641
+ h += '</div></details>';
1642
+ return h;
1643
+ }
1644
+
1645
+ function renderAccessibilityAnalysis(aa) {
1646
+ if (!aa || typeof aa !== "object") return "";
1647
+
1648
+ var h = sectionStart("accessibility_analysis");
1649
+ h += '<summary class="detail-section-title">Accessibility Analysis' + tip("accessibility_analysis") + '</summary>';
1650
+ h += '<div class="detail-grid">';
1651
+
1652
+ if (aa.colorblind_accessibility_score != null) {
1653
+ h += '<div class="detail-cell">';
1654
+ h += '<div class="detail-cell-key">Colorblind Score</div>';
1655
+ h += '<div class="detail-cell-val">' + esc(String(aa.colorblind_accessibility_score)) + '</div>';
1656
+ h += '</div>';
1657
+ }
1658
+
1659
+ var cba = aa.colorblind_accessibility_analysis;
1660
+ if (cba && typeof cba === "object") {
1661
+ if (cba.distinct_edges != null) {
1662
+ h += '<div class="detail-cell">';
1663
+ h += '<div class="detail-cell-key">Distinct Edges</div>';
1664
+ h += '<div class="detail-cell-val">' + esc(String(cba.distinct_edges)) + '</div>';
1665
+ h += '</div>';
1666
+ }
1667
+ if (cba.distinct_colors != null) {
1668
+ h += '<div class="detail-cell">';
1669
+ h += '<div class="detail-cell-key">Distinct Colors</div>';
1670
+ h += '<div class="detail-cell-val">' + esc(String(cba.distinct_colors)) + '</div>';
1671
+ h += '</div>';
1672
+ }
1673
+ if (cba.most_indistinct_pair && cba.most_indistinct_pair.length) {
1674
+ h += '<div class="detail-cell full-width">';
1675
+ h += '<div class="detail-cell-key">Most Indistinct Pair</div>';
1676
+ h += '<div class="detail-cell-val" style="display:flex;gap:6px;align-items:center">';
1677
+ for (var i = 0; i < cba.most_indistinct_pair.length; i++) {
1678
+ var hex = cba.most_indistinct_pair[i];
1679
+ h += '<span style="display:inline-flex;align-items:center;gap:4px">';
1680
+ h += '<span style="width:16px;height:16px;border-radius:3px;border:1px solid var(--cld-border);background:' + esc(hex) + ';display:inline-block;vertical-align:middle"></span>';
1681
+ h += esc(hex);
1682
+ h += '</span>';
1683
+ }
1684
+ h += '</div></div>';
1685
+ }
1686
+ }
1687
+
1688
+ h += '</div></details>';
1689
+ return h;
1690
+ }
1691
+
1692
+ function renderRelatedAssets(related) {
1693
+ if (!related || !related.length) return "";
1694
+
1695
+ var h = sectionStart("related_assets");
1696
+ h += '<summary class="detail-section-title">Related Assets' + tip("related_assets") + ' <span class="count">' + related.length + '</span></summary>';
1697
+
1698
+ for (var i = 0; i < related.length; i++) {
1699
+ var ra = related[i];
1700
+ h += '<div class="meta-row">';
1701
+ h += '<span class="meta-key">' + esc(ra.asset_id || ra.public_id || "#" + (i + 1)) + '</span>';
1702
+ var parts = [];
1703
+ if (ra.format) parts.push(ra.format.toUpperCase());
1704
+ if (ra.resource_type) parts.push(ra.resource_type);
1705
+ if (ra.bytes) parts.push(fmtBytes(ra.bytes));
1706
+ h += '<span class="meta-val">' + esc(parts.join(" \\u00b7 ")) + '</span>';
1707
+ h += '</div>';
1708
+ }
1709
+
1710
+ h += '</details>';
1711
+ return h;
1712
+ }
1713
+
1714
+ var RENDERED_KEYS = {
1715
+ asset_id:1, public_id:1, version:1, version_id:1, signature:1,
1716
+ width:1, height:1, format:1, resource_type:1, created_at:1,
1717
+ tags:1, bytes:1, type:1, etag:1, placeholder:1, url:1,
1718
+ secure_url:1, asset_folder:1, display_name:1, access_mode:1,
1719
+ pages:1, duration:1, is_audio:1, audio_codec:1, audio_frequency:1,
1720
+ channels:1, channel_layout:1, bit_rate:1, backup:1, original_filename:1,
1721
+ metadata:1, info:1, derived:1, context:1, image_metadata:1, media_metadata:1,
1722
+ colors:1, predominant:1, moderation:1, moderation_kind:1, moderation_status:1,
1723
+ faces:1, coordinates:1, eager:1, animated:1, illustration_score:1,
1724
+ semi_transparent:1, grayscale:1, status:1, substatus:1, resource_subtype:1,
1725
+ backup_bytes:1, pixels:1, uploaded_at:1, filename:1, folder:1,
1726
+ api_key:1, derivatives:1, versions:1, access_control:1, related_assets:1,
1727
+ quality_analysis:1, quality_score:1, accessibility_analysis:1, phash:1,
1728
+ cinemagraph_analysis:1, responsive_breakpoints:1, last_updated:1,
1729
+ next_cursor:1, derived_next_cursor:1, usage:1,
1730
+ playback_url:1, video_metadata:1,
1731
+ frame_rate:1, rotation:1, nb_frames:1,
1732
+ audio_codec:1, audio_bit_rate:1, audio_frequency:1, channels:1, channel_layout:1,
1733
+ codec:1, profile:1, level:1, pix_format:1, video_bit_rate:1, dar:1, time_base:1
1734
+ };
1735
+
1736
+ function isEmptyObj(v) {
1737
+ if (v === null || v === undefined || v === "") return true;
1738
+ if (typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0) return true;
1739
+ if (Array.isArray(v) && v.length === 0) return true;
1740
+ return false;
1741
+ }
1742
+
1743
+ var RENDERED_LEAF_KEYS = {
1744
+ codec:1, bit_rate:1, frequency:1, channels:1, channel_layout:1,
1745
+ pix_format:1, profile:1, level:1, dar:1, time_base:1
1746
+ };
1747
+
1748
+ function flattenObj(obj, prefix, out) {
1749
+ var keys = Object.keys(obj);
1750
+ for (var i = 0; i < keys.length; i++) {
1751
+ var k = keys[i];
1752
+ var v = obj[k];
1753
+ var label = prefix ? prefix + " \\u203a " + prettyKey(k) : prettyKey(k);
1754
+ if (isEmptyObj(v)) continue;
1755
+ if (prefix && RENDERED_LEAF_KEYS[k] && typeof v !== "object") continue;
1756
+ if (Array.isArray(v)) {
1757
+ var vs = JSON.stringify(v);
1758
+ if (vs.length > 500) vs = vs.substring(0, 497) + "...";
1759
+ out.push([label, vs, k]);
1760
+ } else if (typeof v === "object") {
1761
+ flattenObj(v, label, out);
1762
+ } else {
1763
+ var s = String(v);
1764
+ if (s.length > 500) s = s.substring(0, 497) + "...";
1765
+ out.push([label, s, k]);
1766
+ }
1767
+ }
1768
+ }
1769
+
1770
+ function renderExtraFields(r) {
1771
+ var cells = [];
1772
+ var keys = Object.keys(r);
1773
+ for (var i = 0; i < keys.length; i++) {
1774
+ var k = keys[i];
1775
+ if (RENDERED_KEYS[k]) continue;
1776
+ var v = r[k];
1777
+ if (isEmptyObj(v)) continue;
1778
+ if (typeof v === "object" && !Array.isArray(v)) {
1779
+ flattenObj(v, prettyKey(k), cells);
1780
+ } else {
1781
+ var vs = typeof v === "object" ? JSON.stringify(v) : String(v);
1782
+ if (vs.length > 500) vs = vs.substring(0, 497) + "...";
1783
+ cells.push([prettyKey(k), vs, k]);
1784
+ }
1785
+ }
1786
+ if (!cells.length) return "";
1787
+ var h = "";
1788
+ for (var c = 0; c < cells.length; c++) {
1789
+ h += '<div class="detail-cell">';
1790
+ h += '<div class="detail-cell-key">' + esc(cells[c][0]) + (cells[c][2] && !cells[c][0].indexOf("\\u203a") ? tip(cells[c][2]) : "") + "</div>";
1791
+ h += '<div class="detail-cell-val" style="word-break:break-all">' + esc(cells[c][1]) + "</div>";
1792
+ h += "</div>";
1793
+ }
1794
+ var out = '<details class="upload-section" style="margin-top:12px">';
1795
+ out += "<summary>More Details</summary>";
1796
+ out += '<div class="detail-grid" style="padding:10px 12px">' + h + "</div>";
1797
+ out += "</details>";
1798
+ return out;
1799
+ }
1800
+
1801
+ function syntaxHighlight(json) {
1802
+ var s = json.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");
1803
+ return s.replace(
1804
+ /("(\\\\u[a-fA-F0-9]{4}|\\\\[^u]|[^\\\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
1805
+ function(m) {
1806
+ var cls = "json-num";
1807
+ if (/^"/.test(m)) {
1808
+ cls = /:$/.test(m) ? "json-key" : "json-str";
1809
+ } else if (/true|false/.test(m)) {
1810
+ cls = "json-bool";
1811
+ } else if (/null/.test(m)) {
1812
+ cls = "json-null";
1813
+ }
1814
+ return '<span class="' + cls + '">' + m + "</span>";
1815
+ }
1816
+ );
1817
+ }
1818
+
1819
+ function renderRawResponse(r) {
1820
+ if (!r || typeof r !== "object") return "";
1821
+ var json = JSON.stringify(r, null, 2);
1822
+ var out = '<details class="upload-section" style="margin-top:12px">';
1823
+ out += "<summary>Raw Response</summary>";
1824
+ out += '<pre class="raw-response-pre">' + syntaxHighlight(json) + "</pre>";
1825
+ out += "</details>";
1826
+ return out;
1827
+ }
1828
+
1829
+ function renderFullDetails(r) {
1830
+ var body = renderHeroPreview(r);
1831
+
1832
+ body += sectionStart("asset_info");
1833
+ body += '<summary class="detail-section-title">Asset Info</summary>';
1834
+ body += renderAssetGrid(r);
1835
+ body += "</details>";
1836
+
1837
+ body += renderAudioInfo(r);
1838
+ body += renderVideoInfo(r);
1839
+ body += renderTags(r.tags);
1840
+ body += renderContext(r.context);
1841
+ body += renderImageMetadata(r.image_metadata || r.media_metadata);
1842
+ body += renderColors(r.colors, r.predominant);
1843
+ body += renderModerationSection(r.moderation, r.moderation_kind, r.moderation_status);
1844
+ body += renderAccessControl(r.access_control);
1845
+ body += renderCoordinates(r.faces, r.coordinates);
1846
+ body += renderLastUpdated(r.last_updated);
1847
+ body += renderMetadata(r.metadata);
1848
+ body += renderInfo(r.info);
1849
+ body += renderDerived(r.derived, r.derived_next_cursor, r.asset_id);
1850
+ body += renderDerivatives(r.derivatives);
1851
+ body += renderRelatedAssets(r.related_assets);
1852
+ body += renderVersions(r.versions);
1853
+ body += renderEager(r.eager);
1854
+ body += renderQualityAnalysis(r.quality_analysis, r.quality_score);
1855
+ body += renderAccessibilityAnalysis(r.accessibility_analysis);
1856
+ body += renderExtraFields(r);
1857
+ body += renderRawResponse(r);
1858
+
1859
+ return body;
1860
+ }
1861
+
1862
+ async function loadMoreDerived(btn) {
1863
+ var cursor = btn.dataset.cursor;
1864
+ var aid = btn.dataset.assetId;
1865
+ if (!cursor || !aid) return;
1866
+ btn.textContent = "Loading\\u2026";
1867
+ btn.disabled = true;
1868
+ try {
1869
+ var res = await app.callServerTool({
1870
+ name: "get-asset-details",
1871
+ arguments: { asset_id: aid, derived_next_cursor: cursor },
1872
+ });
1873
+ var data = ingestResult(res);
1874
+ if (data && data.derived && data.derived.length) {
1875
+ var container = document.getElementById("derived-list");
1876
+ if (container) {
1877
+ var frag = "";
1878
+ for (var i = 0; i < data.derived.length; i++) {
1879
+ var d = data.derived[i];
1880
+ var dUrl = d.secure_url || d.url || "";
1881
+ frag += '<div class="derived-card">';
1882
+ if (dUrl) frag += '<img class="derived-thumb" src="' + esc(dUrl) + '">';
1883
+ frag += '<div class="derived-info">';
1884
+ frag += '<div class="derived-tx">' + esc(d.transformation || "") + '</div>';
1885
+ frag += '<div class="derived-meta">' + (d.format || "").toUpperCase() + " \\u00b7 " + fmtBytes(d.bytes) + '</div>';
1886
+ frag += '</div>';
1887
+ if (dUrl) frag += '<span class="derived-open" data-url="' + esc(dUrl) + '">Open</span>';
1888
+ frag += '</div>';
1889
+ }
1890
+ container.insertAdjacentHTML("beforeend", frag);
1891
+ requestAnimationFrame(function() {
1892
+ app.reportSize(document.documentElement.scrollHeight);
1893
+ });
1894
+ }
1895
+ var countEl = document.getElementById("derived-count");
1896
+ if (countEl) {
1897
+ var total = container.querySelectorAll(".derived-card").length;
1898
+ countEl.textContent = total + (data.derived_next_cursor ? "+" : "");
1899
+ }
1900
+ if (data.derived_next_cursor) {
1901
+ btn.dataset.cursor = data.derived_next_cursor;
1902
+ btn.textContent = "Load More Derived";
1903
+ btn.disabled = false;
1904
+ } else {
1905
+ btn.parentElement.remove();
1906
+ }
1907
+ } else {
1908
+ btn.parentElement.remove();
1909
+ }
1910
+ } catch (e) {
1911
+ showError("Load Failed", e && e.message ? e.message : String(e));
1912
+ btn.textContent = "Load More Derived";
1913
+ btn.disabled = false;
1914
+ }
1915
+ }
1916
+ `;
1917
+ // ── JS: Host context handler ────────────────────────────────────────
1918
+ export const SHARED_JS_HOST_CONTEXT = /* js */ `
1919
+ var _themeOverride = null;
1920
+ var _hostTheme = null;
1921
+
1922
+ function applyTheme() {
1923
+ var effective;
1924
+ if (_themeOverride === "light" || _themeOverride === "dark") {
1925
+ effective = _themeOverride;
1926
+ } else {
1927
+ effective = _hostTheme || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
1928
+ }
1929
+ document.documentElement.setAttribute("data-theme", effective);
1930
+ }
1931
+
1932
+ var _themeIcons = {
1933
+ light: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>',
1934
+ system: '<svg viewBox="0 0 24 24"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/></svg>',
1935
+ dark: '<svg viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>'
1936
+ };
1937
+ var _themeCycle = ["light", "system", "dark"];
1938
+ var _themeLabels = { light: "Light", system: "System", dark: "Dark" };
1939
+
1940
+ function renderThemeToggle() {
1941
+ var existing = document.getElementById("theme-btn");
1942
+ if (existing) existing.remove();
1943
+ var current = _themeOverride || "system";
1944
+ var btn = document.createElement("button");
1945
+ btn.id = "theme-btn";
1946
+ btn.className = "theme-btn";
1947
+ btn.title = _themeLabels[current];
1948
+ btn.innerHTML = _themeIcons[current];
1949
+ btn.addEventListener("click", function() {
1950
+ var cur = _themeOverride || "system";
1951
+ var idx = (_themeCycle.indexOf(cur) + 1) % _themeCycle.length;
1952
+ var next = _themeCycle[idx];
1953
+ _themeOverride = next === "system" ? null : next;
1954
+ try { localStorage.setItem("cld-theme", next); } catch(e) {}
1955
+ applyTheme();
1956
+ renderThemeToggle();
1957
+ });
1958
+ document.body.appendChild(btn);
1959
+ }
1960
+
1961
+ function setupHostContext(app) {
1962
+ try { var saved = localStorage.getItem("cld-theme");
1963
+ if (saved === "light" || saved === "dark") _themeOverride = saved;
1964
+ } catch(e) {}
1965
+
1966
+ app.onhostcontextchanged = function(ctx) {
1967
+ if (ctx.theme) {
1968
+ _hostTheme = ctx.theme;
1969
+ applyTheme();
1970
+ }
1971
+ if (ctx.styles && ctx.styles.variables) {
1972
+ var vars = ctx.styles.variables;
1973
+ for (var k in vars) document.documentElement.style.setProperty(k, vars[k]);
1974
+ }
1975
+ if (ctx.styles && ctx.styles.css && ctx.styles.css.fonts) {
1976
+ var el = document.getElementById("host-fonts");
1977
+ if (!el) { el = document.createElement("style"); el.id = "host-fonts"; document.head.appendChild(el); }
1978
+ el.textContent = ctx.styles.css.fonts;
1979
+ }
1980
+ if (ctx.safeAreaInsets) {
1981
+ var s = ctx.safeAreaInsets;
1982
+ document.body.style.padding =
1983
+ (s.top||0)+"px "+(s.right||0)+"px "+(s.bottom||0)+"px "+(s.left||0)+"px";
1984
+ }
1985
+ };
1986
+
1987
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", function() {
1988
+ if (!_themeOverride) applyTheme();
1989
+ });
1990
+
1991
+ applyTheme();
1992
+ renderThemeToggle();
1993
+ }
1994
+
1995
+ function setupResize(app, minHeight) {
1996
+ var _raf = 0;
1997
+ function report() {
1998
+ cancelAnimationFrame(_raf);
1999
+ _raf = requestAnimationFrame(function() {
2000
+ app.reportSize(Math.max(document.documentElement.scrollHeight, minHeight));
2001
+ });
2002
+ }
2003
+ var ro = new ResizeObserver(report);
2004
+ ro.observe(document.body);
2005
+ ro.observe(document.documentElement);
2006
+ report();
2007
+ }
2008
+ `;
2009
+ // ── JS: Tooltip map (generated at build time from Zod schema descriptions) ──
2010
+ export const SHARED_JS_TOOLTIPS = /* js */ `var FIELD_TIPS = ${TOOLTIP_MAP_JSON};
2011
+
2012
+ function tip(key) {
2013
+ if (!key) return "";
2014
+ var d = FIELD_TIPS[key];
2015
+ if (!d) return "";
2016
+ return ' <span class="help-toggle" tabindex="0">?<span class="help-bubble">' + esc(d) + '</span></span>';
2017
+ }
2018
+ `;
2019
+ //# sourceMappingURL=widget-shared.js.map