samihalawa commited on
Commit
7a8a1e0
Β·
verified Β·
1 Parent(s): 4a78b3a

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1531 -19
index.html CHANGED
@@ -1,19 +1,1531 @@
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>Multi-Platform Product Scraper</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ padding: 20px;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1400px;
23
+ margin: 0 auto;
24
+ }
25
+
26
+ header {
27
+ text-align: center;
28
+ color: white;
29
+ margin-bottom: 30px;
30
+ }
31
+
32
+ h1 {
33
+ font-size: 2.5em;
34
+ margin-bottom: 10px;
35
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
36
+ }
37
+
38
+ .search-section {
39
+ background: white;
40
+ padding: 30px;
41
+ border-radius: 10px;
42
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
43
+ margin-bottom: 30px;
44
+ }
45
+
46
+ .input-group {
47
+ display: flex;
48
+ gap: 10px;
49
+ margin-bottom: 20px;
50
+ flex-wrap: wrap;
51
+ align-items: center;
52
+ }
53
+
54
+ input[type="text"],
55
+ input[type="number"],
56
+ select {
57
+ padding: 12px 15px;
58
+ border: 2px solid #e0e0e0;
59
+ border-radius: 5px;
60
+ font-size: 1em;
61
+ }
62
+
63
+ input[type="text"]:focus,
64
+ input[type="number"]:focus,
65
+ select:focus {
66
+ outline: none;
67
+ border-color: #667eea;
68
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
69
+ }
70
+
71
+ input[type="text"] {
72
+ flex: 1;
73
+ min-width: 250px;
74
+ }
75
+
76
+ input[type="number"] {
77
+ width: 120px;
78
+ }
79
+
80
+ button {
81
+ padding: 12px 30px;
82
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
83
+ color: white;
84
+ border: none;
85
+ border-radius: 5px;
86
+ font-size: 1em;
87
+ font-weight: 600;
88
+ cursor: pointer;
89
+ transition: all 0.3s;
90
+ }
91
+
92
+ button:hover:not(:disabled) {
93
+ transform: translateY(-2px);
94
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
95
+ }
96
+
97
+ button:disabled {
98
+ opacity: 0.6;
99
+ cursor: not-allowed;
100
+ }
101
+
102
+ .platform-selector {
103
+ display: flex;
104
+ gap: 15px;
105
+ margin-top: 20px;
106
+ flex-wrap: wrap;
107
+ }
108
+
109
+ .platform-checkbox {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 8px;
113
+ padding: 10px 15px;
114
+ background: #f5f5f5;
115
+ border-radius: 5px;
116
+ cursor: pointer;
117
+ }
118
+
119
+ .platform-checkbox input {
120
+ cursor: pointer;
121
+ width: 18px;
122
+ height: 18px;
123
+ accent-color: #667eea;
124
+ }
125
+
126
+ .loading {
127
+ text-align: center;
128
+ padding: 40px;
129
+ background: white;
130
+ border-radius: 10px;
131
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
132
+ display: none;
133
+ margin-top: 30px;
134
+ }
135
+
136
+ .loading.active {
137
+ display: block;
138
+ }
139
+
140
+ .spinner {
141
+ border: 4px solid #f3f3f3;
142
+ border-top: 4px solid #667eea;
143
+ border-radius: 50%;
144
+ width: 50px;
145
+ height: 50px;
146
+ animation: spin 1s linear infinite;
147
+ margin: 0 auto 20px;
148
+ }
149
+
150
+ @keyframes spin {
151
+ 0% { transform: rotate(0deg); }
152
+ 100% { transform: rotate(360deg); }
153
+ }
154
+
155
+ .alert {
156
+ padding: 15px;
157
+ border-radius: 5px;
158
+ margin-top: 20px;
159
+ display: none;
160
+ border-left: 4px solid;
161
+ }
162
+
163
+ .alert.active {
164
+ display: block;
165
+ }
166
+
167
+ .alert.error {
168
+ background: #fee;
169
+ color: #c00;
170
+ border-color: #c00;
171
+ }
172
+
173
+ .alert.success {
174
+ background: #d4edda;
175
+ color: #155724;
176
+ border-color: #c3e6cb;
177
+ }
178
+
179
+ .no-results {
180
+ text-align: center;
181
+ padding: 40px;
182
+ background: white;
183
+ border-radius: 10px;
184
+ color: #999;
185
+ font-size: 1.1em;
186
+ margin-top: 30px;
187
+ display: none;
188
+ }
189
+
190
+ .no-results.active {
191
+ display: block;
192
+ }
193
+
194
+ .stats {
195
+ background: white;
196
+ padding: 20px;
197
+ border-radius: 10px;
198
+ margin-top: 20px;
199
+ display: none;
200
+ gap: 30px;
201
+ flex-wrap: wrap;
202
+ }
203
+
204
+ .stats.active {
205
+ display: flex;
206
+ }
207
+
208
+ .stat {
209
+ flex: 1;
210
+ min-width: 150px;
211
+ }
212
+
213
+ .stat-value {
214
+ font-size: 2em;
215
+ font-weight: bold;
216
+ color: #667eea;
217
+ }
218
+
219
+ .stat-label {
220
+ color: #999;
221
+ font-size: 0.9em;
222
+ margin-top: 5px;
223
+ }
224
+
225
+ .results {
226
+ display: grid;
227
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
228
+ gap: 20px;
229
+ margin-top: 30px;
230
+ }
231
+
232
+ .result-card {
233
+ background: white;
234
+ border-radius: 10px;
235
+ overflow: hidden;
236
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
237
+ transition: all 0.3s;
238
+ }
239
+
240
+ .result-card:hover {
241
+ transform: translateY(-5px);
242
+ box-shadow: 0 12px 40px rgba(0,0,0,0.15);
243
+ }
244
+
245
+ .card-header {
246
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
247
+ color: white;
248
+ padding: 15px;
249
+ font-weight: 600;
250
+ display: flex;
251
+ justify-content: space-between;
252
+ align-items: center;
253
+ gap: 10px;
254
+ }
255
+
256
+ .card-title {
257
+ flex: 1;
258
+ overflow: hidden;
259
+ text-overflow: ellipsis;
260
+ white-space: nowrap;
261
+ font-size: 0.95em;
262
+ }
263
+
264
+ .platform-badge {
265
+ background: rgba(255,255,255,0.3);
266
+ padding: 4px 12px;
267
+ border-radius: 15px;
268
+ font-size: 0.75em;
269
+ white-space: nowrap;
270
+ }
271
+
272
+ .card-image {
273
+ width: 100%;
274
+ height: 200px;
275
+ object-fit: cover;
276
+ background: #f0f0f0;
277
+ display: block;
278
+ }
279
+
280
+ .card-content {
281
+ padding: 20px;
282
+ }
283
+
284
+ .product-info {
285
+ margin-bottom: 12px;
286
+ }
287
+
288
+ .label {
289
+ font-weight: 600;
290
+ color: #333;
291
+ margin-bottom: 5px;
292
+ font-size: 0.85em;
293
+ }
294
+
295
+ .value {
296
+ color: #666;
297
+ word-break: break-word;
298
+ font-size: 0.9em;
299
+ line-height: 1.4;
300
+ }
301
+
302
+ .price {
303
+ font-size: 1.8em;
304
+ color: #28a745;
305
+ font-weight: bold;
306
+ margin: 10px 0;
307
+ }
308
+
309
+ .link {
310
+ display: inline-block;
311
+ color: white;
312
+ text-decoration: none;
313
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
314
+ padding: 8px 15px;
315
+ border-radius: 4px;
316
+ font-weight: 600;
317
+ margin-top: 10px;
318
+ font-size: 0.9em;
319
+ }
320
+
321
+ .link:hover {
322
+ transform: translateY(-2px);
323
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
324
+ }
325
+
326
+ @media (max-width: 768px) {
327
+ .input-group {
328
+ flex-direction: column;
329
+ }
330
+
331
+ input[type="text"] {
332
+ min-width: auto;
333
+ }
334
+
335
+ input[type="number"] {
336
+ width: 100%;
337
+ }
338
+
339
+ .results {
340
+ grid-template-columns: 1fr;
341
+ }
342
+
343
+ h1 {
344
+ font-size: 1.8em;
345
+ }
346
+ }
347
+ </style>
348
+ </head>
349
+ <body>
350
+ <div class="container">
351
+ <header>
352
+ <h1>πŸ” Multi-Platform Product Scraper</h1>
353
+ <p>Search CEX, MediaMarkt, and Backmarket</p>
354
+ </header>
355
+
356
+ <div class="search-section">
357
+ <div class="input-group">
358
+ <input
359
+ type="text"
360
+ id="apiKeyInput"
361
+ placeholder="Enter your Apify API Key (apify_api_...)"
362
+ autocomplete="off"
363
+ style="flex: 1;"
364
+ >
365
+ <button onclick="saveApiKey()" style="min-width: 150px;">πŸ’Ύ Save Key</button>
366
+ </div>
367
+
368
+ <div class="input-group" style="margin-top: 15px;">
369
+ <input
370
+ type="text"
371
+ id="searchInput"
372
+ placeholder="Enter product name (e.g., iPhone 13)"
373
+ autocomplete="off"
374
+ >
375
+ <input
376
+ type="number"
377
+ id="maxItems"
378
+ value="5"
379
+ min="1"
380
+ max="50"
381
+ >
382
+ <button id="searchBtn" onclick="searchProducts()">πŸ” Search</button>
383
+ </div>
384
+
385
+ <div class="platform-selector">
386
+ <label class="platform-checkbox">
387
+ <input type="checkbox" id="cex" checked> πŸ’³ CEX
388
+ </label>
389
+ <label class="platform-checkbox">
390
+ <input type="checkbox" id="mediamarkt" checked> πŸ›’ MediaMarkt
391
+ </label>
392
+ <label class="platform-checkbox">
393
+ <input type="checkbox" id="backmarket" checked> ♻️ Backmarket
394
+ </label>
395
+ </div>
396
+ </div>
397
+
398
+ <div class="alert error" id="errorAlert">
399
+ <span id="errorMessage"></span>
400
+ </div>
401
+
402
+ <div class="alert success" id="successAlert">
403
+ <span id="successMessage"></span>
404
+ </div>
405
+
406
+ <div class="loading" id="loading">
407
+ <div class="spinner"></div>
408
+ <div id="loadingText">Searching platforms...</div>
409
+ </div>
410
+
411
+ <div id="stats" class="stats">
412
+ <div class="stat">
413
+ <div class="stat-value" id="totalResults">0</div>
414
+ <div class="stat-label">Total Results</div>
415
+ </div>
416
+ <div class="stat">
417
+ <div class="stat-value" id="platformsUsed">0</div>
418
+ <div class="stat-label">Platforms</div>
419
+ </div>
420
+ <div class="stat">
421
+ <div class="stat-value" id="searchTime">0s</div>
422
+ <div class="stat-label">Time</div>
423
+ </div>
424
+ </div>
425
+
426
+ <div class="no-results" id="noResults">
427
+ <p>πŸ“­ No products found. Try different keywords.</p>
428
+ </div>
429
+
430
+ <div class="results" id="results"></div>
431
+ </div>
432
+
433
+ <script>
434
+ // ============================================
435
+ // GLOBAL STATE
436
+ // ============================================
437
+ let API_KEY = localStorage.getItem('apifyApiKey') || '';
438
+
439
+ const ACTORS = {
440
+ cex: {
441
+ id: 'sync-network~cex-product-scraper-uk-webuy-com',
442
+ endpoint: '/run-sync-get-dataset-items'
443
+ },
444
+ mediamarkt: {
445
+ id: 'scrapegoats~MediaMarkt-Scraper',
446
+ endpoint: '/run-sync-get-dataset-items'
447
+ },
448
+ backmarket: {
449
+ id: 'scrapegoats~Backmarket-Scraper',
450
+ endpoint: '/run-sync-get-dataset-items'
451
+ }
452
+ };
453
+
454
+ const REQUEST_TIMEOUT = 300000;
455
+
456
+ // ============================================
457
+ // INITIALIZATION
458
+ // ============================================
459
+ window.addEventListener('load', function() {
460
+ if (API_KEY) {
461
+ document.getElementById('apiKeyInput').value = '●●●●●●●●' + API_KEY.slice(-4);
462
+ }
463
+ });
464
+
465
+ // ============================================
466
+ // API KEY
467
+ // ============================================
468
+ function saveApiKey() {
469
+ let keyInput = document.getElementById('apiKeyInput').value.trim();
470
+
471
+ if (!keyInput || keyInput.startsWith('●')) {
472
+ showAlert('error', '❌ Please enter a valid API key');
473
+ return;
474
+ }
475
+
476
+ if (!keyInput.startsWith('apify_api_')) {
477
+ showAlert('error', '❌ Must start with "apify_api_"');
478
+ return;
479
+ }
480
+
481
+ API_KEY = keyInput;
482
+ localStorage.setItem('apifyApiKey', API_KEY);
483
+ document.getElementById('apiKeyInput').value = '●●●●●●●●' + API_KEY.slice(-4);
484
+ showAlert('success', 'βœ… API key saved');
485
+ }
486
+
487
+ // ============================================
488
+ // ALERTS
489
+ // ============================================
490
+ function showAlert(type, message) {
491
+ const alertId = type + 'Alert';
492
+ const messageId = type + 'Message';
493
+
494
+ document.getElementById(messageId).textContent = message;
495
+ document.getElementById(alertId).classList.add('active');
496
+
497
+ setTimeout(() => {
498
+ document.getElementById(alertId).classList.remove('active');
499
+ }, 5000);
500
+ }
501
+
502
+ // ============================================
503
+ // UTILITIES
504
+ // ============================================
505
+ function escapeHtml(text) {
506
+ if (!text) return '';
507
+ const div = document.createElement('div');
508
+ div.textContent = String(text);
509
+ return div.innerHTML;
510
+ }
511
+
512
+ function setLoading(active) {
513
+ document.getElementById('loading').classList.toggle('active', active);
514
+ }
515
+
516
+ // ============================================
517
+ // PRICE PARSING - CRITICAL FIX
518
+ // ============================================
519
+ function extractPrice(item) {
520
+ // CEX specific
521
+ if (item.buy_price !== undefined) {
522
+ const price = parseFloat(String(item.buy_price).replace(/[^0-9.]/g, ''));
523
+ if (!isNaN(price)) return price;
524
+ }
525
+
526
+ // Backmarket specific
527
+ if (item.price !== undefined) {
528
+ if (typeof item.price === 'number') return item.price;
529
+ const price = parseFloat(String(item.price).replace(/[^0-9.]/g, ''));
530
+ if (!isNaN(price)) return price;
531
+ }
532
+
533
+ // MediaMarkt specific
534
+ if (item.priceValue !== undefined) {
535
+ const price = parseFloat(String(item.priceValue).replace(/[^0-9.]/g, ''));
536
+ if (!isNaN(price)) return price;
537
+ }
538
+
539
+ // Generic fallbacks
540
+ if (item.price_value !== undefined) {
541
+ const price = parseFloat(String(item.price_value).replace(/[^0-9.]/g, ''));
542
+ if (!isNaN(price)) return price;
543
+ }
544
+
545
+ if (item.listing_price !== undefined) {
546
+ const price = parseFloat(String(item.listing_price).replace(/[^0-9.]/g, ''));
547
+ if (!isNaN(price)) return price;
548
+ }
549
+
550
+ if (item.salePrice !== undefined) {
551
+ const price = parseFloat(String(item.salePrice).replace(/[^0-9.]/g, ''));
552
+ if (!isNaN(price)) return price;
553
+ }
554
+
555
+ return null;
556
+ }
557
+
558
+ function formatPrice(price) {
559
+ if (price === null || price === undefined) return 'N/A';
560
+ if (typeof price === 'number') {
561
+ return '$' + price.toFixed(2);
562
+ }
563
+ return String(price);
564
+ }
565
+
566
+ // ============================================
567
+ // MAIN SEARCH
568
+ // ============================================
569
+ async function searchProducts() {
570
+ if (!API_KEY) {
571
+ showAlert('error', '❌ Save your API key first');
572
+ return;
573
+ }
574
+
575
+ const searchInput = document.getElementById('searchInput').value.trim();
576
+ if (!searchInput) {
577
+ showAlert('error', '❌ Enter a product name');
578
+ return;
579
+ }
580
+
581
+ const platforms = [];
582
+ if (document.getElementById('cex').checked) platforms.push('cex');
583
+ if (document.getElementById('mediamarkt').checked) platforms.push('mediamarkt');
584
+ if (document.getElementById('backmarket').checked) platforms.push('backmarket');
585
+
586
+ if (platforms.length === 0) {
587
+ showAlert('error', '❌ Select at least one platform');
588
+ return;
589
+ }
590
+
591
+ document.getElementById('searchBtn').disabled = true;
592
+ document.getElementById('results').innerHTML = '';
593
+ document.getElementById('noResults').classList.remove('active');
594
+ document.getElementById('stats').classList.remove('active');
595
+ setLoading(true);
596
+
597
+ const startTime = Date.now();
598
+ let allResults = [];
599
+ let successCount = 0;
600
+
601
+ try {
602
+ const promises = platforms.map(p => searchPlatform(p, searchInput));
603
+ const results = await Promise.allSettled(promises);
604
+
605
+ results.forEach((result, idx) => {
606
+ if (result.status === 'fulfilled' && result.value && result.value.length > 0) {
607
+ allResults = allResults.concat(result.value);
608
+ successCount++;
609
+ }
610
+ });
611
+
612
+ const timeElapsed = ((Date.now() - startTime) / 1000).toFixed(2);
613
+
614
+ if (allResults.length === 0) {
615
+ document.getElementById('noResults').classList.add('active');
616
+ } else {
617
+ displayResults(allResults, successCount, timeElapsed);
618
+ showAlert('success', `βœ… Found ${allResults.length} products`);
619
+ }
620
+
621
+ } catch (error) {
622
+ showAlert('error', '❌ Error: ' + error.message);
623
+ } finally {
624
+ setLoading(false);
625
+ document.getElementById('searchBtn').disabled = false;
626
+ }
627
+ }
628
+
629
+ // ============================================
630
+ // PLATFORM SEARCH
631
+ // ============================================
632
+ async function searchPlatform(platform, query) {
633
+ const actor = ACTORS[platform];
634
+ const url = `https://api.apify.com/v2/acts/${actor.id}${actor.endpoint}?token=${API_KEY}`;
635
+
636
+ let payload = {};
637
+
638
+ if (platform === 'cex') {
639
+ payload = {
640
+ search_input: query,
641
+ max_items: parseInt(document.getElementById('maxItems').value) || 5,
642
+ include_images: true,
643
+ include_trade_values: true,
644
+ throttle: 2
645
+ };
646
+ } else if (platform === 'backmarket') {
647
+ payload = {
648
+ function: 'country_and_keywords',
649
+ searchs: [query],
650
+ country_input: 'United States',
651
+ limit_per_search: parseInt(document.getElementById('maxItems').value) || 5
652
+ };
653
+ } else if (platform === 'mediamarkt') {
654
+ payload = {
655
+ search_input: query,
656
+ max_items: parseInt(document.getElementById('maxItems').value) || 5
657
+ };
658
+ }
659
+
660
+ try {
661
+ const controller = new AbortController();
662
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
663
+
664
+ const response = await fetch(url, {
665
+ method: 'POST',
666
+ headers: { 'Content-Type': 'application/json' },
667
+ body: JSON.stringify(payload),
668
+ signal: controller.signal
669
+ });
670
+
671
+ clearTimeout(timeout);
672
+
673
+ if (!response.ok) {
674
+ throw new Error(`HTTP ${response.status}`);
675
+ }
676
+
677
+ const items = await response.json();
678
+
679
+ if (!Array.isArray(items)) {
680
+ return [];
681
+ }
682
+
683
+ return items.map(item => ({
684
+ ...item,
685
+ source: platform.charAt(0).toUpperCase() + platform.slice(1),
686
+ _platform: platform
687
+ }));
688
+
689
+ } catch (error) {
690
+ console.error(`${platform} error:`, error);
691
+ return [];
692
+ }
693
+ }
694
+
695
+ // ============================================
696
+ // DISPLAY RESULTS
697
+ // ============================================
698
+ function displayResults(results, platformCount, timeElapsed) {
699
+ const resultsDiv = document.getElementById('results');
700
+ const statsDiv = document.getElementById('stats');
701
+
702
+ statsDiv.classList.add('active');
703
+ document.getElementById('totalResults').textContent = results.length;
704
+ document.getElementById('platformsUsed').textContent = platformCount;
705
+ document.getElementById('searchTime').textContent = timeElapsed + 's';
706
+
707
+ results.forEach(item => {
708
+ const card = createCard(item);
709
+ resultsDiv.appendChild(card);
710
+ });
711
+ }
712
+
713
+ function createCard(item) {
714
+ const card = document.createElement('div');
715
+ card.className = 'result-card';
716
+
717
+ const title = item.title || item.name || item.product_title || 'No title';
718
+ const price = extractPrice(item);
719
+ const formattedPrice = formatPrice(price);
720
+ const url = item.url || item.link || item.product_url || '#';
721
+ const image = item.image_url || item.imageUrl || item.image || null;
722
+ const description = item.description || item.condition || '';
723
+
724
+ let html = `
725
+ <div class="card-header">
726
+ <div class="card-title" title="${escapeHtml(title)}">${escapeHtml(title.substring(0, 45))}</div>
727
+ <span class="platform-badge">${item.source}</span>
728
+ </div>
729
+ <div class="card-content">
730
+ `;
731
+
732
+ if (image) {
733
+ html += `<img src="${escapeHtml(image)}" class="card-image" onerror="this.style.display='none'">`;
734
+ }
735
+
736
+ html += `
737
+ <div class="product-info">
738
+ <div class="label">πŸ’° Price</div>
739
+ <div class="price">${escapeHtml(formattedPrice)}</div>
740
+ </div>
741
+ `;
742
+
743
+ if (description) {
744
+ html += `
745
+ <div class="product-info">
746
+ <div class="label">πŸ“ Details</div>
747
+ <div class="value">${escapeHtml(description.substring(0, 100))}</div>
748
+ </div>
749
+ `;
750
+ }
751
+
752
+ if (url && url !== '#') {
753
+ html += `
754
+ <div class="product-info">
755
+ <a href="${escapeHtml(url)}" target="_blank" class="link">πŸ”— View</a>
756
+ </div>
757
+ `;
758
+ }
759
+
760
+ html += '</div>';
761
+ card.innerHTML = html;
762
+ return card;
763
+ }
764
+ </script>
765
+ </body>
766
+ </html><!DOCTYPE html>
767
+ <html lang="en">
768
+ <head>
769
+ <meta charset="UTF-8">
770
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
771
+ <title>Multi-Platform Product Scraper</title>
772
+ <style>
773
+ * {
774
+ margin: 0;
775
+ padding: 0;
776
+ box-sizing: border-box;
777
+ }
778
+
779
+ body {
780
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
781
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
782
+ min-height: 100vh;
783
+ padding: 20px;
784
+ }
785
+
786
+ .container {
787
+ max-width: 1400px;
788
+ margin: 0 auto;
789
+ }
790
+
791
+ header {
792
+ text-align: center;
793
+ color: white;
794
+ margin-bottom: 30px;
795
+ }
796
+
797
+ h1 {
798
+ font-size: 2.5em;
799
+ margin-bottom: 10px;
800
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
801
+ }
802
+
803
+ .search-section {
804
+ background: white;
805
+ padding: 30px;
806
+ border-radius: 10px;
807
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
808
+ margin-bottom: 30px;
809
+ }
810
+
811
+ .input-group {
812
+ display: flex;
813
+ gap: 10px;
814
+ margin-bottom: 20px;
815
+ flex-wrap: wrap;
816
+ align-items: center;
817
+ }
818
+
819
+ input[type="text"],
820
+ input[type="number"],
821
+ select {
822
+ padding: 12px 15px;
823
+ border: 2px solid #e0e0e0;
824
+ border-radius: 5px;
825
+ font-size: 1em;
826
+ }
827
+
828
+ input[type="text"]:focus,
829
+ input[type="number"]:focus,
830
+ select:focus {
831
+ outline: none;
832
+ border-color: #667eea;
833
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
834
+ }
835
+
836
+ input[type="text"] {
837
+ flex: 1;
838
+ min-width: 250px;
839
+ }
840
+
841
+ input[type="number"] {
842
+ width: 120px;
843
+ }
844
+
845
+ button {
846
+ padding: 12px 30px;
847
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
848
+ color: white;
849
+ border: none;
850
+ border-radius: 5px;
851
+ font-size: 1em;
852
+ font-weight: 600;
853
+ cursor: pointer;
854
+ transition: all 0.3s;
855
+ }
856
+
857
+ button:hover:not(:disabled) {
858
+ transform: translateY(-2px);
859
+ box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
860
+ }
861
+
862
+ button:disabled {
863
+ opacity: 0.6;
864
+ cursor: not-allowed;
865
+ }
866
+
867
+ .platform-selector {
868
+ display: flex;
869
+ gap: 15px;
870
+ margin-top: 20px;
871
+ flex-wrap: wrap;
872
+ }
873
+
874
+ .platform-checkbox {
875
+ display: flex;
876
+ align-items: center;
877
+ gap: 8px;
878
+ padding: 10px 15px;
879
+ background: #f5f5f5;
880
+ border-radius: 5px;
881
+ cursor: pointer;
882
+ }
883
+
884
+ .platform-checkbox input {
885
+ cursor: pointer;
886
+ width: 18px;
887
+ height: 18px;
888
+ accent-color: #667eea;
889
+ }
890
+
891
+ .loading {
892
+ text-align: center;
893
+ padding: 40px;
894
+ background: white;
895
+ border-radius: 10px;
896
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
897
+ display: none;
898
+ margin-top: 30px;
899
+ }
900
+
901
+ .loading.active {
902
+ display: block;
903
+ }
904
+
905
+ .spinner {
906
+ border: 4px solid #f3f3f3;
907
+ border-top: 4px solid #667eea;
908
+ border-radius: 50%;
909
+ width: 50px;
910
+ height: 50px;
911
+ animation: spin 1s linear infinite;
912
+ margin: 0 auto 20px;
913
+ }
914
+
915
+ @keyframes spin {
916
+ 0% { transform: rotate(0deg); }
917
+ 100% { transform: rotate(360deg); }
918
+ }
919
+
920
+ .alert {
921
+ padding: 15px;
922
+ border-radius: 5px;
923
+ margin-top: 20px;
924
+ display: none;
925
+ border-left: 4px solid;
926
+ }
927
+
928
+ .alert.active {
929
+ display: block;
930
+ }
931
+
932
+ .alert.error {
933
+ background: #fee;
934
+ color: #c00;
935
+ border-color: #c00;
936
+ }
937
+
938
+ .alert.success {
939
+ background: #d4edda;
940
+ color: #155724;
941
+ border-color: #c3e6cb;
942
+ }
943
+
944
+ .no-results {
945
+ text-align: center;
946
+ padding: 40px;
947
+ background: white;
948
+ border-radius: 10px;
949
+ color: #999;
950
+ font-size: 1.1em;
951
+ margin-top: 30px;
952
+ display: none;
953
+ }
954
+
955
+ .no-results.active {
956
+ display: block;
957
+ }
958
+
959
+ .stats {
960
+ background: white;
961
+ padding: 20px;
962
+ border-radius: 10px;
963
+ margin-top: 20px;
964
+ display: none;
965
+ gap: 30px;
966
+ flex-wrap: wrap;
967
+ }
968
+
969
+ .stats.active {
970
+ display: flex;
971
+ }
972
+
973
+ .stat {
974
+ flex: 1;
975
+ min-width: 150px;
976
+ }
977
+
978
+ .stat-value {
979
+ font-size: 2em;
980
+ font-weight: bold;
981
+ color: #667eea;
982
+ }
983
+
984
+ .stat-label {
985
+ color: #999;
986
+ font-size: 0.9em;
987
+ margin-top: 5px;
988
+ }
989
+
990
+ .results {
991
+ display: grid;
992
+ grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
993
+ gap: 20px;
994
+ margin-top: 30px;
995
+ }
996
+
997
+ .result-card {
998
+ background: white;
999
+ border-radius: 10px;
1000
+ overflow: hidden;
1001
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
1002
+ transition: all 0.3s;
1003
+ }
1004
+
1005
+ .result-card:hover {
1006
+ transform: translateY(-5px);
1007
+ box-shadow: 0 12px 40px rgba(0,0,0,0.15);
1008
+ }
1009
+
1010
+ .card-header {
1011
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1012
+ color: white;
1013
+ padding: 15px;
1014
+ font-weight: 600;
1015
+ display: flex;
1016
+ justify-content: space-between;
1017
+ align-items: center;
1018
+ gap: 10px;
1019
+ }
1020
+
1021
+ .card-title {
1022
+ flex: 1;
1023
+ overflow: hidden;
1024
+ text-overflow: ellipsis;
1025
+ white-space: nowrap;
1026
+ font-size: 0.95em;
1027
+ }
1028
+
1029
+ .platform-badge {
1030
+ background: rgba(255,255,255,0.3);
1031
+ padding: 4px 12px;
1032
+ border-radius: 15px;
1033
+ font-size: 0.75em;
1034
+ white-space: nowrap;
1035
+ }
1036
+
1037
+ .card-image {
1038
+ width: 100%;
1039
+ height: 200px;
1040
+ object-fit: cover;
1041
+ background: #f0f0f0;
1042
+ display: block;
1043
+ }
1044
+
1045
+ .card-content {
1046
+ padding: 20px;
1047
+ }
1048
+
1049
+ .product-info {
1050
+ margin-bottom: 12px;
1051
+ }
1052
+
1053
+ .label {
1054
+ font-weight: 600;
1055
+ color: #333;
1056
+ margin-bottom: 5px;
1057
+ font-size: 0.85em;
1058
+ }
1059
+
1060
+ .value {
1061
+ color: #666;
1062
+ word-break: break-word;
1063
+ font-size: 0.9em;
1064
+ line-height: 1.4;
1065
+ }
1066
+
1067
+ .price {
1068
+ font-size: 1.8em;
1069
+ color: #28a745;
1070
+ font-weight: bold;
1071
+ margin: 10px 0;
1072
+ }
1073
+
1074
+ .link {
1075
+ display: inline-block;
1076
+ color: white;
1077
+ text-decoration: none;
1078
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
1079
+ padding: 8px 15px;
1080
+ border-radius: 4px;
1081
+ font-weight: 600;
1082
+ margin-top: 10px;
1083
+ font-size: 0.9em;
1084
+ }
1085
+
1086
+ .link:hover {
1087
+ transform: translateY(-2px);
1088
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
1089
+ }
1090
+
1091
+ @media (max-width: 768px) {
1092
+ .input-group {
1093
+ flex-direction: column;
1094
+ }
1095
+
1096
+ input[type="text"] {
1097
+ min-width: auto;
1098
+ }
1099
+
1100
+ input[type="number"] {
1101
+ width: 100%;
1102
+ }
1103
+
1104
+ .results {
1105
+ grid-template-columns: 1fr;
1106
+ }
1107
+
1108
+ h1 {
1109
+ font-size: 1.8em;
1110
+ }
1111
+ }
1112
+ </style>
1113
+ </head>
1114
+ <body>
1115
+ <div class="container">
1116
+ <header>
1117
+ <h1>πŸ” Multi-Platform Product Scraper</h1>
1118
+ <p>Search CEX, MediaMarkt, and Backmarket</p>
1119
+ </header>
1120
+
1121
+ <div class="search-section">
1122
+ <div class="input-group">
1123
+ <input
1124
+ type="text"
1125
+ id="apiKeyInput"
1126
+ placeholder="Enter your Apify API Key (apify_api_...)"
1127
+ autocomplete="off"
1128
+ style="flex: 1;"
1129
+ >
1130
+ <button onclick="saveApiKey()" style="min-width: 150px;">πŸ’Ύ Save Key</button>
1131
+ </div>
1132
+
1133
+ <div class="input-group" style="margin-top: 15px;">
1134
+ <input
1135
+ type="text"
1136
+ id="searchInput"
1137
+ placeholder="Enter product name (e.g., iPhone 13)"
1138
+ autocomplete="off"
1139
+ >
1140
+ <input
1141
+ type="number"
1142
+ id="maxItems"
1143
+ value="5"
1144
+ min="1"
1145
+ max="50"
1146
+ >
1147
+ <button id="searchBtn" onclick="searchProducts()">πŸ” Search</button>
1148
+ </div>
1149
+
1150
+ <div class="platform-selector">
1151
+ <label class="platform-checkbox">
1152
+ <input type="checkbox" id="cex" checked> πŸ’³ CEX
1153
+ </label>
1154
+ <label class="platform-checkbox">
1155
+ <input type="checkbox" id="mediamarkt" checked> πŸ›’ MediaMarkt
1156
+ </label>
1157
+ <label class="platform-checkbox">
1158
+ <input type="checkbox" id="backmarket" checked> ♻️ Backmarket
1159
+ </label>
1160
+ </div>
1161
+ </div>
1162
+
1163
+ <div class="alert error" id="errorAlert">
1164
+ <span id="errorMessage"></span>
1165
+ </div>
1166
+
1167
+ <div class="alert success" id="successAlert">
1168
+ <span id="successMessage"></span>
1169
+ </div>
1170
+
1171
+ <div class="loading" id="loading">
1172
+ <div class="spinner"></div>
1173
+ <div id="loadingText">Searching platforms...</div>
1174
+ </div>
1175
+
1176
+ <div id="stats" class="stats">
1177
+ <div class="stat">
1178
+ <div class="stat-value" id="totalResults">0</div>
1179
+ <div class="stat-label">Total Results</div>
1180
+ </div>
1181
+ <div class="stat">
1182
+ <div class="stat-value" id="platformsUsed">0</div>
1183
+ <div class="stat-label">Platforms</div>
1184
+ </div>
1185
+ <div class="stat">
1186
+ <div class="stat-value" id="searchTime">0s</div>
1187
+ <div class="stat-label">Time</div>
1188
+ </div>
1189
+ </div>
1190
+
1191
+ <div class="no-results" id="noResults">
1192
+ <p>πŸ“­ No products found. Try different keywords.</p>
1193
+ </div>
1194
+
1195
+ <div class="results" id="results"></div>
1196
+ </div>
1197
+
1198
+ <script>
1199
+ // ============================================
1200
+ // GLOBAL STATE
1201
+ // ============================================
1202
+ let API_KEY = localStorage.getItem('apifyApiKey') || '';
1203
+
1204
+ const ACTORS = {
1205
+ cex: {
1206
+ id: 'sync-network~cex-product-scraper-uk-webuy-com',
1207
+ endpoint: '/run-sync-get-dataset-items'
1208
+ },
1209
+ mediamarkt: {
1210
+ id: 'scrapegoats~MediaMarkt-Scraper',
1211
+ endpoint: '/run-sync-get-dataset-items'
1212
+ },
1213
+ backmarket: {
1214
+ id: 'scrapegoats~Backmarket-Scraper',
1215
+ endpoint: '/run-sync-get-dataset-items'
1216
+ }
1217
+ };
1218
+
1219
+ const REQUEST_TIMEOUT = 300000;
1220
+
1221
+ // ============================================
1222
+ // INITIALIZATION
1223
+ // ============================================
1224
+ window.addEventListener('load', function() {
1225
+ if (API_KEY) {
1226
+ document.getElementById('apiKeyInput').value = '●●●●●●●●' + API_KEY.slice(-4);
1227
+ }
1228
+ });
1229
+
1230
+ // ============================================
1231
+ // API KEY
1232
+ // ============================================
1233
+ function saveApiKey() {
1234
+ let keyInput = document.getElementById('apiKeyInput').value.trim();
1235
+
1236
+ if (!keyInput || keyInput.startsWith('●')) {
1237
+ showAlert('error', '❌ Please enter a valid API key');
1238
+ return;
1239
+ }
1240
+
1241
+ if (!keyInput.startsWith('apify_api_')) {
1242
+ showAlert('error', '❌ Must start with "apify_api_"');
1243
+ return;
1244
+ }
1245
+
1246
+ API_KEY = keyInput;
1247
+ localStorage.setItem('apifyApiKey', API_KEY);
1248
+ document.getElementById('apiKeyInput').value = '●●●●●●●●' + API_KEY.slice(-4);
1249
+ showAlert('success', 'βœ… API key saved');
1250
+ }
1251
+
1252
+ // ============================================
1253
+ // ALERTS
1254
+ // ============================================
1255
+ function showAlert(type, message) {
1256
+ const alertId = type + 'Alert';
1257
+ const messageId = type + 'Message';
1258
+
1259
+ document.getElementById(messageId).textContent = message;
1260
+ document.getElementById(alertId).classList.add('active');
1261
+
1262
+ setTimeout(() => {
1263
+ document.getElementById(alertId).classList.remove('active');
1264
+ }, 5000);
1265
+ }
1266
+
1267
+ // ============================================
1268
+ // UTILITIES
1269
+ // ============================================
1270
+ function escapeHtml(text) {
1271
+ if (!text) return '';
1272
+ const div = document.createElement('div');
1273
+ div.textContent = String(text);
1274
+ return div.innerHTML;
1275
+ }
1276
+
1277
+ function setLoading(active) {
1278
+ document.getElementById('loading').classList.toggle('active', active);
1279
+ }
1280
+
1281
+ // ============================================
1282
+ // PRICE PARSING - CRITICAL FIX
1283
+ // ============================================
1284
+ function extractPrice(item) {
1285
+ // CEX specific
1286
+ if (item.buy_price !== undefined) {
1287
+ const price = parseFloat(String(item.buy_price).replace(/[^0-9.]/g, ''));
1288
+ if (!isNaN(price)) return price;
1289
+ }
1290
+
1291
+ // Backmarket specific
1292
+ if (item.price !== undefined) {
1293
+ if (typeof item.price === 'number') return item.price;
1294
+ const price = parseFloat(String(item.price).replace(/[^0-9.]/g, ''));
1295
+ if (!isNaN(price)) return price;
1296
+ }
1297
+
1298
+ // MediaMarkt specific
1299
+ if (item.priceValue !== undefined) {
1300
+ const price = parseFloat(String(item.priceValue).replace(/[^0-9.]/g, ''));
1301
+ if (!isNaN(price)) return price;
1302
+ }
1303
+
1304
+ // Generic fallbacks
1305
+ if (item.price_value !== undefined) {
1306
+ const price = parseFloat(String(item.price_value).replace(/[^0-9.]/g, ''));
1307
+ if (!isNaN(price)) return price;
1308
+ }
1309
+
1310
+ if (item.listing_price !== undefined) {
1311
+ const price = parseFloat(String(item.listing_price).replace(/[^0-9.]/g, ''));
1312
+ if (!isNaN(price)) return price;
1313
+ }
1314
+
1315
+ if (item.salePrice !== undefined) {
1316
+ const price = parseFloat(String(item.salePrice).replace(/[^0-9.]/g, ''));
1317
+ if (!isNaN(price)) return price;
1318
+ }
1319
+
1320
+ return null;
1321
+ }
1322
+
1323
+ function formatPrice(price) {
1324
+ if (price === null || price === undefined) return 'N/A';
1325
+ if (typeof price === 'number') {
1326
+ return '$' + price.toFixed(2);
1327
+ }
1328
+ return String(price);
1329
+ }
1330
+
1331
+ // ============================================
1332
+ // MAIN SEARCH
1333
+ // ============================================
1334
+ async function searchProducts() {
1335
+ if (!API_KEY) {
1336
+ showAlert('error', '❌ Save your API key first');
1337
+ return;
1338
+ }
1339
+
1340
+ const searchInput = document.getElementById('searchInput').value.trim();
1341
+ if (!searchInput) {
1342
+ showAlert('error', '❌ Enter a product name');
1343
+ return;
1344
+ }
1345
+
1346
+ const platforms = [];
1347
+ if (document.getElementById('cex').checked) platforms.push('cex');
1348
+ if (document.getElementById('mediamarkt').checked) platforms.push('mediamarkt');
1349
+ if (document.getElementById('backmarket').checked) platforms.push('backmarket');
1350
+
1351
+ if (platforms.length === 0) {
1352
+ showAlert('error', '❌ Select at least one platform');
1353
+ return;
1354
+ }
1355
+
1356
+ document.getElementById('searchBtn').disabled = true;
1357
+ document.getElementById('results').innerHTML = '';
1358
+ document.getElementById('noResults').classList.remove('active');
1359
+ document.getElementById('stats').classList.remove('active');
1360
+ setLoading(true);
1361
+
1362
+ const startTime = Date.now();
1363
+ let allResults = [];
1364
+ let successCount = 0;
1365
+
1366
+ try {
1367
+ const promises = platforms.map(p => searchPlatform(p, searchInput));
1368
+ const results = await Promise.allSettled(promises);
1369
+
1370
+ results.forEach((result, idx) => {
1371
+ if (result.status === 'fulfilled' && result.value && result.value.length > 0) {
1372
+ allResults = allResults.concat(result.value);
1373
+ successCount++;
1374
+ }
1375
+ });
1376
+
1377
+ const timeElapsed = ((Date.now() - startTime) / 1000).toFixed(2);
1378
+
1379
+ if (allResults.length === 0) {
1380
+ document.getElementById('noResults').classList.add('active');
1381
+ } else {
1382
+ displayResults(allResults, successCount, timeElapsed);
1383
+ showAlert('success', `βœ… Found ${allResults.length} products`);
1384
+ }
1385
+
1386
+ } catch (error) {
1387
+ showAlert('error', '❌ Error: ' + error.message);
1388
+ } finally {
1389
+ setLoading(false);
1390
+ document.getElementById('searchBtn').disabled = false;
1391
+ }
1392
+ }
1393
+
1394
+ // ============================================
1395
+ // PLATFORM SEARCH
1396
+ // ============================================
1397
+ async function searchPlatform(platform, query) {
1398
+ const actor = ACTORS[platform];
1399
+ const url = `https://api.apify.com/v2/acts/${actor.id}${actor.endpoint}?token=${API_KEY}`;
1400
+
1401
+ let payload = {};
1402
+
1403
+ if (platform === 'cex') {
1404
+ payload = {
1405
+ search_input: query,
1406
+ max_items: parseInt(document.getElementById('maxItems').value) || 5,
1407
+ include_images: true,
1408
+ include_trade_values: true,
1409
+ throttle: 2
1410
+ };
1411
+ } else if (platform === 'backmarket') {
1412
+ payload = {
1413
+ function: 'country_and_keywords',
1414
+ searchs: [query],
1415
+ country_input: 'United States',
1416
+ limit_per_search: parseInt(document.getElementById('maxItems').value) || 5
1417
+ };
1418
+ } else if (platform === 'mediamarkt') {
1419
+ payload = {
1420
+ search_input: query,
1421
+ max_items: parseInt(document.getElementById('maxItems').value) || 5
1422
+ };
1423
+ }
1424
+
1425
+ try {
1426
+ const controller = new AbortController();
1427
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT);
1428
+
1429
+ const response = await fetch(url, {
1430
+ method: 'POST',
1431
+ headers: { 'Content-Type': 'application/json' },
1432
+ body: JSON.stringify(payload),
1433
+ signal: controller.signal
1434
+ });
1435
+
1436
+ clearTimeout(timeout);
1437
+
1438
+ if (!response.ok) {
1439
+ throw new Error(`HTTP ${response.status}`);
1440
+ }
1441
+
1442
+ const items = await response.json();
1443
+
1444
+ if (!Array.isArray(items)) {
1445
+ return [];
1446
+ }
1447
+
1448
+ return items.map(item => ({
1449
+ ...item,
1450
+ source: platform.charAt(0).toUpperCase() + platform.slice(1),
1451
+ _platform: platform
1452
+ }));
1453
+
1454
+ } catch (error) {
1455
+ console.error(`${platform} error:`, error);
1456
+ return [];
1457
+ }
1458
+ }
1459
+
1460
+ // ============================================
1461
+ // DISPLAY RESULTS
1462
+ // ============================================
1463
+ function displayResults(results, platformCount, timeElapsed) {
1464
+ const resultsDiv = document.getElementById('results');
1465
+ const statsDiv = document.getElementById('stats');
1466
+
1467
+ statsDiv.classList.add('active');
1468
+ document.getElementById('totalResults').textContent = results.length;
1469
+ document.getElementById('platformsUsed').textContent = platformCount;
1470
+ document.getElementById('searchTime').textContent = timeElapsed + 's';
1471
+
1472
+ results.forEach(item => {
1473
+ const card = createCard(item);
1474
+ resultsDiv.appendChild(card);
1475
+ });
1476
+ }
1477
+
1478
+ function createCard(item) {
1479
+ const card = document.createElement('div');
1480
+ card.className = 'result-card';
1481
+
1482
+ const title = item.title || item.name || item.product_title || 'No title';
1483
+ const price = extractPrice(item);
1484
+ const formattedPrice = formatPrice(price);
1485
+ const url = item.url || item.link || item.product_url || '#';
1486
+ const image = item.image_url || item.imageUrl || item.image || null;
1487
+ const description = item.description || item.condition || '';
1488
+
1489
+ let html = `
1490
+ <div class="card-header">
1491
+ <div class="card-title" title="${escapeHtml(title)}">${escapeHtml(title.substring(0, 45))}</div>
1492
+ <span class="platform-badge">${item.source}</span>
1493
+ </div>
1494
+ <div class="card-content">
1495
+ `;
1496
+
1497
+ if (image) {
1498
+ html += `<img src="${escapeHtml(image)}" class="card-image" onerror="this.style.display='none'">`;
1499
+ }
1500
+
1501
+ html += `
1502
+ <div class="product-info">
1503
+ <div class="label">πŸ’° Price</div>
1504
+ <div class="price">${escapeHtml(formattedPrice)}</div>
1505
+ </div>
1506
+ `;
1507
+
1508
+ if (description) {
1509
+ html += `
1510
+ <div class="product-info">
1511
+ <div class="label">πŸ“ Details</div>
1512
+ <div class="value">${escapeHtml(description.substring(0, 100))}</div>
1513
+ </div>
1514
+ `;
1515
+ }
1516
+
1517
+ if (url && url !== '#') {
1518
+ html += `
1519
+ <div class="product-info">
1520
+ <a href="${escapeHtml(url)}" target="_blank" class="link">πŸ”— View</a>
1521
+ </div>
1522
+ `;
1523
+ }
1524
+
1525
+ html += '</div>';
1526
+ card.innerHTML = html;
1527
+ return card;
1528
+ }
1529
+ </script>
1530
+ </body>
1531
+ </html>