eubottura commited on
Commit
80e2aaa
·
verified ·
1 Parent(s): 180cd3c

Upload folder using huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +731 -19
index.html CHANGED
@@ -1,19 +1,731 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>PhotoShot Pro | Production Coordinator</title>
7
+ <!-- Phosphor Icons for modern UI iconography -->
8
+ <script src="https://unpkg.com/@phosphor-icons/web"></script>
9
+ <style>
10
+ :root {
11
+ /* Color Palette - Modern & Professional */
12
+ --primary: #2563eb;
13
+ --primary-dark: #1e40af;
14
+ --secondary: #64748b;
15
+ --bg-body: #f1f5f9;
16
+ --bg-surface: #ffffff;
17
+ --bg-panel: #ffffff;
18
+ --text-main: #0f172a;
19
+ --text-muted: #64748b;
20
+ --border: #e2e8f0;
21
+ --accent-success: #10b981;
22
+ --accent-warning: #f59e0b;
23
+ --accent-danger: #ef4444;
24
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
25
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
26
+ --radius: 12px;
27
+ --font-family: 'Inter', system-ui, -apple-system, sans-serif;
28
+ }
29
+
30
+ * {
31
+ box-sizing: border-box;
32
+ margin: 0;
33
+ padding: 0;
34
+ }
35
+
36
+ body {
37
+ font-family: var(--font-family);
38
+ background-color: var(--bg-body);
39
+ color: var(--text-main);
40
+ line-height: 1.5;
41
+ min-height: 100vh;
42
+ display: flex;
43
+ flex-direction: column;
44
+ }
45
+
46
+ /* Header */
47
+ header {
48
+ background: var(--bg-surface);
49
+ border-bottom: 1px solid var(--border);
50
+ padding: 1rem 2rem;
51
+ display: flex;
52
+ justify-content: space-between;
53
+ align-items: center;
54
+ position: sticky;
55
+ top: 0;
56
+ z-index: 100;
57
+ }
58
+
59
+ .brand {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 0.75rem;
63
+ font-weight: 700;
64
+ font-size: 1.25rem;
65
+ color: var(--primary);
66
+ }
67
+
68
+ .brand i {
69
+ font-size: 1.5rem;
70
+ }
71
+
72
+ .anycoder-link {
73
+ font-size: 0.875rem;
74
+ color: var(--text-muted);
75
+ text-decoration: none;
76
+ background: #f8fafc;
77
+ padding: 0.5rem 1rem;
78
+ border-radius: var(--radius);
79
+ border: 1px solid var(--border);
80
+ transition: all 0.2s;
81
+ }
82
+
83
+ .anycoder-link:hover {
84
+ background: var(--primary);
85
+ color: white;
86
+ border-color: var(--primary);
87
+ }
88
+
89
+ /* Main Layout */
90
+ main {
91
+ flex: 1;
92
+ display: grid;
93
+ grid-template-columns: 350px 1fr;
94
+ gap: 2rem;
95
+ padding: 2rem;
96
+ max-width: 1600px;
97
+ margin: 0 auto;
98
+ width: 100%;
99
+ }
100
+
101
+ @media (max-width: 900px) {
102
+ main {
103
+ grid-template-columns: 1fr;
104
+ }
105
+ }
106
+
107
+ /* Panels */
108
+ .panel {
109
+ background: var(--bg-panel);
110
+ border-radius: var(--radius);
111
+ box-shadow: var(--shadow-sm);
112
+ border: 1px solid var(--border);
113
+ padding: 1.5rem;
114
+ display: flex;
115
+ flex-direction: column;
116
+ gap: 1.5rem;
117
+ height: fit-content;
118
+ }
119
+
120
+ .panel-header {
121
+ display: flex;
122
+ align-items: center;
123
+ gap: 0.5rem;
124
+ font-weight: 600;
125
+ font-size: 1.1rem;
126
+ color: var(--text-main);
127
+ padding-bottom: 1rem;
128
+ border-bottom: 1px solid var(--border);
129
+ }
130
+
131
+ /* Form Elements */
132
+ .form-group {
133
+ display: flex;
134
+ flex-direction: column;
135
+ gap: 0.5rem;
136
+ }
137
+
138
+ label {
139
+ font-size: 0.875rem;
140
+ font-weight: 500;
141
+ color: var(--text-muted);
142
+ }
143
+
144
+ textarea {
145
+ width: 100%;
146
+ padding: 0.75rem;
147
+ border: 1px solid var(--border);
148
+ border-radius: var(--radius);
149
+ font-family: inherit;
150
+ resize: vertical;
151
+ min-height: 120px;
152
+ font-size: 0.95rem;
153
+ transition: border-color 0.2s;
154
+ }
155
+
156
+ textarea:focus {
157
+ outline: none;
158
+ border-color: var(--primary);
159
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
160
+ }
161
+
162
+ .btn-primary {
163
+ background: var(--primary);
164
+ color: white;
165
+ border: none;
166
+ padding: 0.75rem 1.5rem;
167
+ border-radius: var(--radius);
168
+ font-weight: 600;
169
+ cursor: pointer;
170
+ display: flex;
171
+ justify-content: center;
172
+ align-items: center;
173
+ gap: 0.5rem;
174
+ transition: background 0.2s;
175
+ }
176
+
177
+ .btn-primary:hover {
178
+ background: var(--primary-dark);
179
+ }
180
+
181
+ .btn-secondary {
182
+ background: transparent;
183
+ color: var(--text-muted);
184
+ border: 1px solid var(--border);
185
+ padding: 0.75rem 1.5rem;
186
+ border-radius: var(--radius);
187
+ font-weight: 600;
188
+ cursor: pointer;
189
+ transition: all 0.2s;
190
+ }
191
+
192
+ .btn-secondary:hover {
193
+ background: #f8fafc;
194
+ color: var(--text-main);
195
+ }
196
+
197
+ /* Results Area */
198
+ .results-header {
199
+ display: flex;
200
+ justify-content: space-between;
201
+ align-items: center;
202
+ margin-bottom: 1rem;
203
+ }
204
+
205
+ .shot-grid {
206
+ display: grid;
207
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
208
+ gap: 1.5rem;
209
+ }
210
+
211
+ /* Shot Card */
212
+ .shot-card {
213
+ background: #fff;
214
+ border: 1px solid var(--border);
215
+ border-radius: var(--radius);
216
+ padding: 1.25rem;
217
+ transition: transform 0.2s, box-shadow 0.2s;
218
+ position: relative;
219
+ overflow: hidden;
220
+ }
221
+
222
+ .shot-card:hover {
223
+ transform: translateY(-2px);
224
+ box-shadow: var(--shadow-md);
225
+ }
226
+
227
+ .shot-card::before {
228
+ content: '';
229
+ position: absolute;
230
+ top: 0;
231
+ left: 0;
232
+ width: 4px;
233
+ height: 100%;
234
+ background: var(--secondary);
235
+ }
236
+
237
+ .shot-card.featured::before {
238
+ background: var(--primary);
239
+ }
240
+
241
+ .shot-card.macro::before {
242
+ background: var(--accent-warning);
243
+ }
244
+
245
+ .shot-header {
246
+ display: flex;
247
+ justify-content: space-between;
248
+ align-items: flex-start;
249
+ margin-bottom: 1rem;
250
+ }
251
+
252
+ .shot-type {
253
+ font-weight: 700;
254
+ font-size: 1rem;
255
+ color: var(--text-main);
256
+ }
257
+
258
+ .shot-badge {
259
+ font-size: 0.75rem;
260
+ padding: 0.25rem 0.5rem;
261
+ border-radius: 99px;
262
+ font-weight: 600;
263
+ text-transform: uppercase;
264
+ letter-spacing: 0.5px;
265
+ }
266
+
267
+ .badge-global {
268
+ background: #eff6ff;
269
+ color: var(--primary);
270
+ }
271
+
272
+ .badge-single {
273
+ background: #f0fdf4;
274
+ color: var(--accent-success);
275
+ }
276
+
277
+ .shot-details {
278
+ display: flex;
279
+ flex-direction: column;
280
+ gap: 0.75rem;
281
+ }
282
+
283
+ .detail-row {
284
+ display: flex;
285
+ align-items: flex-start;
286
+ gap: 0.75rem;
287
+ font-size: 0.875rem;
288
+ }
289
+
290
+ .detail-row i {
291
+ color: var(--text-muted);
292
+ margin-top: 2px;
293
+ flex-shrink: 0;
294
+ }
295
+
296
+ .color-tag {
297
+ display: inline-block;
298
+ background: var(--bg-body);
299
+ padding: 0.2rem 0.5rem;
300
+ border-radius: 4px;
301
+ font-size: 0.8rem;
302
+ margin-right: 0.25rem;
303
+ margin-bottom: 0.25rem;
304
+ border: 1px solid var(--border);
305
+ }
306
+
307
+ .overlay-text {
308
+ background: var(--text-main);
309
+ color: white;
310
+ padding: 0.5rem;
311
+ border-radius: 6px;
312
+ font-family: monospace;
313
+ font-size: 0.8rem;
314
+ margin-top: 0.5rem;
315
+ }
316
+
317
+ .overlay-text.empty {
318
+ background: transparent;
319
+ color: var(--text-muted);
320
+ font-style: italic;
321
+ border: 1px dashed var(--border);
322
+ }
323
+
324
+ /* Empty State */
325
+ .empty-state {
326
+ text-align: center;
327
+ padding: 4rem 2rem;
328
+ color: var(--text-muted);
329
+ }
330
+
331
+ .empty-state i {
332
+ font-size: 3rem;
333
+ margin-bottom: 1rem;
334
+ opacity: 0.5;
335
+ }
336
+
337
+ /* Summary Bar */
338
+ .summary-bar {
339
+ background: var(--bg-surface);
340
+ padding: 1rem 1.5rem;
341
+ border-radius: var(--radius);
342
+ margin-bottom: 1.5rem;
343
+ border: 1px solid var(--border);
344
+ display: flex;
345
+ gap: 2rem;
346
+ flex-wrap: wrap;
347
+ }
348
+
349
+ .summary-item {
350
+ display: flex;
351
+ flex-direction: column;
352
+ }
353
+
354
+ .summary-label {
355
+ font-size: 0.75rem;
356
+ text-transform: uppercase;
357
+ color: var(--text-muted);
358
+ font-weight: 600;
359
+ }
360
+
361
+ .summary-value {
362
+ font-size: 1.1rem;
363
+ font-weight: 700;
364
+ color: var(--text-main);
365
+ }
366
+
367
+ /* Toast Notification */
368
+ .toast {
369
+ position: fixed;
370
+ bottom: 2rem;
371
+ right: 2rem;
372
+ background: var(--text-main);
373
+ color: white;
374
+ padding: 1rem 1.5rem;
375
+ border-radius: var(--radius);
376
+ box-shadow: var(--shadow-md);
377
+ transform: translateY(150%);
378
+ transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
379
+ z-index: 1000;
380
+ display: flex;
381
+ align-items: center;
382
+ gap: 0.75rem;
383
+ }
384
+
385
+ .toast.show {
386
+ transform: translateY(0);
387
+ }
388
+ </style>
389
+ </head>
390
+ <body>
391
+
392
+ <header>
393
+ <div class="brand">
394
+ <i class="ph ph-camera"></i>
395
+ PhotoShot Pro
396
+ </div>
397
+ <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">
398
+ Built with anycoder
399
+ </a>
400
+ </header>
401
+
402
+ <main>
403
+ <!-- Left Panel: Input Controls -->
404
+ <section class="panel">
405
+ <div class="panel-header">
406
+ <i class="ph ph-sliders-horizontal"></i>
407
+ Production Input
408
+ </div>
409
+
410
+ <div class="form-group">
411
+ <label for="productInput">Product Name & Details</label>
412
+ <textarea id="productInput" placeholder="e.g. Men's High-Waisted Slim Fit Chino / Navy, Beige, Black - Pay 2 Take 3 (Includes Free Belt)"></textarea>
413
+ <small style="color: var(--text-muted); font-size: 0.75rem;">Include gender, features, colors (separated by / or ,), and promotions.</small>
414
+ </div>
415
+
416
+ <div style="display: flex; gap: 1rem;">
417
+ <button id="generateBtn" class="btn-primary" style="flex: 1;">
418
+ <i class="ph ph-magic-wand"></i> Generate List
419
+ </button>
420
+ <button id="clearBtn" class="btn-secondary">
421
+ <i class="ph ph-trash"></i>
422
+ </button>
423
+ </div>
424
+
425
+ <div style="margin-top: auto; border-top: 1px solid var(--border); padding-top: 1rem;">
426
+ <label>Logic Preview</label>
427
+ <div style="font-size: 0.8rem; color: var(--text-muted); display: flex; flex-direction: column; gap: 0.5rem;">
428
+ <div style="display: flex; justify-content: space-between;">
429
+ <span>Gender Detection:</span>
430
+ <strong id="previewGender">-</strong>
431
+ </div>
432
+ <div style="display: flex; justify-content: space-between;">
433
+ <span>Colors Found:</span>
434
+ <strong id="previewColors">-</strong>
435
+ </div>
436
+ <div style="display: flex; justify-content: space-between;">
437
+ <span>Promo Offer:</span>
438
+ <strong id="previewPromo">-</strong>
439
+ </div>
440
+ </div>
441
+ </div>
442
+ </section>
443
+
444
+ <!-- Right Panel: Output Results -->
445
+ <section class="panel" style="background: transparent; border: none; box-shadow: none; padding: 0;">
446
+ <div id="resultsArea">
447
+ <!-- Empty State -->
448
+ <div class="empty-state">
449
+ <i class="ph ph-clipboard-text"></i>
450
+ <h3>Ready to Coordinate</h3>
451
+ <p>Enter a product name on the left to generate a photography shot list based on your production logic.</p>
452
+ </div>
453
+ </div>
454
+ </section>
455
+ </main>
456
+
457
+ <div id="toast" class="toast">
458
+ <i class="ph ph-check-circle" style="font-size: 1.25rem;"></i>
459
+ <span>Shot list generated successfully!</span>
460
+ </div>
461
+
462
+ <script>
463
+ // --- DOM Elements ---
464
+ const productInput = document.getElementById('productInput');
465
+ const generateBtn = document.getElementById('generateBtn');
466
+ const clearBtn = document.getElementById('clearBtn');
467
+ const resultsArea = document.getElementById('resultsArea');
468
+ const toast = document.getElementById('toast');
469
+
470
+ const previewGender = document.getElementById('previewGender');
471
+ const previewColors = document.getElementById('previewColors');
472
+ const previewPromo = document.getElementById('previewPromo');
473
+
474
+ // --- Logic Configuration ---
475
+ const SHOT_DEFINITIONS = [
476
+ { id: 'mirror', name: 'Review Mirror', type: 'global', gender: true },
477
+ { id: 'bed', name: 'Review Bed (Pile)', type: 'global', gender: false },
478
+ { id: 'main', name: 'Main Product Photo', type: 'global', gender: false, overlay: true },
479
+ { id: 'iso', name: 'Isolated Product (PNG)', type: 'global', gender: false },
480
+ { id: 'macro', name: 'Detail Shot 2 (Macro)', type: 'single', gender: false, macroException: true },
481
+ { id: 'street', name: 'Model Shot 1 (Street)', type: 'single', gender: true },
482
+ { id: 'urban', name: 'Model Shot 2 (Urban)', type: 'single', gender: true },
483
+ { id: 'hand', name: 'Detail Shot 1 (Hand)', type: 'single', gender: false }
484
+ ];
485
+
486
+ // --- Parsing Logic ---
487
+
488
+ function parseProductDetails(input) {
489
+ const text = input.trim();
490
+ if (!text) return null;
491
+
492
+ // 1. Gender Detection
493
+ let gender = 'Unspecified';
494
+ if (text.toLowerCase().includes("men's")) gender = 'Male';
495
+ else if (text.toLowerCase().includes("women's")) gender = 'Female';
496
+
497
+ // 2. Color Parsing (Split by "/" or ",")
498
+ // We look for the color section usually after the product name, but regex is safer for specific delimiters
499
+ // Removing everything inside parens first to avoid confusing "Pay X Take Y" logic with colors
500
+ const cleanTextForColors = text.replace(/\([^)]*\)/g, '');
501
+
502
+ // Split by / or comma, trim whitespace, filter empty
503
+ let colors = cleanTextForColors.split(/\/|,/).map(c => c.trim()).filter(c => c.length > 0);
504
+
505
+ // If no delimiters found but we have text, we might assume the whole thing is one color or no color specified.
506
+ // For this tool, we assume explicit delimiters are used for multi-color.
507
+ // However, if the split returns 1 item that is the whole product name, we might be parsing wrong.
508
+ // Let's try to isolate colors: usually the last part of the string before brackets.
509
+ // For simplicity based on prompt: "Extract available colors by splitting..."
510
+
511
+ // Filter out non-color words (heuristic)
512
+ const stopWords = ['pants', 'shirt', 'jacket', 'dress', 'shoes', 'fit', 'style', 'collection', 'pay', 'take', 'men\'s', 'women\'s'];
513
+ colors = colors.filter(c => !stopWords.includes(c.toLowerCase()));
514
+
515
+ if (colors.length === 0) colors = ['Default'];
516
+
517
+ // 3. Promo Detection ("Pay X Take Y")
518
+ const promoRegex = /(pay \d+ take \d+)/i;
519
+ const promoMatch = text.match(promoRegex);
520
+ const promoText = promoMatch ? promoMatch[0] : null;
521
+
522
+ // 4. Feature Adaptation (Structural Details)
523
+ // Looking for specific keywords mentioned in prompt + common fashion terms
524
+ const featureKeywords = [
525
+ 'high-waisted', 'vertical front seams', 'pleated', 'slim fit',
526
+ 'relaxed fit', 'skinny', 'straight leg', 'tapered', 'cargo',
527
+ 'pockets', 'zipper', 'buttons', 'elastic waist'
528
+ ];
529
+
530
+ // Normalize text for checking
531
+ const lowerText = text.toLowerCase();
532
+ const foundFeatures = featureKeywords.filter(feat => lowerText.includes(feat.toLowerCase()));
533
+ const featureString = foundFeatures.length > 0 ? foundFeatures.join(', ') : null;
534
+
535
+ // 5. Bonus/Gift Check (for Macro Exception)
536
+ const hasBonusOrGift = /bonus|gift|free|includes/i.test(text);
537
+
538
+ return {
539
+ raw: text,
540
+ gender,
541
+ colors,
542
+ promoText,
543
+ featureString,
544
+ hasBonusOrGift
545
+ };
546
+ }
547
+
548
+ // --- Rendering Logic ---
549
+
550
+ function renderShotList(data) {
551
+ let colorIndex = 0;
552
+ const cardsHTML = SHOT_DEFINITIONS.map((shot, index) => {
553
+ let assignedColors = [];
554
+ let overlayText = '';
555
+ let description = data.raw;
556
+ let badgeClass = shot.type === 'global' ? 'badge-global' : 'badge-single';
557
+ let badgeText = shot.type === 'global' ? 'All Colors' : 'Variant Focus';
558
+ let cardClass = '';
559
+
560
+ // Color Assignment
561
+ if (shot.type === 'global') {
562
+ assignedColors = [...data.colors];
563
+ cardClass = 'featured';
564
+ } else {
565
+ // Individual: Rotate colors
566
+ const color = data.colors[colorIndex % data.colors.length];
567
+ assignedColors = [color];
568
+ colorIndex++;
569
+ }
570
+
571
+ // Overlay Logic
572
+ if (shot.overlay) {
573
+ overlayText = data.promoText || '';
574
+ }
575
+
576
+ // Macro Exception
577
+ if (shot.macroException) {
578
+ cardClass = 'macro';
579
+ if (data.hasBonusOrGift && data.promoText) {
580
+ overlayText = ''; // Strip promo
581
+ // Logic: "list only the product name and color"
582
+ // We construct a clean description
583
+ const cleanName = data.raw.replace(data.promoText, '').replace(/\s\s+/g, ' ').trim();
584
+ description = cleanName;
585
+ }
586
+ }
587
+
588
+ // Set Exclusion for Individual (Prompt: "exclude multi-piece promotional logic... focus solely on color variant")
589
+ if (shot.type === 'single' && data.promoText) {
590
+ // Remove promo from description for individual shots
591
+ description = description.replace(data.promoText, '').replace(/\s\s+/g, ' ').trim();
592
+ }
593
+
594
+ // Append features if present
595
+ if (data.featureString) {
596
+ description += ` (${data.featureString})`;
597
+ }
598
+
599
+ // Render HTML
600
+ return `
601
+ <div class="shot-card ${cardClass}">
602
+ <div class="shot-header">
603
+ <span class="shot-type">${shot.name}</span>
604
+ <span class="shot-badge ${badgeClass}">${badgeText}</span>
605
+ </div>
606
+
607
+ <div class="shot-details">
608
+ ${shot.gender ? `
609
+ <div class="detail-row">
610
+ <i class="ph ph-gender-intersex"></i>
611
+ <span><strong>Model:</strong> ${data.gender}</span>
612
+ </div>` : ''}
613
+
614
+ <div class="detail-row">
615
+ <i class="ph ph-palette"></i>
616
+ <div>
617
+ <strong>Color${assignedColors.length > 1 ? 's' : ''}:</strong><br>
618
+ <div style="margin-top:4px;">
619
+ ${assignedColors.map(c => `<span class="color-tag">${c}</span>`).join('')}
620
+ </div>
621
+ </div>
622
+ </div>
623
+
624
+ <div class="detail-row">
625
+ <i class="ph ph-info"></i>
626
+ <span>${description}</span>
627
+ </div>
628
+
629
+ ${shot.overlay || shot.macroException ? `
630
+ <div class="detail-row">
631
+ <i class="ph ph-text-aa"></i>
632
+ <div style="width: 100%;">
633
+ <strong>Text Overlay:</strong>
634
+ <div class="overlay-text ${!overlayText ? 'empty' : ''}">
635
+ ${overlayText ? overlayText : 'None'}
636
+ </div>
637
+ </div>
638
+ </div>` : ''}
639
+ </div>
640
+ </div>
641
+ `;
642
+ }).join('');
643
+
644
+ // Summary Bar
645
+ const summaryHTML = `
646
+ <div class="summary-bar">
647
+ <div class="summary-item">
648
+ <span class="summary-label">Total Shots</span>
649
+ <span class="summary-value">${SHOT_DEFINITIONS.length}</span>
650
+ </div>
651
+ <div class="summary-item">
652
+ <span class="summary-label">Target Gender</span>
653
+ <span class="summary-value">${data.gender}</span>
654
+ </div>
655
+ <div class="summary-item">
656
+ <span class="summary-label">Colors Detected</span>
657
+ <span class="summary-value">${data.colors.length}</span>
658
+ </div>
659
+ <div class="summary-item">
660
+ <span class="summary-label">Promotion</span>
661
+ <span class="summary-value" style="color: ${data.promoText ? 'var(--accent-success)' : 'var(--text-muted)'}">
662
+ ${data.promoText ? 'Active' : 'None'}
663
+ </span>
664
+ </div>
665
+ </div>
666
+ <div class="shot-grid">
667
+ ${cardsHTML}
668
+ </div>
669
+ `;
670
+
671
+ resultsArea.innerHTML = summaryHTML;
672
+ }
673
+
674
+ // --- Event Listeners ---
675
+
676
+ function updatePreview() {
677
+ const data = parseProductDetails(productInput.value);
678
+ if (data) {
679
+ previewGender.textContent = data.gender;
680
+ previewColors.textContent = data.colors.join(', ');
681
+ previewPromo.textContent = data.promoText || 'None';
682
+ } else {
683
+ previewGender.textContent = '-';
684
+ previewColors.textContent = '-';
685
+ previewPromo.textContent = '-';
686
+ }
687
+ }
688
+
689
+ function showToast() {
690
+ toast.classList.add('show');
691
+ setTimeout(() => {
692
+ toast.classList.remove('show');
693
+ }, 3000);
694
+ }
695
+
696
+ generateBtn.addEventListener('click', () => {
697
+ const data = parseProductDetails(productInput.value);
698
+ if (data) {
699
+ renderShotList(data);
700
+ showToast();
701
+ } else {
702
+ resultsArea.innerHTML = `
703
+ <div class="empty-state">
704
+ <i class="ph ph-warning-circle" style="color: var(--accent-danger)"></i>
705
+ <h3>Invalid Input</h3>
706
+ <p>Please enter a product name to generate the shot list.</p>
707
+ </div>
708
+ `;
709
+ }
710
+ });
711
+
712
+ clearBtn.addEventListener('click', () => {
713
+ productInput.value = '';
714
+ updatePreview();
715
+ resultsArea.innerHTML = `
716
+ <div class="empty-state">
717
+ <i class="ph ph-clipboard-text"></i>
718
+ <h3>Ready to Coordinate</h3>
719
+ <p>Enter a product name on the left to generate a photography shot list based on your production logic.</p>
720
+ </div>
721
+ `;
722
+ });
723
+
724
+ productInput.addEventListener('input', updatePreview);
725
+
726
+ // --- Initialization ---
727
+ updatePreview();
728
+
729
+ </script>
730
+ </body>
731
+ </html>