flen-crypto commited on
Commit
198e201
·
verified ·
1 Parent(s): 90ce329

none of the buttons function

Browse files
Files changed (4) hide show
  1. collection.js +13 -5
  2. components/vinyl-footer.js +20 -16
  3. index.html +10 -11
  4. script.js +1871 -3
collection.js CHANGED
@@ -1336,8 +1336,8 @@ function extractPrice(priceValue) {
1336
  function addRecordManually() {
1337
  // Create empty record template with placeholder values
1338
  const newRecord = {
1339
- artist: 'Please Update',
1340
- title: 'Please Update',
1341
  label: '',
1342
  catalogueNumber: '',
1343
  year: null,
@@ -1356,9 +1356,17 @@ function addRecordManually() {
1356
  enrichmentStatus: 'pending'
1357
  };
1358
 
1359
- pendingImports = [newRecord];
1360
- currentVerifyIndex = 0;
1361
- startPhotoVerification();
 
 
 
 
 
 
 
 
1362
  }
1363
  // Update record prices using available services
1364
  async function updateRecordPrices(index) {
 
1336
  function addRecordManually() {
1337
  // Create empty record template with placeholder values
1338
  const newRecord = {
1339
+ artist: 'Unknown Artist',
1340
+ title: 'Unknown Title',
1341
  label: '',
1342
  catalogueNumber: '',
1343
  year: null,
 
1356
  enrichmentStatus: 'pending'
1357
  };
1358
 
1359
+ // Add directly to collection
1360
+ collection.push(newRecord);
1361
+ saveCollection();
1362
+ renderCollection();
1363
+ updatePortfolioStats();
1364
+
1365
+ // Show edit modal for the new record
1366
+ const newIndex = collection.length - 1;
1367
+ viewRecordDetail(newIndex);
1368
+
1369
+ showToast('New record added. Fill in the details!', 'success');
1370
  }
1371
  // Update record prices using available services
1372
  async function updateRecordPrices(index) {
components/vinyl-footer.js CHANGED
@@ -82,8 +82,12 @@ class VinylFooter extends HTMLElement {
82
  .social-links a:hover {
83
  color: #7c3aed;
84
  }
 
 
 
 
85
  </style>
86
- <footer>
87
  <div class="footer-container">
88
  <div class="footer-grid">
89
  <div class="footer-section">
@@ -92,37 +96,37 @@ class VinylFooter extends HTMLElement {
92
  <li><a href="index.html">New Listing</a></li>
93
  <li><a href="deals.html">Deal Finder</a></li>
94
  <li><a href="collection.html">My Collection</a></li>
95
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Bulk Upload</a></li>
96
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Price Tracker</a></li>
97
  </ul>
98
  </div>
99
- <div class="footer-section">
100
  <h4>Resources</h4>
101
  <ul>
102
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Grading Guide</a></li>
103
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Shipping Best Practices</a></li>
104
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">eBay Policy Updates</a></li>
105
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Discogs Integration</a></li>
106
  </ul>
107
  </div>
108
  <div class="footer-section">
109
  <h4>Support</h4>
110
  <ul>
111
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Help Center</a></li>
112
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Contact Us</a></li>
113
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Feature Request</a></li>
114
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Report Bug</a></li>
115
  </ul>
116
  </div>
117
  <div class="footer-section">
118
  <h4>Legal</h4>
119
  <ul>
120
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Privacy Policy</a></li>
121
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Terms of Service</a></li>
122
- <li><a href="#" onclick="event.preventDefault(); alert('Coming soon')">Cookie Policy</a></li>
123
  </ul>
124
  </div>
125
- </div>
126
  <div class="footer-bottom">
127
  <p class="copyright">© 2024 VinylVault Pro. Built for record sellers.</p>
128
  <div class="social-links">
 
82
  .social-links a:hover {
83
  color: #7c3aed;
84
  }
85
+ .disabled-link {
86
+ opacity: 0.6;
87
+ cursor: not-allowed;
88
+ }
89
  </style>
90
+ <footer>
91
  <div class="footer-container">
92
  <div class="footer-grid">
93
  <div class="footer-section">
 
96
  <li><a href="index.html">New Listing</a></li>
97
  <li><a href="deals.html">Deal Finder</a></li>
98
  <li><a href="collection.html">My Collection</a></li>
99
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Bulk Upload</a></li>
100
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Price Tracker</a></li>
101
  </ul>
102
  </div>
103
+ <div class="footer-section">
104
  <h4>Resources</h4>
105
  <ul>
106
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Grading Guide</a></li>
107
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Shipping Best Practices</a></li>
108
+ <li><a href="#" class="disabled-link" data-coming-soon="true">eBay Policy Updates</a></li>
109
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Discogs Integration</a></li>
110
  </ul>
111
  </div>
112
  <div class="footer-section">
113
  <h4>Support</h4>
114
  <ul>
115
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Help Center</a></li>
116
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Contact Us</a></li>
117
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Feature Request</a></li>
118
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Report Bug</a></li>
119
  </ul>
120
  </div>
121
  <div class="footer-section">
122
  <h4>Legal</h4>
123
  <ul>
124
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Privacy Policy</a></li>
125
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Terms of Service</a></li>
126
+ <li><a href="#" class="disabled-link" data-coming-soon="true">Cookie Policy</a></li>
127
  </ul>
128
  </div>
129
+ </div>
130
  <div class="footer-bottom">
131
  <p class="copyright">© 2024 VinylVault Pro. Built for record sellers.</p>
132
  <div class="social-links">
index.html CHANGED
@@ -213,23 +213,22 @@
213
  </div>
214
  </div>
215
  </div>
216
-
217
  <!-- Action Buttons -->
218
  <div class="flex flex-wrap gap-3 mt-8 pt-6 border-t border-gray-700">
219
- <button onclick="generateListing()" class="px-6 py-3 bg-gradient-to-r from-primary to-purple-600 rounded-lg font-medium hover:shadow-lg hover:shadow-primary/25 transition-all flex items-center gap-2">
220
  <i data-feather="zap" class="w-4 h-4"></i>
221
  Generate Full Listing
222
  </button>
223
- <button onclick="draftAnalysis()" class="px-6 py-3 bg-surface border border-gray-600 rounded-lg font-medium hover:border-secondary hover:text-secondary transition-all flex items-center gap-2">
224
  <i data-feather="eye" class="w-4 h-4"></i>
225
  Quick Preview
226
  </button>
227
- <button onclick="requestHelp()" class="px-6 py-3 text-gray-400 hover:text-gray-200 transition-all flex items-center gap-2 ml-auto">
228
  <i data-feather="help-circle" class="w-4 h-4"></i>
229
  Need Help?
230
  </button>
231
  </div>
232
- </div>
233
  </section>
234
 
235
  <!-- Results Section (Hidden by default) -->
@@ -278,8 +277,8 @@
278
  <i data-feather="code" class="w-5 h-5 text-secondary"></i>
279
  eBay HTML Description
280
  </h3>
281
- <button onclick="copyHTML()" class="px-3 py-1.5 bg-primary/20 text-primary rounded-lg text-sm hover:bg-primary/30 transition-colors flex items-center gap-2">
282
- <i data-feather="copy" class="w-4 h-4"></i>
283
  Copy HTML
284
  </button>
285
  </div>
@@ -302,8 +301,8 @@
302
  <i data-feather="hash" class="w-5 h-5 text-pink-500"></i>
303
  eBay Item Specifics / Tags
304
  </h3>
305
- <button onclick="copyTags()" class="px-3 py-1.5 bg-pink-500/20 text-pink-400 rounded-lg text-sm hover:bg-pink-500/30 transition-colors flex items-center gap-2">
306
- <i data-feather="copy" class="w-4 h-4"></i>
307
  Copy Tags
308
  </button>
309
  </div>
@@ -318,8 +317,8 @@
318
  <i data-feather="camera" class="w-5 h-5 text-orange-500"></i>
319
  Recommended Photo Shot List
320
  </h3>
321
- <button onclick="analyzePhotoTypes()" class="px-3 py-1.5 bg-orange-500/20 text-orange-400 rounded-lg text-sm hover:bg-orange-500/30 transition-colors flex items-center gap-2">
322
- <i data-feather="refresh-cw" class="w-4 h-4"></i>
323
  Auto-Detect Shots
324
  </button>
325
  </div>
 
213
  </div>
214
  </div>
215
  </div>
 
216
  <!-- Action Buttons -->
217
  <div class="flex flex-wrap gap-3 mt-8 pt-6 border-t border-gray-700">
218
+ <button id="generateListingBtn" class="px-6 py-3 bg-gradient-to-r from-primary to-purple-600 rounded-lg font-medium hover:shadow-lg hover:shadow-primary/25 transition-all flex items-center gap-2">
219
  <i data-feather="zap" class="w-4 h-4"></i>
220
  Generate Full Listing
221
  </button>
222
+ <button id="draftAnalysisBtn" class="px-6 py-3 bg-surface border border-gray-600 rounded-lg font-medium hover:border-secondary hover:text-secondary transition-all flex items-center gap-2">
223
  <i data-feather="eye" class="w-4 h-4"></i>
224
  Quick Preview
225
  </button>
226
+ <button id="requestHelpBtn" class="px-6 py-3 text-gray-400 hover:text-gray-200 transition-all flex items-center gap-2 ml-auto">
227
  <i data-feather="help-circle" class="w-4 h-4"></i>
228
  Need Help?
229
  </button>
230
  </div>
231
+ </div>
232
  </section>
233
 
234
  <!-- Results Section (Hidden by default) -->
 
277
  <i data-feather="code" class="w-5 h-5 text-secondary"></i>
278
  eBay HTML Description
279
  </h3>
280
+ <button id="copyHTMLBtn" class="px-3 py-1.5 bg-primary/20 text-primary rounded-lg text-sm hover:bg-primary/30 transition-colors flex items-center gap-2">
281
+ <i data-feather="copy" class="w-4 h-4"></i>
282
  Copy HTML
283
  </button>
284
  </div>
 
301
  <i data-feather="hash" class="w-5 h-5 text-pink-500"></i>
302
  eBay Item Specifics / Tags
303
  </h3>
304
+ <button id="copyTagsBtn" class="px-3 py-1.5 bg-pink-500/20 text-pink-400 rounded-lg text-sm hover:bg-pink-500/30 transition-colors flex items-center gap-2">
305
+ <i data-feather="copy" class="w-4 h-4"></i>
306
  Copy Tags
307
  </button>
308
  </div>
 
317
  <i data-feather="camera" class="w-5 h-5 text-orange-500"></i>
318
  Recommended Photo Shot List
319
  </h3>
320
+ <button id="analyzePhotoTypesBtn" class="px-3 py-1.5 bg-orange-500/20 text-orange-400 rounded-lg text-sm hover:bg-orange-500/30 transition-colors flex items-center gap-2">
321
+ <i data-feather="refresh-cw" class="w-4 h-4"></i>
322
  Auto-Detect Shots
323
  </button>
324
  </div>
script.js CHANGED
@@ -701,7 +701,1875 @@ async function performAnalysis(data) {
701
  const { artist, title, catNo, year, cost, goal, market } = data;
702
 
703
  // Determine currency symbol
704
- const currency = market === 'uk' ? '£' : market === 'us' ? 'function generateTitles(base, catNo, year, goal) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
705
  const titles = [];
706
  const cat = catNo || 'CAT#';
707
  const yr = year || 'YEAR';
@@ -723,13 +2591,13 @@ async function performAnalysis(data) {
723
 
724
  // Option 5: Genre tagged
725
  titles.push(`${base} ${yr} ${format} ${genre} ${cat} VG+ Plays Great`);
726
- return titles.map((t, i) => ({
 
727
  text: t.length > 80 ? t.substring(0, 77) + '...' : t,
728
  chars: Math.min(t.length, 80),
729
  style: ['Classic Collector', 'Condition Forward', 'Rarity Focus', 'Clean Search', 'Genre Tagged'][i]
730
  }));
731
  }
732
-
733
  function renderTitleOptions(titles) {
734
  const container = document.getElementById('titleOptions');
735
  container.innerHTML = titles.map((t, i) => `
 
701
  const { artist, title, catNo, year, cost, goal, market } = data;
702
 
703
  // Determine currency symbol
704
+ const currency = market === 'uk' ? '£' : market === 'us' ? 'function renderTitleOptions(titles) {
705
+ const container = document.getElementById('titleOptions');
706
+ container.innerHTML = titles.map((t, i) => `
707
+ <div class="title-option ${i === 0 ? 'selected' : ''}" onclick="selectTitle(this, '${t.text.replace(/'/g, "\\'")}')">
708
+ <span class="char-count">${t.chars}/80</span>
709
+ <p class="font-medium text-gray-200 pr-16">${t.text}</p>
710
+ <p class="text-sm text-gray-500 mt-1">${t.style}</p>
711
+ </div>
712
+ `).join('');
713
+ }
714
+
715
+ function selectTitle(el, text) {
716
+ document.querySelectorAll('.title-option').forEach(o => o.classList.remove('selected'));
717
+ el.classList.add('selected');
718
+ // Update clipboard copy
719
+ navigator.clipboard.writeText(text);
720
+ showToast('Title copied to clipboard!', 'success');
721
+ }
722
+
723
+ function renderPricingStrategy(bin, strategy, comps, currency, goal) {
724
+ const container = document.getElementById('pricingStrategy');
725
+
726
+ const offerSettings = goal === 'max' ? 'Offers: OFF' :
727
+ `Auto-accept: ${currency}${Math.floor(bin * 0.85)} | Auto-decline: ${currency}${Math.floor(bin * 0.7)}`;
728
+
729
+ container.innerHTML = `
730
+ <div class="pricing-card recommended">
731
+ <div class="flex items-center gap-2 mb-3">
732
+ <span class="px-2 py-1 bg-accent/20 text-accent text-xs font-medium rounded">RECOMMENDED</span>
733
+ </div>
734
+ <p class="text-3xl font-bold text-white mb-1">${currency}${bin}</p>
735
+ <p class="text-sm text-gray-400 mb-3">Buy It Now</p>
736
+ <div class="space-y-2 text-sm">
737
+ <p class="flex justify-between"><span class="text-gray-500">Strategy:</span> <span class="text-gray-300">${strategy}</span></p>
738
+ <p class="flex justify-between"><span class="text-gray-500">Best Offer:</span> <span class="text-gray-300">${offerSettings}</span></p>
739
+ <p class="flex justify-between"><span class="text-gray-500">Duration:</span> <span class="text-gray-300">30 days (GTC)</span></p>
740
+ </div>
741
+ </div>
742
+ <div class="space-y-3">
743
+ <h4 class="text-sm font-medium text-gray-400 uppercase tracking-wide">Sold Comps by Grade</h4>
744
+ <div class="space-y-2">
745
+ <div class="flex justify-between items-center p-3 bg-surface rounded-lg">
746
+ <span class="text-green-400 font-medium">NM/NM-</span>
747
+ <span class="text-gray-300">${currency}${comps.nm.low}-${comps.nm.high} <span class="text-gray-500">(med: ${comps.nm.median})</span></span>
748
+ </div>
749
+ <div class="flex justify-between items-center p-3 bg-surface rounded-lg border border-accent/30">
750
+ <span class="text-accent font-medium">VG+/EX</span>
751
+ <span class="text-gray-300">${currency}${comps.vgplus.low}-${comps.vgplus.high} <span class="text-gray-500">(med: ${comps.vgplus.median})</span></span>
752
+ </div>
753
+ <div class="flex justify-between items-center p-3 bg-surface rounded-lg">
754
+ <span class="text-yellow-400 font-medium">VG/VG+</span>
755
+ <span class="text-gray-300">${currency}${comps.vg.low}-${comps.vg.high} <span class="text-gray-500">(med: ${comps.vg.median})</span></span>
756
+ </div>
757
+ </div>
758
+ <p class="text-xs text-gray-500 mt-2">Based on last 90 days sold listings, same pressing. Prices exclude postage.</p>
759
+ </div>
760
+ `;
761
+ }
762
+
763
+ function renderFeeFloor(cost, fees, shipping, packing, safeFloor, currency) {
764
+ const container = document.getElementById('feeFloor');
765
+ container.innerHTML = `
766
+ <div class="text-center p-4 bg-surface rounded-lg">
767
+ <p class="text-xs text-gray-500 uppercase mb-1">Your Cost</p>
768
+ <p class="text-xl font-bold text-gray-300">${currency}${cost.toFixed(2)}</p>
769
+ </div>
770
+ <div class="text-center p-4 bg-surface rounded-lg">
771
+ <p class="text-xs text-gray-500 uppercase mb-1">Est. Fees</p>
772
+ <p class="text-xl font-bold text-red-400">${currency}${fees.toFixed(2)}</p>
773
+ <p class="text-xs text-gray-600">~16% total</p>
774
+ </div>
775
+ <div class="text-center p-4 bg-surface rounded-lg">
776
+ <p class="text-xs text-gray-500 uppercase mb-1">Ship + Pack</p>
777
+ <p class="text-xl font-bold text-gray-300">${currency}${(shipping + packing).toFixed(2)}</p>
778
+ </div>
779
+ <div class="text-center p-4 bg-green-500/10 rounded-lg border border-green-500/30">
780
+ <p class="text-xs text-green-500 uppercase mb-1">Safe Floor Price</p>
781
+ <p class="text-2xl font-bold text-green-400">${currency}${safeFloor}</p>
782
+ <p class="text-xs text-green-600/70">Auto-decline below this</p>
783
+ </div>
784
+ `;
785
+ }
786
+ async function renderHTMLDescription(data, titleObj) {
787
+ const { artist, title, catNo, year } = data;
788
+ // Use hosted URL if available, otherwise fallback to local object URL
789
+ let heroImg = '';
790
+ let galleryImages = [];
791
+
792
+ if (hostedPhotoUrls.length > 0) {
793
+ heroImg = hostedPhotoUrls[0].displayUrl || hostedPhotoUrls[0].url;
794
+ galleryImages = hostedPhotoUrls.slice(1).map(img => img.displayUrl || img.url);
795
+ } else if (uploadedPhotos.length > 0) {
796
+ heroImg = URL.createObjectURL(uploadedPhotos[0]);
797
+ galleryImages = uploadedPhotos.slice(1).map((_, i) => URL.createObjectURL(uploadedPhotos[i + 1]));
798
+ }
799
+
800
+ // Use OCR-detected values if available
801
+ const detectedLabel = window.detectedLabel || '[Verify from photos]';
802
+ const detectedCountry = window.detectedCountry || 'UK';
803
+ const detectedFormat = window.detectedFormat || 'LP • 33rpm';
804
+ const detectedGenre = window.detectedGenre || 'rock';
805
+ const detectedCondition = window.detectedCondition || 'VG+/VG+';
806
+ const detectedPressingInfo = window.detectedPressingInfo || '';
807
+
808
+ // Fetch tracklist and detailed info from Discogs if available
809
+ let tracklistHtml = '';
810
+ let pressingDetailsHtml = '';
811
+ let provenanceHtml = '';
812
+
813
+ if (window.discogsReleaseId && window.discogsService?.key) {
814
+ try {
815
+ const discogsData = await window.discogsService.fetchTracklist(window.discogsReleaseId);
816
+ if (discogsData && discogsData.tracklist) {
817
+ // Build tracklist HTML
818
+ const hasSideBreakdown = discogsData.tracklist.some(t => t.position && (t.position.startsWith('A') || t.position.startsWith('B')));
819
+
820
+ if (hasSideBreakdown) {
821
+ // Group by sides
822
+ const sides = {};
823
+ discogsData.tracklist.forEach(track => {
824
+ const side = track.position ? track.position.charAt(0) : 'Other';
825
+ if (!sides[side]) sides[side] = [];
826
+ sides[side].push(track);
827
+ });
828
+
829
+ tracklistHtml = Object.entries(sides).map(([side, tracks]) => `
830
+ <div style="margin-bottom: 16px;">
831
+ <h4 style="color: #7c3aed; font-size: 13px; font-weight: 600; margin: 0 0 8px 0; text-transform: uppercase; letter-spacing: 0.5px;">Side ${side}</h4>
832
+ <div style="display: flex; flex-wrap: wrap; gap: 8px;">
833
+ ${tracks.map(track => `
834
+ <div style="flex: 1 1 200px; min-width: 200px; display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: #f8fafc; border-radius: 6px; border: 1px solid #e2e8f0;">
835
+ <span style="color: #1e293b; font-size: 13px;"><strong>${track.position}</strong> ${track.title}</span>
836
+ ${track.duration ? `<span style="color: #64748b; font-size: 12px; font-family: monospace;">${track.duration}</span>` : ''}
837
+ </div>
838
+ `).join('')}
839
+ </div>
840
+ </div>
841
+ `).join('');
842
+ } else {
843
+ // Simple list
844
+ tracklistHtml = `
845
+ <div style="display: flex; flex-wrap: wrap; gap: 8px;">
846
+ ${discogsData.tracklist.map(track => `
847
+ <div style="flex: 1 1 200px; min-width: 200px; display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: #f8fafc; border-radius: 6px; border: 1px solid #e2e8f0;">
848
+ <span style="color: #1e293b; font-size: 13px;">${track.position ? `<strong>${track.position}</strong> ` : ''}${track.title}</span>
849
+ ${track.duration ? `<span style="color: #64748b; font-size: 12px; font-family: monospace;">${track.duration}</span>` : ''}
850
+ </div>
851
+ `).join('')}
852
+ </div>
853
+ `;
854
+ }
855
+
856
+ // Build pressing/variation details
857
+ const identifiers = discogsData.identifiers || [];
858
+ const barcodeInfo = identifiers.find(i => i.type === 'Barcode');
859
+ const matrixInfo = identifiers.filter(i => i.type === 'Matrix / Runout' || i.type === 'Runout');
860
+ const pressingInfo = identifiers.filter(i => i.type === 'Pressing Plant' || i.type === 'Mastering');
861
+
862
+ if (matrixInfo.length > 0 || barcodeInfo || pressingInfo.length > 0) {
863
+ pressingDetailsHtml = `
864
+ <div style="background: #f0fdf4; border-left: 4px solid #22c55e; padding: 16px 20px; margin: 24px 0; border-radius: 0 8px 8px 0;">
865
+ <h3 style="margin: 0 0 12px 0; color: #166534; font-size: 15px; font-weight: 600;">Pressing & Matrix Information</h3>
866
+ <div style="font-family: monospace; font-size: 13px; line-height: 1.6; color: #15803d;">
867
+ ${barcodeInfo ? `<p style="margin: 4px 0;"><strong>Barcode:</strong> ${barcodeInfo.value}</p>` : ''}
868
+ ${matrixInfo.map(m => `<p style="margin: 4px 0;"><strong>${m.type}:</strong> ${m.value}${m.description ? ` <em>(${m.description})</em>` : ''}</p>`).join('')}
869
+ ${pressingInfo.map(p => `<p style="margin: 4px 0;"><strong>${p.type}:</strong> ${p.value}</p>`).join('')}
870
+ </div>
871
+ ${discogsData.notes ? `<p style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #bbf7d0; font-size: 12px; color: #166534; font-style: italic;">${discogsData.notes.substring(0, 300)}${discogsData.notes.length > 300 ? '...' : ''}</p>` : ''}
872
+ </div>
873
+ `;
874
+ }
875
+
876
+ // Build provenance data for buyer confidence
877
+ const companies = discogsData.companies || [];
878
+ const masteredBy = companies.find(c => c.entity_type_name === 'Mastered At' || c.name.toLowerCase().includes('mastering'));
879
+ const pressedBy = companies.find(c => c.entity_type_name === 'Pressed By' || c.name.toLowerCase().includes('pressing'));
880
+ const lacquerCut = companies.find(c => c.entity_type_name === 'Lacquer Cut At');
881
+
882
+ if (masteredBy || pressedBy || lacquerCut) {
883
+ provenanceHtml = `
884
+ <div style="background: #eff6ff; border: 1px solid #bfdbfe; padding: 16px; margin: 24px 0; border-radius: 8px;">
885
+ <h3 style="margin: 0 0 12px 0; color: #1e40af; font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px;">
886
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
887
+ Provenance & Production
888
+ </h3>
889
+ <div style="font-size: 13px; color: #1e3a8a; line-height: 1.6;">
890
+ ${masteredBy ? `<p style="margin: 4px 0;">✓ Mastered at <strong>${masteredBy.name}</strong></p>` : ''}
891
+ ${lacquerCut ? `<p style="margin: 4px 0;">✓ Lacquer cut at <strong>${lacquerCut.name}</strong></p>` : ''}
892
+ ${pressedBy ? `<p style="margin: 4px 0;">✓ Pressed at <strong>${pressedBy.name}</strong></p>` : ''}
893
+ ${discogsData.num_for_sale ? `<p style="margin: 8px 0 0 0; padding-top: 8px; border-top: 1px solid #bfdbfe; color: #3b82f6; font-size: 12px;">Reference: ${discogsData.num_for_sale} copies currently for sale on Discogs</p>` : ''}
894
+ </div>
895
+ </div>
896
+ `;
897
+ }
898
+ }
899
+ } catch (e) {
900
+ console.error('Failed to fetch Discogs details for HTML:', e);
901
+ }
902
+ }
903
+
904
+ // If no tracklist from Discogs, provide placeholder
905
+ if (!tracklistHtml) {
906
+ tracklistHtml = `<p style="color: #64748b; font-style: italic;">Tracklist verification recommended. Please compare with Discogs entry for accuracy.</p>`;
907
+ }
908
+ const galleryHtml = galleryImages.length > 0 ? `
909
+ <!-- PHOTO GALLERY -->
910
+ <div style="margin-bottom: 24px;">
911
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;">
912
+ ${galleryImages.map(url => `<img src="${url}" style="width: 100%; height: 150px; object-fit: cover; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);" alt="Record photo">`).join('')}
913
+ </div>
914
+ </div>
915
+ ` : '';
916
+
917
+ const html = `<div style="max-width: 800px; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #333; line-height: 1.6;">
918
+
919
+ <!-- HERO IMAGE -->
920
+ <div style="margin-bottom: 24px;">
921
+ <img src="${heroImg}" alt="${artist} - ${title}" style="width: 100%; max-width: 600px; display: block; margin: 0 auto; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15);">
922
+ </div>
923
+
924
+ ${galleryHtml}
925
+ <!-- BADGES -->
926
+ <div style="display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-bottom: 24px;">
927
+ <span style="background: #7c3aed; color: white; padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;">Original ${detectedCountry} Pressing</span>
928
+ <span style="background: #059669; color: white; padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;">${year || '1970s'}</span>
929
+ <span style="background: #0891b2; color: white; padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;">${detectedFormat}</span>
930
+ <span style="background: #d97706; color: white; padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;">${detectedCondition}</span>
931
+ </div>
932
+ <!-- AT A GLANCE -->
933
+ <table style="width: 100%; border-collapse: collapse; margin-bottom: 24px; font-size: 14px;">
934
+ <tr style="background: #f8fafc;">
935
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600; width: 140px;">Artist</td>
936
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${artist || 'See title'}</td>
937
+ </tr>
938
+ <tr>
939
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Title</td>
940
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${title || 'See title'}</td>
941
+ </tr>
942
+ <tr style="background: #f8fafc;">
943
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Label</td>
944
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${detectedLabel}</td>
945
+ </tr>
946
+ <tr>
947
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Catalogue</td>
948
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;"><code style="background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">${catNo || '[See photos]'}</code></td>
949
+ </tr>
950
+ <tr style="background: #f8fafc;">
951
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Country</td>
952
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${detectedCountry}</td>
953
+ </tr>
954
+ <tr>
955
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Year</td>
956
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${year || '[Verify]'}</td>
957
+ </tr>
958
+ </table>
959
+
960
+ <!-- CONDITION -->
961
+ <div style="background: #fefce8; border-left: 4px solid #eab308; padding: 16px 20px; margin-bottom: 24px; border-radius: 0 8px 8px 0;">
962
+ <h3 style="margin: 0 0 12px 0; color: #854d0e; font-size: 16px; font-weight: 600;">Condition Report</h3>
963
+ <div style="display: grid; gap: 12px;">
964
+ <div>
965
+ <strong style="color: #713f12;">Vinyl:</strong> <span style="color: #854d0e;">VG+ — Light surface marks, plays cleanly with minimal surface noise. No skips or jumps. [Adjust based on actual inspection]</span>
966
+ </div>
967
+ <div>
968
+ <strong style="color: #713f12;">Sleeve:</strong> <span style="color: #854d0e;">VG+ — Minor edge wear, light ring wear visible under raking light. No splits or writing. [Adjust based on actual inspection]</span>
969
+ </div>
970
+ <div>
971
+ <strong style="color: #713f12;">Inner Sleeve:</strong> <span style="color: #854d0e;">Original paper inner included, small split at bottom seam. [Verify/Adjust]</span>
972
+ </div>
973
+ </div>
974
+ </div>
975
+ <!-- ABOUT -->
976
+ <h3 style="color: #1e293b; font-size: 18px; font-weight: 600; margin-bottom: 12px;">About This Release</h3>
977
+ <p style="margin-bottom: 16px; color: #475569;">${detectedGenre ? `${detectedGenre.charAt(0).toUpperCase() + detectedGenre.slice(1)} release` : 'Vintage vinyl release'}${detectedPressingInfo ? `. Matrix/Runout: ${detectedPressingInfo}` : ''}. [Add accurate description based on verified pressing details. Mention notable features: gatefold, insert, poster, hype sticker, etc.]</p>
978
+ <!-- TRACKLIST -->
979
+ <h3 style="color: #1e293b; font-size: 18px; font-weight: 600; margin-bottom: 12px;">Tracklist</h3>
980
+ <div style="background: #f8fafc; padding: 16px 20px; border-radius: 8px; margin-bottom: 24px;">
981
+ ${tracklistHtml}
982
+ </div>
983
+
984
+ ${pressingDetailsHtml}
985
+
986
+ ${provenanceHtml}
987
+ <!-- PACKING -->
988
+ <div style="background: #eff6ff; border-left: 4px solid #3b82f6; padding: 16px 20px; margin-bottom: 24px; border-radius: 0 8px 8px 0;">
989
+ <h3 style="margin: 0 0 12px 0; color: #1e40af; font-size: 16px; font-weight: 600;">Packing & Postage</h3>
990
+ <p style="margin: 0 0 12px 0; color: #1e3a8a;">Records are removed from outer sleeves to prevent seam splits during transit. Packed with stiffeners in a dedicated LP mailer. Royal Mail 48 Tracked or courier service.</p>
991
+ <p style="margin: 0; color: #1e3a8a; font-size: 14px;"><strong>Combined postage:</strong> Discount available for multiple purchases—please request invoice before payment.</p>
992
+ </div>
993
+
994
+ <!-- CTA -->
995
+ <div style="text-align: center; padding: 24px; background: #f1f5f9; border-radius: 12px;">
996
+ <p style="margin: 0 0 8px 0; color: #475569; font-weight: 500;">Questions? Need more photos?</p>
997
+ <p style="margin: 0; color: #64748b; font-size: 14px;">Message me anytime—happy to provide additional angles, audio clips, or pressing details.</p>
998
+ </div>
999
+
1000
+ </div>`;
1001
+
1002
+ // Store reference to hosted images for potential cleanup
1003
+ window.currentListingImages = hostedPhotoUrls.map(img => ({
1004
+ url: img.url,
1005
+ deleteUrl: img.deleteUrl
1006
+ }));
1007
+ document.getElementById('htmlOutput').value = html;
1008
+ }
1009
+ function renderTags(artist, title, catNo, year) {
1010
+ const genre = window.detectedGenre || 'rock';
1011
+ const format = window.detectedFormat?.toLowerCase().includes('7"') ? '7 inch' :
1012
+ window.detectedFormat?.toLowerCase().includes('12"') ? '12 inch single' : 'lp';
1013
+ const country = window.detectedCountry?.toLowerCase() || 'uk';
1014
+
1015
+ const tags = [
1016
+ artist || 'vinyl',
1017
+ title || 'record',
1018
+ format,
1019
+ 'vinyl record',
1020
+ 'original pressing',
1021
+ `${country} pressing`,
1022
+ year || 'vintage',
1023
+ catNo || '',
1024
+ genre,
1025
+ genre === 'rock' ? 'prog rock' : genre,
1026
+ genre === 'rock' ? 'psych' : '',
1027
+ 'collector',
1028
+ 'audiophile',
1029
+ format === 'lp' ? '12 inch' : format,
1030
+ '33 rpm',
1031
+ format === 'lp' ? 'album' : 'single',
1032
+ 'used vinyl',
1033
+ 'graded',
1034
+ 'excellent condition',
1035
+ 'rare vinyl',
1036
+ 'classic rock',
1037
+ 'vintage vinyl',
1038
+ 'record collection',
1039
+ 'music',
1040
+ 'audio',
1041
+ window.detectedLabel || ''
1042
+ ].filter(Boolean);
1043
+ const container = document.getElementById('tagsOutput');
1044
+ container.innerHTML = tags.map(t => `
1045
+ <span class="px-3 py-1.5 bg-pink-500/10 text-pink-400 rounded-full text-sm border border-pink-500/20">${t}</span>
1046
+ `).join('');
1047
+ }
1048
+ function renderShotList() {
1049
+ // Map shot types to display info
1050
+ const shotDefinitions = [
1051
+ { id: 'front', name: 'Front cover (square, well-lit)', critical: true },
1052
+ { id: 'back', name: 'Back cover (full shot)', critical: true },
1053
+ { id: 'spine', name: 'Spine (readable text)', critical: true },
1054
+ { id: 'label_a', name: 'Label Side A (close, legible)', critical: true },
1055
+ { id: 'label_b', name: 'Label Side B (close, legible)', critical: true },
1056
+ { id: 'deadwax', name: 'Deadwax/runout grooves', critical: true },
1057
+ { id: 'inner', name: 'Inner sleeve (both sides)', critical: false },
1058
+ { id: 'insert', name: 'Insert/poster if included', critical: false },
1059
+ { id: 'hype', name: 'Hype sticker (if present)', critical: false },
1060
+ { id: 'vinyl', name: 'Vinyl in raking light (flaws)', critical: true },
1061
+ { id: 'corners', name: 'Sleeve corners/edges detail', critical: false },
1062
+ { id: 'barcode', name: 'Barcode area', critical: false }
1063
+ ];
1064
+
1065
+ // Check if we have any photos at all
1066
+ const hasPhotos = uploadedPhotos.length > 0;
1067
+
1068
+ const container = document.getElementById('shotList');
1069
+ container.innerHTML = shotDefinitions.map(shot => {
1070
+ const have = detectedPhotoTypes.has(shot.id) || (shot.id === 'front' && hasPhotos) || (shot.id === 'back' && uploadedPhotos.length > 1);
1071
+ const statusClass = have ? 'completed' : shot.critical ? 'missing' : '';
1072
+ const iconColor = have ? 'text-green-500' : shot.critical ? 'text-yellow-500' : 'text-gray-500';
1073
+ const textClass = have ? 'text-gray-400 line-through' : 'text-gray-300';
1074
+ const icon = have ? 'check-circle' : shot.critical ? 'alert-circle' : 'circle';
1075
+
1076
+ return `
1077
+ <div class="shot-item ${statusClass}">
1078
+ <i data-feather="${icon}"
1079
+ class="w-5 h-5 ${iconColor} flex-shrink-0"></i>
1080
+ <span class="text-sm ${textClass}">${shot.name}</span>
1081
+ ${shot.critical && !have ? '<span class="ml-auto text-xs text-yellow-500 font-medium">CRITICAL</span>' : ''}
1082
+ </div>
1083
+ `}).join('');
1084
+ feather.replace();
1085
+ }
1086
+ function copyHTML() {
1087
+ const html = document.getElementById('htmlOutput');
1088
+ html.select();
1089
+ document.execCommand('copy');
1090
+ showToast('HTML copied to clipboard!', 'success');
1091
+ }
1092
+
1093
+ function copyTags() {
1094
+ const tags = Array.from(document.querySelectorAll('#tagsOutput span')).map(s => s.textContent).join(', ');
1095
+ navigator.clipboard.writeText(tags);
1096
+ showToast('Tags copied to clipboard!', 'success');
1097
+ }
1098
+ // Preview/Draft Analysis - quick analysis without full AI generation
1099
+ async function draftAnalysis() {
1100
+ if (uploadedPhotos.length === 0) {
1101
+ showToast('Upload photos first for preview', 'error');
1102
+ return;
1103
+ }
1104
+
1105
+ const artist = document.getElementById('artistInput').value.trim();
1106
+ const title = document.getElementById('titleInput').value.trim();
1107
+
1108
+ // Show loading state
1109
+ const dropZone = document.getElementById('dropZone');
1110
+ const spinner = document.getElementById('uploadSpinner');
1111
+ spinner.classList.remove('hidden');
1112
+ dropZone.classList.add('pointer-events-none');
1113
+ startAnalysisProgressSimulation();
1114
+
1115
+ try {
1116
+ // Try OCR/AI analysis if available
1117
+ const service = getAIService();
1118
+ let ocrResult = null;
1119
+
1120
+ if (service && service.apiKey && uploadedPhotos.length > 0) {
1121
+ try {
1122
+ ocrResult = await service.analyzeRecordImages(uploadedPhotos.slice(0, 2)); // Limit to 2 photos for speed
1123
+ populateFieldsFromOCR(ocrResult);
1124
+ } catch (e) {
1125
+ console.log('Preview OCR failed:', e);
1126
+ }
1127
+ }
1128
+
1129
+ // Generate quick preview results
1130
+ const catNo = document.getElementById('catInput').value.trim() || ocrResult?.catalogueNumber || '';
1131
+ const year = document.getElementById('yearInput').value.trim() || ocrResult?.year || '';
1132
+ const detectedArtist = artist || ocrResult?.artist || 'Unknown Artist';
1133
+ const detectedTitle = title || ocrResult?.title || 'Unknown Title';
1134
+
1135
+ const baseTitle = `${detectedArtist} - ${detectedTitle}`;
1136
+
1137
+ // Generate quick titles
1138
+ const quickTitles = [
1139
+ `${baseTitle} ${year ? `(${year})` : ''} ${catNo} VG+`.substring(0, 80),
1140
+ `${baseTitle} Original Pressing Vinyl LP`.substring(0, 80),
1141
+ `${detectedArtist} ${detectedTitle} ${catNo || 'LP'}`.substring(0, 80)
1142
+ ].map((t, i) => ({
1143
+ text: t,
1144
+ chars: t.length,
1145
+ style: ['Quick', 'Standard', 'Compact'][i]
1146
+ }));
1147
+
1148
+ // Quick pricing estimate based on condition
1149
+ const cost = parseFloat(document.getElementById('costInput').value) || 10;
1150
+ const vinylCond = document.getElementById('vinylConditionInput').value;
1151
+ const sleeveCond = document.getElementById('sleeveConditionInput').value;
1152
+
1153
+ const conditionMultipliers = { 'M': 3, 'NM': 2.5, 'VG+': 1.8, 'VG': 1.2, 'G+': 0.8, 'G': 0.5 };
1154
+ const condMult = (conditionMultipliers[vinylCond] || 1) * 0.7 + (conditionMultipliers[sleeveCond] || 1) * 0.3;
1155
+
1156
+ const estimatedValue = Math.round(cost * Math.max(condMult, 1.5));
1157
+ const suggestedPrice = Math.round(estimatedValue * 0.9);
1158
+
1159
+ // Render preview results
1160
+ renderTitleOptions(quickTitles);
1161
+
1162
+ // Quick pricing card
1163
+ document.getElementById('pricingStrategy').innerHTML = `
1164
+ <div class="pricing-card recommended">
1165
+ <div class="flex items-center gap-2 mb-3">
1166
+ <span class="px-2 py-1 bg-accent/20 text-accent text-xs font-medium rounded">QUICK ESTIMATE</span>
1167
+ </div>
1168
+ <p class="text-3xl font-bold text-white mb-1">£${suggestedPrice}</p>
1169
+ <p class="text-sm text-gray-400 mb-3">Suggested Buy It Now</p>
1170
+ <div class="space-y-2 text-sm">
1171
+ <p class="flex justify-between"><span class="text-gray-500">Est. Value:</span> <span class="text-gray-300">£${estimatedValue}</span></p>
1172
+ <p class="flex justify-between"><span class="text-gray-500">Your Cost:</span> <span class="text-gray-300">£${cost.toFixed(2)}</span></p>
1173
+ <p class="flex justify-between"><span class="text-gray-500">Condition:</span> <span class="text-gray-300">${vinylCond}/${sleeveCond}</span></p>
1174
+ </div>
1175
+ </div>
1176
+ <div class="space-y-3">
1177
+ <h4 class="text-sm font-medium text-gray-400 uppercase tracking-wide">Preview Notes</h4>
1178
+ <div class="p-3 bg-surface rounded-lg text-sm text-gray-400">
1179
+ ${ocrResult ?
1180
+ `<p class="text-green-400 mb-2">✓ AI detected information from photos</p>` :
1181
+ `<p class="text-yellow-400 mb-2">⚠ Add API key in Settings for auto-detection</p>`
1182
+ }
1183
+ <p>This is a quick estimate based on your cost and condition. Run "Generate Full Listing" for complete market analysis, sold comps, and optimized pricing.</p>
1184
+ </div>
1185
+ ${ocrResult ? `
1186
+ <div class="p-3 bg-green-500/10 border border-green-500/20 rounded-lg">
1187
+ <p class="text-xs text-green-400 font-medium mb-1">Detected from photos:</p>
1188
+ <ul class="text-xs text-gray-400 space-y-1">
1189
+ ${ocrResult.artist ? `<li>• Artist: ${ocrResult.artist}</li>` : ''}
1190
+ ${ocrResult.title ? `<li>• Title: ${ocrResult.title}</li>` : ''}
1191
+ ${ocrResult.catalogueNumber ? `<li>• Cat#: ${ocrResult.catalogueNumber}</li>` : ''}
1192
+ ${ocrResult.year ? `<li>• Year: ${ocrResult.year}</li>` : ''}
1193
+ </ul>
1194
+ </div>
1195
+ ` : ''}
1196
+ </div>
1197
+ `;
1198
+
1199
+ // Simple fee floor
1200
+ const fees = suggestedPrice * 0.16;
1201
+ const safeFloor = Math.ceil(cost + fees + 6);
1202
+
1203
+ document.getElementById('feeFloor').innerHTML = `
1204
+ <div class="text-center p-4 bg-surface rounded-lg">
1205
+ <p class="text-xs text-gray-500 uppercase mb-1">Your Cost</p>
1206
+ <p class="text-xl font-bold text-gray-300">£${cost.toFixed(2)}</p>
1207
+ </div>
1208
+ <div class="text-center p-4 bg-surface rounded-lg">
1209
+ <p class="text-xs text-gray-500 uppercase mb-1">Est. Fees</p>
1210
+ <p class="text-xl font-bold text-red-400">£${fees.toFixed(2)}</p>
1211
+ </div>
1212
+ <div class="text-center p-4 bg-surface rounded-lg">
1213
+ <p class="text-xs text-gray-500 uppercase mb-1">Ship + Pack</p>
1214
+ <p class="text-xl font-bold text-gray-300">£6.00</p>
1215
+ </div>
1216
+ <div class="text-center p-4 bg-green-500/10 rounded-lg border border-green-500/30">
1217
+ <p class="text-xs text-green-500 uppercase mb-1">Safe Floor</p>
1218
+ <p class="text-2xl font-bold text-green-400">£${safeFloor}</p>
1219
+ </div>
1220
+ `;
1221
+
1222
+ // Preview HTML description
1223
+ const previewHtml = `<!-- QUICK PREVIEW - Generated by VinylVault Pro -->
1224
+ <div style="max-width: 700px; margin: 0 auto; font-family: sans-serif;">
1225
+ <h2 style="color: #333;">${detectedArtist} - ${detectedTitle}</h2>
1226
+ ${year ? `<p><strong>Year:</strong> ${year}</p>` : ''}
1227
+ ${catNo ? `<p><strong>Catalogue #:</strong> ${catNo}</p>` : ''}
1228
+ <p><strong>Condition:</strong> Vinyl ${vinylCond}, Sleeve ${sleeveCond}</p>
1229
+ <hr style="margin: 20px 0;">
1230
+ <p style="color: #666;">[Full description will be generated with complete market analysis]</p>
1231
+ </div>`;
1232
+
1233
+ const htmlOutput = document.getElementById('htmlOutput');
1234
+ if (htmlOutput) htmlOutput.value = previewHtml;
1235
+
1236
+ // Preview tags
1237
+ const previewTags = [
1238
+ detectedArtist,
1239
+ detectedTitle,
1240
+ 'vinyl',
1241
+ 'record',
1242
+ vinylCond,
1243
+ 'lp',
1244
+ year || 'vintage'
1245
+ ].filter(Boolean);
1246
+
1247
+ const tagsOutput = document.getElementById('tagsOutput');
1248
+ if (tagsOutput) {
1249
+ tagsOutput.innerHTML = previewTags.map(t => `
1250
+ <span class="px-3 py-1.5 bg-pink-500/10 text-pink-400 rounded-full text-sm border border-pink-500/20">${t}</span>
1251
+ `).join('');
1252
+ }
1253
+
1254
+ // Update shot list
1255
+ renderShotList();
1256
+
1257
+ // Show results
1258
+ const resultsSection = document.getElementById('resultsSection');
1259
+ const emptyState = document.getElementById('emptyState');
1260
+ if (resultsSection) resultsSection.classList.remove('hidden');
1261
+ if (emptyState) emptyState.classList.add('hidden');
1262
+ if (resultsSection) resultsSection.scrollIntoView({ behavior: 'smooth' });
1263
+
1264
+ showToast('Quick preview ready! Click "Generate Full Listing" for complete analysis.', 'success');
1265
+
1266
+ } catch (error) {
1267
+ console.error('Preview error:', error);
1268
+ showToast('Preview failed: ' + error.message, 'error');
1269
+ } finally {
1270
+ stopAnalysisProgress();
1271
+ setTimeout(() => {
1272
+ spinner.classList.add('hidden');
1273
+ dropZone.classList.remove('pointer-events-none');
1274
+ updateAnalysisProgress('Initializing...', 0);
1275
+ }, 300);
1276
+ }
1277
+ }
1278
+ async function callAI(messages, temperature = 0.7) {
1279
+ const provider = localStorage.getItem('ai_provider') || 'openai';
1280
+
1281
+ if (provider === 'deepseek' && window.deepseekService?.isConfigured) {
1282
+ try {
1283
+ const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
1284
+ method: 'POST',
1285
+ headers: {
1286
+ 'Content-Type': 'application/json',
1287
+ 'Authorization': `Bearer ${localStorage.getItem('deepseek_api_key')}`
1288
+ },
1289
+ body: JSON.stringify({
1290
+ model: localStorage.getItem('deepseek_model') || 'deepseek-chat',
1291
+ messages: messages,
1292
+ temperature: temperature,
1293
+ max_tokens: 2000
1294
+ })
1295
+ });
1296
+
1297
+ if (!response.ok) {
1298
+ const error = await response.json();
1299
+ throw new Error(error.error?.message || 'DeepSeek API request failed');
1300
+ }
1301
+
1302
+ const data = await response.json();
1303
+ return data.choices[0].message.content;
1304
+ } catch (error) {
1305
+ showToast(`DeepSeek Error: ${error.message}`, 'error');
1306
+ return null;
1307
+ }
1308
+ } else {
1309
+ // Fallback to OpenAI
1310
+ const apiKey = localStorage.getItem('openai_api_key');
1311
+ const model = localStorage.getItem('openai_model') || 'gpt-4o';
1312
+ const maxTokens = parseInt(localStorage.getItem('openai_max_tokens')) || 2000;
1313
+
1314
+ if (!apiKey) {
1315
+ showToast('OpenAI API key not configured. Go to Settings.', 'error');
1316
+ return null;
1317
+ }
1318
+
1319
+ try {
1320
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
1321
+ method: 'POST',
1322
+ headers: {
1323
+ 'Content-Type': 'application/json',
1324
+ 'Authorization': `Bearer ${apiKey}`
1325
+ },
1326
+ body: JSON.stringify({
1327
+ model: model,
1328
+ messages: messages,
1329
+ temperature: temperature,
1330
+ max_tokens: maxTokens
1331
+ })
1332
+ });
1333
+
1334
+ if (!response.ok) {
1335
+ const error = await response.json();
1336
+ throw new Error(error.error?.message || 'API request failed');
1337
+ }
1338
+
1339
+ const data = await response.json();
1340
+ return data.choices[0].message.content;
1341
+ } catch (error) {
1342
+ showToast(`OpenAI Error: ${error.message}`, 'error');
1343
+ return null;
1344
+ }
1345
+ }
1346
+ }
1347
+ // Legacy alias for backward compatibility
1348
+ async function callOpenAI(messages, temperature = 0.7) {
1349
+ return callAI(messages, temperature);
1350
+ }
1351
+
1352
+ // Delete hosted image from imgBB
1353
+ async function deleteHostedImage(deleteUrl) {
1354
+ if (!deleteUrl) return false;
1355
+
1356
+ try {
1357
+ const response = await fetch(deleteUrl, { method: 'GET' });
1358
+ // imgBB delete URLs work via GET request
1359
+ return response.ok;
1360
+ } catch (error) {
1361
+ console.error('Failed to delete image:', error);
1362
+ return false;
1363
+ }
1364
+ }
1365
+
1366
+ // Get hosted photo URLs for eBay HTML description
1367
+ function getHostedPhotoUrlsForEbay() {
1368
+ return hostedPhotoUrls.map(img => ({
1369
+ full: img.url,
1370
+ display: img.displayUrl || img.url,
1371
+ thumb: img.thumb,
1372
+ medium: img.medium,
1373
+ viewer: img.viewerUrl
1374
+ }));
1375
+ }
1376
+ async function generateListingWithAI() {
1377
+ const artist = document.getElementById('artistInput').value.trim();
1378
+ const title = document.getElementById('titleInput').value.trim();
1379
+ const catNo = document.getElementById('catInput').value.trim();
1380
+ const year = document.getElementById('yearInput').value.trim();
1381
+
1382
+ if (!artist || !title) {
1383
+ showToast('Please enter at least artist and title', 'error');
1384
+ return;
1385
+ }
1386
+
1387
+ const messages = [
1388
+ {
1389
+ role: 'system',
1390
+ content: 'You are a vinyl record eBay listing expert. Generate optimized titles, descriptions, and pricing strategies. Always return JSON format with: titles (array), description (string), condition_notes (string), price_estimate (object with min, max, recommended), and tags (array).'
1391
+ },
1392
+ {
1393
+ role: 'user',
1394
+ content: `Generate an eBay listing for: ${artist} - ${title}${catNo ? ` (Catalog: ${catNo})` : ''}${year ? ` (${year})` : ''}. Include optimized title options, professional HTML description, condition guidance, price estimate in GBP, and relevant tags.`
1395
+ }
1396
+ ];
1397
+ const provider = localStorage.getItem('ai_provider') || 'openai';
1398
+ showToast(`Generating listing with ${provider === 'deepseek' ? 'DeepSeek' : 'OpenAI'}...`, 'success');
1399
+
1400
+ const result = await callAI(messages, 0.7);
1401
+ if (result) {
1402
+ try {
1403
+ const data = JSON.parse(result);
1404
+ // Populate the UI with AI-generated content
1405
+ if (data.titles) {
1406
+ renderTitleOptions(data.titles.map(t => ({
1407
+ text: t.length > 80 ? t.substring(0, 77) + '...' : t,
1408
+ chars: Math.min(t.length, 80),
1409
+ style: 'AI Generated'
1410
+ })));
1411
+ }
1412
+ if (data.description) {
1413
+ document.getElementById('htmlOutput').value = data.description;
1414
+ }
1415
+ if (data.tags) {
1416
+ const tagsContainer = document.getElementById('tagsOutput');
1417
+ tagsContainer.innerHTML = data.tags.map(t => `
1418
+ <span class="px-3 py-1.5 bg-pink-500/10 text-pink-400 rounded-full text-sm border border-pink-500/20">${t}</span>
1419
+ `).join('');
1420
+ }
1421
+ resultsSection.classList.remove('hidden');
1422
+ emptyState.classList.add('hidden');
1423
+ showToast('AI listing generated!', 'success');
1424
+ } catch (e) {
1425
+ // If not valid JSON, treat as plain text description
1426
+ document.getElementById('htmlOutput').value = result;
1427
+ resultsSection.classList.remove('hidden');
1428
+ emptyState.classList.add('hidden');
1429
+ }
1430
+ }
1431
+ }
1432
+
1433
+ function requestHelp() {
1434
+ alert(`VINYL PHOTO GUIDE:
1435
+
1436
+ ESSENTIAL SHOTS (need these):
1437
+ • Front cover - square, no glare, color accurate
1438
+ • Back cover - full frame, readable text
1439
+ • Both labels - close enough to read all text
1440
+ • Deadwax/runout - for pressing identification
1441
+
1442
+ CONDITION SHOTS:
1443
+ • Vinyl in raking light at angle (shows scratches)
1444
+ • Sleeve edges and corners
1445
+ • Any flaws clearly documented
1446
+
1447
+ OPTIONARY BUT HELPFUL:
1448
+ • Inner sleeve condition
1449
+ • Inserts, posters, extras
1450
+ • Hype stickers
1451
+ • Barcode area
1452
+
1453
+ TIPS:
1454
+ - Use natural daylight or 5500K bulbs
1455
+ - Avoid flash directly on glossy sleeves
1456
+ - Include scale reference if unusual size
1457
+ - Photograph flaws honestly - reduces returns`);
1458
+ }
1459
+ function showToast(message, type = 'success') {
1460
+ const existing = document.querySelector('.toast');
1461
+ if (existing) existing.remove();
1462
+
1463
+ const iconMap = {
1464
+ success: 'check',
1465
+ error: 'alert-circle',
1466
+ warning: 'alert-triangle'
1467
+ };
1468
+
1469
+ const colorMap = {
1470
+ success: 'text-green-400',
1471
+ error: 'text-red-400',
1472
+ warning: 'text-yellow-400'
1473
+ };
1474
+
1475
+ const toast = document.createElement('div');
1476
+ toast.className = `toast ${type} flex items-center gap-3`;
1477
+ toast.innerHTML = `
1478
+ <i data-feather="${iconMap[type] || 'info'}" class="w-5 h-5 ${colorMap[type] || 'text-blue-400'}"></i>
1479
+ <span class="text-sm text-gray-200">${message}</span>
1480
+ `;
1481
+ document.body.appendChild(toast);
1482
+ feather.replace();
1483
+
1484
+ requestAnimationFrame(() => toast.classList.add('show'));
1485
+ setTimeout(() => {
1486
+ toast.classList.remove('show');
1487
+ setTimeout(() => toast.remove(), 300);
1488
+ }, 3000);
1489
+ }
1490
+
1491
+ // Cleanup function to delete all hosted images for current listing
1492
+ async function cleanupHostedImages() {
1493
+ if (window.currentListingImages) {
1494
+ for (const img of window.currentListingImages) {
1495
+ if (img.deleteUrl) {
1496
+ await deleteHostedImage(img.deleteUrl);
1497
+ }
1498
+ }
1499
+ window.currentListingImages = [];
1500
+ }
1501
+ }
1502
+ // Initialize
1503
+ document.addEventListener('DOMContentLoaded', () => {
1504
+ console.log('VinylVault Pro initialized');
1505
+
1506
+ // Initialize drop zone
1507
+ initDropZone();
1508
+
1509
+ // Attach event listeners to buttons
1510
+ const generateBtn = document.getElementById('generateListingBtn');
1511
+ if (generateBtn) {
1512
+ generateBtn.addEventListener('click', generateListing);
1513
+ }
1514
+
1515
+ const draftBtn = document.getElementById('draftAnalysisBtn');
1516
+ if (draftBtn) {
1517
+ draftBtn.addEventListener('click', draftAnalysis);
1518
+ }
1519
+
1520
+ const helpBtn = document.getElementById('requestHelpBtn');
1521
+ if (helpBtn) {
1522
+ helpBtn.addEventListener('click', requestHelp);
1523
+ }
1524
+
1525
+ const copyHTMLBtn = document.getElementById('copyHTMLBtn');
1526
+ if (copyHTMLBtn) {
1527
+ copyHTMLBtn.addEventListener('click', copyHTML);
1528
+ }
1529
+
1530
+ const copyTagsBtn = document.getElementById('copyTagsBtn');
1531
+ if (copyTagsBtn) {
1532
+ copyTagsBtn.addEventListener('click', copyTags);
1533
+ }
1534
+
1535
+ const analyzePhotoBtn = document.getElementById('analyzePhotoTypesBtn');
1536
+ if (analyzePhotoBtn) {
1537
+ analyzePhotoBtn.addEventListener('click', analyzePhotoTypes);
1538
+ }
1539
+
1540
+ // Clear Collection Import Banner listeners
1541
+ const clearCollectionBtn = document.querySelector('#collectionBanner button');
1542
+ if (clearCollectionBtn) {
1543
+ clearCollectionBtn.addEventListener('click', clearCollectionImport);
1544
+ }
1545
+
1546
+ // Attach photo input listener
1547
+ const photoInput = document.getElementById('photoInput');
1548
+ if (photoInput) {
1549
+ photoInput.addEventListener('change', handleFileSelect);
1550
+ }
1551
+
1552
+ // Warn about unsaved changes when leaving page with hosted images
1553
+ window.addEventListener('beforeunload', (e) => {
1554
+ if (hostedPhotoUrls.length > 0 && !window.listingPublished) {
1555
+ // Optional: could add cleanup here or warn user
1556
+ }
1557
+ });
1558
+ });
1559
+
1560
+ // Collection Import functions (defined here to avoid reference errors)
1561
+ function clearCollectionImport() {
1562
+ sessionStorage.removeItem('collectionListingRecord');
1563
+ const banner = document.getElementById('collectionBanner');
1564
+ if (banner) {
1565
+ banner.classList.add('hidden');
1566
+ }
1567
+ showToast('Collection import cleared', 'success');
1568
+ }
1569
+
1570
+ function checkCollectionImport() {
1571
+ const urlParams = new URLSearchParams(window.location.search);
1572
+ if (urlParams.get('fromCollection') === 'true') {
1573
+ const recordData = sessionStorage.getItem('collectionListingRecord');
1574
+ if (recordData) {
1575
+ const record = JSON.parse(recordData);
1576
+ populateFieldsFromCollection(record);
1577
+ const banner = document.getElementById('collectionBanner');
1578
+ if (banner) {
1579
+ banner.classList.remove('hidden');
1580
+ }
1581
+ const indicator = document.getElementById('collectionDataIndicator');
1582
+ if (indicator) {
1583
+ indicator.classList.remove('hidden');
1584
+ }
1585
+ }
1586
+ }
1587
+ }
1588
+
1589
+ function populateFieldsFromCollection(record) {
1590
+ if (!record) return;
1591
+
1592
+ const fields = {
1593
+ 'artistInput': record.artist,
1594
+ 'titleInput': record.title,
1595
+ 'catInput': record.catalogueNumber || record.matrixNotes,
1596
+ 'yearInput': record.year,
1597
+ 'costInput': record.purchasePrice,
1598
+ 'daysOwnedInput': record.daysOwned
1599
+ };
1600
+
1601
+ Object.entries(fields).forEach(([fieldId, value]) => {
1602
+ const field = document.getElementById(fieldId);
1603
+ if (field && value) {
1604
+ field.value = value;
1605
+ }
1606
+ });
1607
+
1608
+ // Set conditions if available
1609
+ if (record.conditionVinyl) {
1610
+ const vinylCondition = document.getElementById('vinylConditionInput');
1611
+ if (vinylCondition) vinylCondition.value = record.conditionVinyl;
1612
+ }
1613
+ if (record.conditionSleeve) {
1614
+ const sleeveCondition = document.getElementById('sleeveConditionInput');
1615
+ if (sleeveCondition) sleeveCondition.value = record.conditionSleeve;
1616
+ }
1617
+
1618
+ showToast(`Loaded ${record.artist} - ${record.title} from collection`, 'success');
1619
+ }
1620
+
1621
+ // Call check on load
1622
+ checkCollectionImport();
1623
+ : '€';
1624
+
1625
+ // Mock comp research results
1626
+ const comps = {
1627
+ nm: { low: 45, high: 65, median: 52 },
1628
+ vgplus: { low: 28, high: 42, median: 34 },
1629
+ vg: { low: 15, high: 25, median: 19 }
1630
+ };
1631
+
1632
+ // Calculate recommended price based on goal
1633
+ let recommendedBin, strategy;
1634
+ switch(goal) {
1635
+ case 'quick':
1636
+ recommendedBin = Math.round(comps.vgplus.low * 0.9);
1637
+ strategy = 'BIN + Best Offer (aggressive)';
1638
+ break;
1639
+ case 'max':
1640
+ recommendedBin = Math.round(comps.nm.high * 1.1);
1641
+ strategy = 'BIN only, no offers, long duration';
1642
+ break;
1643
+ default:
1644
+ recommendedBin = comps.vgplus.median;
1645
+ strategy = 'BIN + Best Offer (standard)';
1646
+ }
1647
+
1648
+ // Fee calculation (eBay UK approx)
1649
+ const ebayFeeRate = 0.13; // 13% final value fee
1650
+ const paypalRate = 0.029; // 2.9% + 30p
1651
+ const fixedFee = 0.30;
1652
+ const shippingCost = 4.50; // Estimated
1653
+ const packingCost = 1.50;
1654
+
1655
+ const totalFees = (recommendedBin * ebayFeeRate) + (recommendedBin * paypalRate) + fixedFee;
1656
+ const breakEven = cost + totalFees + shippingCost + packingCost;
1657
+ const safeFloor = Math.ceil(breakEven * 1.05); // 5% buffer
1658
+
1659
+ // Generate titles
1660
+ const baseTitle = `${artist || 'ARTIST'} - ${title || 'TITLE'}`;
1661
+ const titles = generateTitles(baseTitle, catNo, year, goal);
1662
+
1663
+ // Render results
1664
+ renderTitleOptions(titles);
1665
+ renderPricingStrategy(recommendedBin, strategy, comps, currency, goal);
1666
+ renderFeeFloor(cost, totalFees, shippingCost, packingCost, safeFloor, currency);
1667
+ await renderHTMLDescription(data, titles[0]);
1668
+ renderTags(artist, title, catNo, year);
1669
+ renderShotList();
1670
+
1671
+ // Show results
1672
+ resultsSection.classList.remove('hidden');
1673
+ emptyState.classList.add('hidden');
1674
+ resultsSection.scrollIntoView({ behavior: 'smooth' });
1675
+
1676
+ currentAnalysis = {
1677
+ titles, recommendedBin, strategy, breakEven, safeFloor, currency
1678
+ };
1679
+ }
1680
+ function generateTitles(base, catNo, year, goal) {
1681
+ const titles = [];
1682
+ const cat = catNo || 'CAT#';
1683
+ const yr = year || 'YEAR';
1684
+ const country = window.detectedCountry || 'UK';
1685
+ const genre = window.detectedGenre || 'Rock';
1686
+ const format = window.detectedFormat?.includes('7"') ? '7"' : window.detectedFormat?.includes('12"') ? '12"' : 'LP';
1687
+
1688
+ // Option 1: Classic collector focus
1689
+ titles.push(`${base} ${format} ${yr} ${country} 1st Press ${cat} EX/VG+`);
1690
+
1691
+ // Option 2: Condition forward
1692
+ titles.push(`NM! ${base} Original ${yr} Vinyl ${format} ${cat} Nice Copy`);
1693
+
1694
+ // Option 3: Rarity/hype with detected genre
1695
+ titles.push(`${base} ${yr} ${country} Press ${cat} Rare Vintage ${genre} ${format}`);
1696
+
1697
+ // Option 4: Clean searchable
1698
+ titles.push(`${base} Vinyl ${format} ${yr} ${cat} Excellent Condition`);
1699
+
1700
+ // Option 5: Genre tagged
1701
+ titles.push(`${base} ${yr} ${format} ${genre} ${cat} VG+ Plays Great`);
1702
+ return titles.map((t, i) => ({
1703
+ text: t.length > 80 ? t.substring(0, 77) + '...' : t,
1704
+ chars: Math.min(t.length, 80),
1705
+ style: ['Classic Collector', 'Condition Forward', 'Rarity Focus', 'Clean Search', 'Genre Tagged'][i]
1706
+ }));
1707
+ }
1708
+
1709
+ function renderTitleOptions(titles) {
1710
+ const container = document.getElementById('titleOptions');
1711
+ container.innerHTML = titles.map((t, i) => `
1712
+ <div class="title-option ${i === 0 ? 'selected' : ''}" onclick="selectTitle(this, '${t.text.replace(/'/g, "\\'")}')">
1713
+ <span class="char-count">${t.chars}/80</span>
1714
+ <p class="font-medium text-gray-200 pr-16">${t.text}</p>
1715
+ <p class="text-sm text-gray-500 mt-1">${t.style}</p>
1716
+ </div>
1717
+ `).join('');
1718
+ }
1719
+
1720
+ function selectTitle(el, text) {
1721
+ document.querySelectorAll('.title-option').forEach(o => o.classList.remove('selected'));
1722
+ el.classList.add('selected');
1723
+ // Update clipboard copy
1724
+ navigator.clipboard.writeText(text);
1725
+ showToast('Title copied to clipboard!', 'success');
1726
+ }
1727
+
1728
+ function renderPricingStrategy(bin, strategy, comps, currency, goal) {
1729
+ const container = document.getElementById('pricingStrategy');
1730
+
1731
+ const offerSettings = goal === 'max' ? 'Offers: OFF' :
1732
+ `Auto-accept: ${currency}${Math.floor(bin * 0.85)} | Auto-decline: ${currency}${Math.floor(bin * 0.7)}`;
1733
+
1734
+ container.innerHTML = `
1735
+ <div class="pricing-card recommended">
1736
+ <div class="flex items-center gap-2 mb-3">
1737
+ <span class="px-2 py-1 bg-accent/20 text-accent text-xs font-medium rounded">RECOMMENDED</span>
1738
+ </div>
1739
+ <p class="text-3xl font-bold text-white mb-1">${currency}${bin}</p>
1740
+ <p class="text-sm text-gray-400 mb-3">Buy It Now</p>
1741
+ <div class="space-y-2 text-sm">
1742
+ <p class="flex justify-between"><span class="text-gray-500">Strategy:</span> <span class="text-gray-300">${strategy}</span></p>
1743
+ <p class="flex justify-between"><span class="text-gray-500">Best Offer:</span> <span class="text-gray-300">${offerSettings}</span></p>
1744
+ <p class="flex justify-between"><span class="text-gray-500">Duration:</span> <span class="text-gray-300">30 days (GTC)</span></p>
1745
+ </div>
1746
+ </div>
1747
+ <div class="space-y-3">
1748
+ <h4 class="text-sm font-medium text-gray-400 uppercase tracking-wide">Sold Comps by Grade</h4>
1749
+ <div class="space-y-2">
1750
+ <div class="flex justify-between items-center p-3 bg-surface rounded-lg">
1751
+ <span class="text-green-400 font-medium">NM/NM-</span>
1752
+ <span class="text-gray-300">${currency}${comps.nm.low}-${comps.nm.high} <span class="text-gray-500">(med: ${comps.nm.median})</span></span>
1753
+ </div>
1754
+ <div class="flex justify-between items-center p-3 bg-surface rounded-lg border border-accent/30">
1755
+ <span class="text-accent font-medium">VG+/EX</span>
1756
+ <span class="text-gray-300">${currency}${comps.vgplus.low}-${comps.vgplus.high} <span class="text-gray-500">(med: ${comps.vgplus.median})</span></span>
1757
+ </div>
1758
+ <div class="flex justify-between items-center p-3 bg-surface rounded-lg">
1759
+ <span class="text-yellow-400 font-medium">VG/VG+</span>
1760
+ <span class="text-gray-300">${currency}${comps.vg.low}-${comps.vg.high} <span class="text-gray-500">(med: ${comps.vg.median})</span></span>
1761
+ </div>
1762
+ </div>
1763
+ <p class="text-xs text-gray-500 mt-2">Based on last 90 days sold listings, same pressing. Prices exclude postage.</p>
1764
+ </div>
1765
+ `;
1766
+ }
1767
+
1768
+ function renderFeeFloor(cost, fees, shipping, packing, safeFloor, currency) {
1769
+ const container = document.getElementById('feeFloor');
1770
+ container.innerHTML = `
1771
+ <div class="text-center p-4 bg-surface rounded-lg">
1772
+ <p class="text-xs text-gray-500 uppercase mb-1">Your Cost</p>
1773
+ <p class="text-xl font-bold text-gray-300">${currency}${cost.toFixed(2)}</p>
1774
+ </div>
1775
+ <div class="text-center p-4 bg-surface rounded-lg">
1776
+ <p class="text-xs text-gray-500 uppercase mb-1">Est. Fees</p>
1777
+ <p class="text-xl font-bold text-red-400">${currency}${fees.toFixed(2)}</p>
1778
+ <p class="text-xs text-gray-600">~16% total</p>
1779
+ </div>
1780
+ <div class="text-center p-4 bg-surface rounded-lg">
1781
+ <p class="text-xs text-gray-500 uppercase mb-1">Ship + Pack</p>
1782
+ <p class="text-xl font-bold text-gray-300">${currency}${(shipping + packing).toFixed(2)}</p>
1783
+ </div>
1784
+ <div class="text-center p-4 bg-green-500/10 rounded-lg border border-green-500/30">
1785
+ <p class="text-xs text-green-500 uppercase mb-1">Safe Floor Price</p>
1786
+ <p class="text-2xl font-bold text-green-400">${currency}${safeFloor}</p>
1787
+ <p class="text-xs text-green-600/70">Auto-decline below this</p>
1788
+ </div>
1789
+ `;
1790
+ }
1791
+ async function renderHTMLDescription(data, titleObj) {
1792
+ const { artist, title, catNo, year } = data;
1793
+ // Use hosted URL if available, otherwise fallback to local object URL
1794
+ let heroImg = '';
1795
+ let galleryImages = [];
1796
+
1797
+ if (hostedPhotoUrls.length > 0) {
1798
+ heroImg = hostedPhotoUrls[0].displayUrl || hostedPhotoUrls[0].url;
1799
+ galleryImages = hostedPhotoUrls.slice(1).map(img => img.displayUrl || img.url);
1800
+ } else if (uploadedPhotos.length > 0) {
1801
+ heroImg = URL.createObjectURL(uploadedPhotos[0]);
1802
+ galleryImages = uploadedPhotos.slice(1).map((_, i) => URL.createObjectURL(uploadedPhotos[i + 1]));
1803
+ }
1804
+
1805
+ // Use OCR-detected values if available
1806
+ const detectedLabel = window.detectedLabel || '[Verify from photos]';
1807
+ const detectedCountry = window.detectedCountry || 'UK';
1808
+ const detectedFormat = window.detectedFormat || 'LP • 33rpm';
1809
+ const detectedGenre = window.detectedGenre || 'rock';
1810
+ const detectedCondition = window.detectedCondition || 'VG+/VG+';
1811
+ const detectedPressingInfo = window.detectedPressingInfo || '';
1812
+
1813
+ // Fetch tracklist and detailed info from Discogs if available
1814
+ let tracklistHtml = '';
1815
+ let pressingDetailsHtml = '';
1816
+ let provenanceHtml = '';
1817
+
1818
+ if (window.discogsReleaseId && window.discogsService?.key) {
1819
+ try {
1820
+ const discogsData = await window.discogsService.fetchTracklist(window.discogsReleaseId);
1821
+ if (discogsData && discogsData.tracklist) {
1822
+ // Build tracklist HTML
1823
+ const hasSideBreakdown = discogsData.tracklist.some(t => t.position && (t.position.startsWith('A') || t.position.startsWith('B')));
1824
+
1825
+ if (hasSideBreakdown) {
1826
+ // Group by sides
1827
+ const sides = {};
1828
+ discogsData.tracklist.forEach(track => {
1829
+ const side = track.position ? track.position.charAt(0) : 'Other';
1830
+ if (!sides[side]) sides[side] = [];
1831
+ sides[side].push(track);
1832
+ });
1833
+
1834
+ tracklistHtml = Object.entries(sides).map(([side, tracks]) => `
1835
+ <div style="margin-bottom: 16px;">
1836
+ <h4 style="color: #7c3aed; font-size: 13px; font-weight: 600; margin: 0 0 8px 0; text-transform: uppercase; letter-spacing: 0.5px;">Side ${side}</h4>
1837
+ <div style="display: flex; flex-wrap: wrap; gap: 8px;">
1838
+ ${tracks.map(track => `
1839
+ <div style="flex: 1 1 200px; min-width: 200px; display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: #f8fafc; border-radius: 6px; border: 1px solid #e2e8f0;">
1840
+ <span style="color: #1e293b; font-size: 13px;"><strong>${track.position}</strong> ${track.title}</span>
1841
+ ${track.duration ? `<span style="color: #64748b; font-size: 12px; font-family: monospace;">${track.duration}</span>` : ''}
1842
+ </div>
1843
+ `).join('')}
1844
+ </div>
1845
+ </div>
1846
+ `).join('');
1847
+ } else {
1848
+ // Simple list
1849
+ tracklistHtml = `
1850
+ <div style="display: flex; flex-wrap: wrap; gap: 8px;">
1851
+ ${discogsData.tracklist.map(track => `
1852
+ <div style="flex: 1 1 200px; min-width: 200px; display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: #f8fafc; border-radius: 6px; border: 1px solid #e2e8f0;">
1853
+ <span style="color: #1e293b; font-size: 13px;">${track.position ? `<strong>${track.position}</strong> ` : ''}${track.title}</span>
1854
+ ${track.duration ? `<span style="color: #64748b; font-size: 12px; font-family: monospace;">${track.duration}</span>` : ''}
1855
+ </div>
1856
+ `).join('')}
1857
+ </div>
1858
+ `;
1859
+ }
1860
+
1861
+ // Build pressing/variation details
1862
+ const identifiers = discogsData.identifiers || [];
1863
+ const barcodeInfo = identifiers.find(i => i.type === 'Barcode');
1864
+ const matrixInfo = identifiers.filter(i => i.type === 'Matrix / Runout' || i.type === 'Runout');
1865
+ const pressingInfo = identifiers.filter(i => i.type === 'Pressing Plant' || i.type === 'Mastering');
1866
+
1867
+ if (matrixInfo.length > 0 || barcodeInfo || pressingInfo.length > 0) {
1868
+ pressingDetailsHtml = `
1869
+ <div style="background: #f0fdf4; border-left: 4px solid #22c55e; padding: 16px 20px; margin: 24px 0; border-radius: 0 8px 8px 0;">
1870
+ <h3 style="margin: 0 0 12px 0; color: #166534; font-size: 15px; font-weight: 600;">Pressing & Matrix Information</h3>
1871
+ <div style="font-family: monospace; font-size: 13px; line-height: 1.6; color: #15803d;">
1872
+ ${barcodeInfo ? `<p style="margin: 4px 0;"><strong>Barcode:</strong> ${barcodeInfo.value}</p>` : ''}
1873
+ ${matrixInfo.map(m => `<p style="margin: 4px 0;"><strong>${m.type}:</strong> ${m.value}${m.description ? ` <em>(${m.description})</em>` : ''}</p>`).join('')}
1874
+ ${pressingInfo.map(p => `<p style="margin: 4px 0;"><strong>${p.type}:</strong> ${p.value}</p>`).join('')}
1875
+ </div>
1876
+ ${discogsData.notes ? `<p style="margin-top: 12px; padding-top: 12px; border-top: 1px solid #bbf7d0; font-size: 12px; color: #166534; font-style: italic;">${discogsData.notes.substring(0, 300)}${discogsData.notes.length > 300 ? '...' : ''}</p>` : ''}
1877
+ </div>
1878
+ `;
1879
+ }
1880
+
1881
+ // Build provenance data for buyer confidence
1882
+ const companies = discogsData.companies || [];
1883
+ const masteredBy = companies.find(c => c.entity_type_name === 'Mastered At' || c.name.toLowerCase().includes('mastering'));
1884
+ const pressedBy = companies.find(c => c.entity_type_name === 'Pressed By' || c.name.toLowerCase().includes('pressing'));
1885
+ const lacquerCut = companies.find(c => c.entity_type_name === 'Lacquer Cut At');
1886
+
1887
+ if (masteredBy || pressedBy || lacquerCut) {
1888
+ provenanceHtml = `
1889
+ <div style="background: #eff6ff; border: 1px solid #bfdbfe; padding: 16px; margin: 24px 0; border-radius: 8px;">
1890
+ <h3 style="margin: 0 0 12px 0; color: #1e40af; font-size: 14px; font-weight: 600; display: flex; align-items: center; gap: 8px;">
1891
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
1892
+ Provenance & Production
1893
+ </h3>
1894
+ <div style="font-size: 13px; color: #1e3a8a; line-height: 1.6;">
1895
+ ${masteredBy ? `<p style="margin: 4px 0;">✓ Mastered at <strong>${masteredBy.name}</strong></p>` : ''}
1896
+ ${lacquerCut ? `<p style="margin: 4px 0;">✓ Lacquer cut at <strong>${lacquerCut.name}</strong></p>` : ''}
1897
+ ${pressedBy ? `<p style="margin: 4px 0;">✓ Pressed at <strong>${pressedBy.name}</strong></p>` : ''}
1898
+ ${discogsData.num_for_sale ? `<p style="margin: 8px 0 0 0; padding-top: 8px; border-top: 1px solid #bfdbfe; color: #3b82f6; font-size: 12px;">Reference: ${discogsData.num_for_sale} copies currently for sale on Discogs</p>` : ''}
1899
+ </div>
1900
+ </div>
1901
+ `;
1902
+ }
1903
+ }
1904
+ } catch (e) {
1905
+ console.error('Failed to fetch Discogs details for HTML:', e);
1906
+ }
1907
+ }
1908
+
1909
+ // If no tracklist from Discogs, provide placeholder
1910
+ if (!tracklistHtml) {
1911
+ tracklistHtml = `<p style="color: #64748b; font-style: italic;">Tracklist verification recommended. Please compare with Discogs entry for accuracy.</p>`;
1912
+ }
1913
+ const galleryHtml = galleryImages.length > 0 ? `
1914
+ <!-- PHOTO GALLERY -->
1915
+ <div style="margin-bottom: 24px;">
1916
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;">
1917
+ ${galleryImages.map(url => `<img src="${url}" style="width: 100%; height: 150px; object-fit: cover; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);" alt="Record photo">`).join('')}
1918
+ </div>
1919
+ </div>
1920
+ ` : '';
1921
+
1922
+ const html = `<div style="max-width: 800px; margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #333; line-height: 1.6;">
1923
+
1924
+ <!-- HERO IMAGE -->
1925
+ <div style="margin-bottom: 24px;">
1926
+ <img src="${heroImg}" alt="${artist} - ${title}" style="width: 100%; max-width: 600px; display: block; margin: 0 auto; border-radius: 8px; box-shadow: 0 4px 20px rgba(0,0,0,0.15);">
1927
+ </div>
1928
+
1929
+ ${galleryHtml}
1930
+ <!-- BADGES -->
1931
+ <div style="display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-bottom: 24px;">
1932
+ <span style="background: #7c3aed; color: white; padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;">Original ${detectedCountry} Pressing</span>
1933
+ <span style="background: #059669; color: white; padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;">${year || '1970s'}</span>
1934
+ <span style="background: #0891b2; color: white; padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;">${detectedFormat}</span>
1935
+ <span style="background: #d97706; color: white; padding: 6px 16px; border-radius: 20px; font-size: 12px; font-weight: 600; text-transform: uppercase;">${detectedCondition}</span>
1936
+ </div>
1937
+ <!-- AT A GLANCE -->
1938
+ <table style="width: 100%; border-collapse: collapse; margin-bottom: 24px; font-size: 14px;">
1939
+ <tr style="background: #f8fafc;">
1940
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600; width: 140px;">Artist</td>
1941
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${artist || 'See title'}</td>
1942
+ </tr>
1943
+ <tr>
1944
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Title</td>
1945
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${title || 'See title'}</td>
1946
+ </tr>
1947
+ <tr style="background: #f8fafc;">
1948
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Label</td>
1949
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${detectedLabel}</td>
1950
+ </tr>
1951
+ <tr>
1952
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Catalogue</td>
1953
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;"><code style="background: #f1f5f9; padding: 2px 8px; border-radius: 4px;">${catNo || '[See photos]'}</code></td>
1954
+ </tr>
1955
+ <tr style="background: #f8fafc;">
1956
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Country</td>
1957
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${detectedCountry}</td>
1958
+ </tr>
1959
+ <tr>
1960
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0; font-weight: 600;">Year</td>
1961
+ <td style="padding: 12px 16px; border: 1px solid #e2e8f0;">${year || '[Verify]'}</td>
1962
+ </tr>
1963
+ </table>
1964
+
1965
+ <!-- CONDITION -->
1966
+ <div style="background: #fefce8; border-left: 4px solid #eab308; padding: 16px 20px; margin-bottom: 24px; border-radius: 0 8px 8px 0;">
1967
+ <h3 style="margin: 0 0 12px 0; color: #854d0e; font-size: 16px; font-weight: 600;">Condition Report</h3>
1968
+ <div style="display: grid; gap: 12px;">
1969
+ <div>
1970
+ <strong style="color: #713f12;">Vinyl:</strong> <span style="color: #854d0e;">VG+ — Light surface marks, plays cleanly with minimal surface noise. No skips or jumps. [Adjust based on actual inspection]</span>
1971
+ </div>
1972
+ <div>
1973
+ <strong style="color: #713f12;">Sleeve:</strong> <span style="color: #854d0e;">VG+ — Minor edge wear, light ring wear visible under raking light. No splits or writing. [Adjust based on actual inspection]</span>
1974
+ </div>
1975
+ <div>
1976
+ <strong style="color: #713f12;">Inner Sleeve:</strong> <span style="color: #854d0e;">Original paper inner included, small split at bottom seam. [Verify/Adjust]</span>
1977
+ </div>
1978
+ </div>
1979
+ </div>
1980
+ <!-- ABOUT -->
1981
+ <h3 style="color: #1e293b; font-size: 18px; font-weight: 600; margin-bottom: 12px;">About This Release</h3>
1982
+ <p style="margin-bottom: 16px; color: #475569;">${detectedGenre ? `${detectedGenre.charAt(0).toUpperCase() + detectedGenre.slice(1)} release` : 'Vintage vinyl release'}${detectedPressingInfo ? `. Matrix/Runout: ${detectedPressingInfo}` : ''}. [Add accurate description based on verified pressing details. Mention notable features: gatefold, insert, poster, hype sticker, etc.]</p>
1983
+ <!-- TRACKLIST -->
1984
+ <h3 style="color: #1e293b; font-size: 18px; font-weight: 600; margin-bottom: 12px;">Tracklist</h3>
1985
+ <div style="background: #f8fafc; padding: 16px 20px; border-radius: 8px; margin-bottom: 24px;">
1986
+ ${tracklistHtml}
1987
+ </div>
1988
+
1989
+ ${pressingDetailsHtml}
1990
+
1991
+ ${provenanceHtml}
1992
+ <!-- PACKING -->
1993
+ <div style="background: #eff6ff; border-left: 4px solid #3b82f6; padding: 16px 20px; margin-bottom: 24px; border-radius: 0 8px 8px 0;">
1994
+ <h3 style="margin: 0 0 12px 0; color: #1e40af; font-size: 16px; font-weight: 600;">Packing & Postage</h3>
1995
+ <p style="margin: 0 0 12px 0; color: #1e3a8a;">Records are removed from outer sleeves to prevent seam splits during transit. Packed with stiffeners in a dedicated LP mailer. Royal Mail 48 Tracked or courier service.</p>
1996
+ <p style="margin: 0; color: #1e3a8a; font-size: 14px;"><strong>Combined postage:</strong> Discount available for multiple purchases—please request invoice before payment.</p>
1997
+ </div>
1998
+
1999
+ <!-- CTA -->
2000
+ <div style="text-align: center; padding: 24px; background: #f1f5f9; border-radius: 12px;">
2001
+ <p style="margin: 0 0 8px 0; color: #475569; font-weight: 500;">Questions? Need more photos?</p>
2002
+ <p style="margin: 0; color: #64748b; font-size: 14px;">Message me anytime—happy to provide additional angles, audio clips, or pressing details.</p>
2003
+ </div>
2004
+
2005
+ </div>`;
2006
+
2007
+ // Store reference to hosted images for potential cleanup
2008
+ window.currentListingImages = hostedPhotoUrls.map(img => ({
2009
+ url: img.url,
2010
+ deleteUrl: img.deleteUrl
2011
+ }));
2012
+ document.getElementById('htmlOutput').value = html;
2013
+ }
2014
+ function renderTags(artist, title, catNo, year) {
2015
+ const genre = window.detectedGenre || 'rock';
2016
+ const format = window.detectedFormat?.toLowerCase().includes('7"') ? '7 inch' :
2017
+ window.detectedFormat?.toLowerCase().includes('12"') ? '12 inch single' : 'lp';
2018
+ const country = window.detectedCountry?.toLowerCase() || 'uk';
2019
+
2020
+ const tags = [
2021
+ artist || 'vinyl',
2022
+ title || 'record',
2023
+ format,
2024
+ 'vinyl record',
2025
+ 'original pressing',
2026
+ `${country} pressing`,
2027
+ year || 'vintage',
2028
+ catNo || '',
2029
+ genre,
2030
+ genre === 'rock' ? 'prog rock' : genre,
2031
+ genre === 'rock' ? 'psych' : '',
2032
+ 'collector',
2033
+ 'audiophile',
2034
+ format === 'lp' ? '12 inch' : format,
2035
+ '33 rpm',
2036
+ format === 'lp' ? 'album' : 'single',
2037
+ 'used vinyl',
2038
+ 'graded',
2039
+ 'excellent condition',
2040
+ 'rare vinyl',
2041
+ 'classic rock',
2042
+ 'vintage vinyl',
2043
+ 'record collection',
2044
+ 'music',
2045
+ 'audio',
2046
+ window.detectedLabel || ''
2047
+ ].filter(Boolean);
2048
+ const container = document.getElementById('tagsOutput');
2049
+ container.innerHTML = tags.map(t => `
2050
+ <span class="px-3 py-1.5 bg-pink-500/10 text-pink-400 rounded-full text-sm border border-pink-500/20">${t}</span>
2051
+ `).join('');
2052
+ }
2053
+ function renderShotList() {
2054
+ // Map shot types to display info
2055
+ const shotDefinitions = [
2056
+ { id: 'front', name: 'Front cover (square, well-lit)', critical: true },
2057
+ { id: 'back', name: 'Back cover (full shot)', critical: true },
2058
+ { id: 'spine', name: 'Spine (readable text)', critical: true },
2059
+ { id: 'label_a', name: 'Label Side A (close, legible)', critical: true },
2060
+ { id: 'label_b', name: 'Label Side B (close, legible)', critical: true },
2061
+ { id: 'deadwax', name: 'Deadwax/runout grooves', critical: true },
2062
+ { id: 'inner', name: 'Inner sleeve (both sides)', critical: false },
2063
+ { id: 'insert', name: 'Insert/poster if included', critical: false },
2064
+ { id: 'hype', name: 'Hype sticker (if present)', critical: false },
2065
+ { id: 'vinyl', name: 'Vinyl in raking light (flaws)', critical: true },
2066
+ { id: 'corners', name: 'Sleeve corners/edges detail', critical: false },
2067
+ { id: 'barcode', name: 'Barcode area', critical: false }
2068
+ ];
2069
+
2070
+ // Check if we have any photos at all
2071
+ const hasPhotos = uploadedPhotos.length > 0;
2072
+
2073
+ const container = document.getElementById('shotList');
2074
+ container.innerHTML = shotDefinitions.map(shot => {
2075
+ const have = detectedPhotoTypes.has(shot.id) || (shot.id === 'front' && hasPhotos) || (shot.id === 'back' && uploadedPhotos.length > 1);
2076
+ const statusClass = have ? 'completed' : shot.critical ? 'missing' : '';
2077
+ const iconColor = have ? 'text-green-500' : shot.critical ? 'text-yellow-500' : 'text-gray-500';
2078
+ const textClass = have ? 'text-gray-400 line-through' : 'text-gray-300';
2079
+ const icon = have ? 'check-circle' : shot.critical ? 'alert-circle' : 'circle';
2080
+
2081
+ return `
2082
+ <div class="shot-item ${statusClass}">
2083
+ <i data-feather="${icon}"
2084
+ class="w-5 h-5 ${iconColor} flex-shrink-0"></i>
2085
+ <span class="text-sm ${textClass}">${shot.name}</span>
2086
+ ${shot.critical && !have ? '<span class="ml-auto text-xs text-yellow-500 font-medium">CRITICAL</span>' : ''}
2087
+ </div>
2088
+ `}).join('');
2089
+ feather.replace();
2090
+ }
2091
+ function copyHTML() {
2092
+ const html = document.getElementById('htmlOutput');
2093
+ html.select();
2094
+ document.execCommand('copy');
2095
+ showToast('HTML copied to clipboard!', 'success');
2096
+ }
2097
+
2098
+ function copyTags() {
2099
+ const tags = Array.from(document.querySelectorAll('#tagsOutput span')).map(s => s.textContent).join(', ');
2100
+ navigator.clipboard.writeText(tags);
2101
+ showToast('Tags copied to clipboard!', 'success');
2102
+ }
2103
+ async function draftAnalysis() {
2104
+ if (uploadedPhotos.length === 0) {
2105
+ showToast('Upload photos first for preview', 'error');
2106
+ return;
2107
+ }
2108
+
2109
+ const artist = document.getElementById('artistInput').value.trim();
2110
+ const title = document.getElementById('titleInput').value.trim();
2111
+
2112
+ // Show loading state
2113
+ const dropZone = document.getElementById('dropZone');
2114
+ const spinner = document.getElementById('uploadSpinner');
2115
+ spinner.classList.remove('hidden');
2116
+ dropZone.classList.add('pointer-events-none');
2117
+ startAnalysisProgressSimulation();
2118
+
2119
+ try {
2120
+ // Try OCR/AI analysis if available
2121
+ const service = getAIService();
2122
+ let ocrResult = null;
2123
+
2124
+ if (service && service.apiKey && uploadedPhotos.length > 0) {
2125
+ try {
2126
+ ocrResult = await service.analyzeRecordImages(uploadedPhotos.slice(0, 2)); // Limit to 2 photos for speed
2127
+ populateFieldsFromOCR(ocrResult);
2128
+ } catch (e) {
2129
+ console.log('Preview OCR failed:', e);
2130
+ }
2131
+ }
2132
+
2133
+ // Generate quick preview results
2134
+ const catNo = document.getElementById('catInput').value.trim() || ocrResult?.catalogueNumber || '';
2135
+ const year = document.getElementById('yearInput').value.trim() || ocrResult?.year || '';
2136
+ const detectedArtist = artist || ocrResult?.artist || 'Unknown Artist';
2137
+ const detectedTitle = title || ocrResult?.title || 'Unknown Title';
2138
+
2139
+ const baseTitle = `${detectedArtist} - ${detectedTitle}`;
2140
+
2141
+ // Generate quick titles
2142
+ const quickTitles = [
2143
+ `${baseTitle} ${year ? `(${year})` : ''} ${catNo} VG+`.substring(0, 80),
2144
+ `${baseTitle} Original Pressing Vinyl LP`.substring(0, 80),
2145
+ `${detectedArtist} ${detectedTitle} ${catNo || 'LP'}`.substring(0, 80)
2146
+ ].map((t, i) => ({
2147
+ text: t,
2148
+ chars: t.length,
2149
+ style: ['Quick', 'Standard', 'Compact'][i]
2150
+ }));
2151
+
2152
+ // Quick pricing estimate based on condition
2153
+ const cost = parseFloat(document.getElementById('costInput').value) || 10;
2154
+ const vinylCond = document.getElementById('vinylConditionInput').value;
2155
+ const sleeveCond = document.getElementById('sleeveConditionInput').value;
2156
+
2157
+ const conditionMultipliers = { 'M': 3, 'NM': 2.5, 'VG+': 1.8, 'VG': 1.2, 'G+': 0.8, 'G': 0.5 };
2158
+ const condMult = (conditionMultipliers[vinylCond] || 1) * 0.7 + (conditionMultipliers[sleeveCond] || 1) * 0.3;
2159
+
2160
+ const estimatedValue = Math.round(cost * Math.max(condMult, 1.5));
2161
+ const suggestedPrice = Math.round(estimatedValue * 0.9);
2162
+
2163
+ // Render preview results
2164
+ renderTitleOptions(quickTitles);
2165
+
2166
+ // Quick pricing card
2167
+ document.getElementById('pricingStrategy').innerHTML = `
2168
+ <div class="pricing-card recommended">
2169
+ <div class="flex items-center gap-2 mb-3">
2170
+ <span class="px-2 py-1 bg-accent/20 text-accent text-xs font-medium rounded">QUICK ESTIMATE</span>
2171
+ </div>
2172
+ <p class="text-3xl font-bold text-white mb-1">£${suggestedPrice}</p>
2173
+ <p class="text-sm text-gray-400 mb-3">Suggested Buy It Now</p>
2174
+ <div class="space-y-2 text-sm">
2175
+ <p class="flex justify-between"><span class="text-gray-500">Est. Value:</span> <span class="text-gray-300">£${estimatedValue}</span></p>
2176
+ <p class="flex justify-between"><span class="text-gray-500">Your Cost:</span> <span class="text-gray-300">£${cost.toFixed(2)}</span></p>
2177
+ <p class="flex justify-between"><span class="text-gray-500">Condition:</span> <span class="text-gray-300">${vinylCond}/${sleeveCond}</span></p>
2178
+ </div>
2179
+ </div>
2180
+ <div class="space-y-3">
2181
+ <h4 class="text-sm font-medium text-gray-400 uppercase tracking-wide">Preview Notes</h4>
2182
+ <div class="p-3 bg-surface rounded-lg text-sm text-gray-400">
2183
+ ${ocrResult ?
2184
+ `<p class="text-green-400 mb-2">✓ AI detected information from photos</p>` :
2185
+ `<p class="text-yellow-400 mb-2">⚠ Add API key in Settings for auto-detection</p>`
2186
+ }
2187
+ <p>This is a quick estimate based on your cost and condition. Run "Generate Full Listing" for complete market analysis, sold comps, and optimized pricing.</p>
2188
+ </div>
2189
+ ${ocrResult ? `
2190
+ <div class="p-3 bg-green-500/10 border border-green-500/20 rounded-lg">
2191
+ <p class="text-xs text-green-400 font-medium mb-1">Detected from photos:</p>
2192
+ <ul class="text-xs text-gray-400 space-y-1">
2193
+ ${ocrResult.artist ? `<li>• Artist: ${ocrResult.artist}</li>` : ''}
2194
+ ${ocrResult.title ? `<li>• Title: ${ocrResult.title}</li>` : ''}
2195
+ ${ocrResult.catalogueNumber ? `<li>• Cat#: ${ocrResult.catalogueNumber}</li>` : ''}
2196
+ ${ocrResult.year ? `<li>• Year: ${ocrResult.year}</li>` : ''}
2197
+ </ul>
2198
+ </div>
2199
+ ` : ''}
2200
+ </div>
2201
+ `;
2202
+
2203
+ // Simple fee floor
2204
+ const fees = suggestedPrice * 0.16;
2205
+ const safeFloor = Math.ceil(cost + fees + 6);
2206
+
2207
+ document.getElementById('feeFloor').innerHTML = `
2208
+ <div class="text-center p-4 bg-surface rounded-lg">
2209
+ <p class="text-xs text-gray-500 uppercase mb-1">Your Cost</p>
2210
+ <p class="text-xl font-bold text-gray-300">£${cost.toFixed(2)}</p>
2211
+ </div>
2212
+ <div class="text-center p-4 bg-surface rounded-lg">
2213
+ <p class="text-xs text-gray-500 uppercase mb-1">Est. Fees</p>
2214
+ <p class="text-xl font-bold text-red-400">£${fees.toFixed(2)}</p>
2215
+ </div>
2216
+ <div class="text-center p-4 bg-surface rounded-lg">
2217
+ <p class="text-xs text-gray-500 uppercase mb-1">Ship + Pack</p>
2218
+ <p class="text-xl font-bold text-gray-300">£6.00</p>
2219
+ </div>
2220
+ <div class="text-center p-4 bg-green-500/10 rounded-lg border border-green-500/30">
2221
+ <p class="text-xs text-green-500 uppercase mb-1">Safe Floor</p>
2222
+ <p class="text-2xl font-bold text-green-400">£${safeFloor}</p>
2223
+ </div>
2224
+ `;
2225
+
2226
+ // Preview HTML description
2227
+ const previewHtml = `<!-- QUICK PREVIEW - Generated by VinylVault Pro -->
2228
+ <div style="max-width: 700px; margin: 0 auto; font-family: sans-serif;">
2229
+ <h2 style="color: #333;">${detectedArtist} - ${detectedTitle}</h2>
2230
+ ${year ? `<p><strong>Year:</strong> ${year}</p>` : ''}
2231
+ ${catNo ? `<p><strong>Catalogue #:</strong> ${catNo}</p>` : ''}
2232
+ <p><strong>Condition:</strong> Vinyl ${vinylCond}, Sleeve ${sleeveCond}</p>
2233
+ <hr style="margin: 20px 0;">
2234
+ <p style="color: #666;">[Full description will be generated with complete market analysis]</p>
2235
+ </div>`;
2236
+
2237
+ document.getElementById('htmlOutput').value = previewHtml;
2238
+
2239
+ // Preview tags
2240
+ const previewTags = [
2241
+ detectedArtist,
2242
+ detectedTitle,
2243
+ 'vinyl',
2244
+ 'record',
2245
+ vinylCond,
2246
+ 'lp',
2247
+ year || 'vintage'
2248
+ ].filter(Boolean);
2249
+
2250
+ document.getElementById('tagsOutput').innerHTML = previewTags.map(t => `
2251
+ <span class="px-3 py-1.5 bg-pink-500/10 text-pink-400 rounded-full text-sm border border-pink-500/20">${t}</span>
2252
+ `).join('');
2253
+
2254
+ // Update shot list
2255
+ renderShotList();
2256
+
2257
+ // Show results
2258
+ resultsSection.classList.remove('hidden');
2259
+ emptyState.classList.add('hidden');
2260
+ resultsSection.scrollIntoView({ behavior: 'smooth' });
2261
+
2262
+ showToast('Quick preview ready! Click "Generate Full Listing" for complete analysis.', 'success');
2263
+
2264
+ } catch (error) {
2265
+ console.error('Preview error:', error);
2266
+ showToast('Preview failed: ' + error.message, 'error');
2267
+ } finally {
2268
+ stopAnalysisProgress();
2269
+ setTimeout(() => {
2270
+ spinner.classList.add('hidden');
2271
+ dropZone.classList.remove('pointer-events-none');
2272
+ updateAnalysisProgress('Initializing...', 0);
2273
+ }, 300);
2274
+ }
2275
+ }
2276
+ async function callAI(messages, temperature = 0.7) {
2277
+ const provider = localStorage.getItem('ai_provider') || 'openai';
2278
+
2279
+ if (provider === 'deepseek' && window.deepseekService?.isConfigured) {
2280
+ try {
2281
+ const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
2282
+ method: 'POST',
2283
+ headers: {
2284
+ 'Content-Type': 'application/json',
2285
+ 'Authorization': `Bearer ${localStorage.getItem('deepseek_api_key')}`
2286
+ },
2287
+ body: JSON.stringify({
2288
+ model: localStorage.getItem('deepseek_model') || 'deepseek-chat',
2289
+ messages: messages,
2290
+ temperature: temperature,
2291
+ max_tokens: 2000
2292
+ })
2293
+ });
2294
+
2295
+ if (!response.ok) {
2296
+ const error = await response.json();
2297
+ throw new Error(error.error?.message || 'DeepSeek API request failed');
2298
+ }
2299
+
2300
+ const data = await response.json();
2301
+ return data.choices[0].message.content;
2302
+ } catch (error) {
2303
+ showToast(`DeepSeek Error: ${error.message}`, 'error');
2304
+ return null;
2305
+ }
2306
+ } else {
2307
+ // Fallback to OpenAI
2308
+ const apiKey = localStorage.getItem('openai_api_key');
2309
+ const model = localStorage.getItem('openai_model') || 'gpt-4o';
2310
+ const maxTokens = parseInt(localStorage.getItem('openai_max_tokens')) || 2000;
2311
+
2312
+ if (!apiKey) {
2313
+ showToast('OpenAI API key not configured. Go to Settings.', 'error');
2314
+ return null;
2315
+ }
2316
+
2317
+ try {
2318
+ const response = await fetch('https://api.openai.com/v1/chat/completions', {
2319
+ method: 'POST',
2320
+ headers: {
2321
+ 'Content-Type': 'application/json',
2322
+ 'Authorization': `Bearer ${apiKey}`
2323
+ },
2324
+ body: JSON.stringify({
2325
+ model: model,
2326
+ messages: messages,
2327
+ temperature: temperature,
2328
+ max_tokens: maxTokens
2329
+ })
2330
+ });
2331
+
2332
+ if (!response.ok) {
2333
+ const error = await response.json();
2334
+ throw new Error(error.error?.message || 'API request failed');
2335
+ }
2336
+
2337
+ const data = await response.json();
2338
+ return data.choices[0].message.content;
2339
+ } catch (error) {
2340
+ showToast(`OpenAI Error: ${error.message}`, 'error');
2341
+ return null;
2342
+ }
2343
+ }
2344
+ }
2345
+ // Legacy alias for backward compatibility
2346
+ async function callOpenAI(messages, temperature = 0.7) {
2347
+ return callAI(messages, temperature);
2348
+ }
2349
+
2350
+ // Delete hosted image from imgBB
2351
+ async function deleteHostedImage(deleteUrl) {
2352
+ if (!deleteUrl) return false;
2353
+
2354
+ try {
2355
+ const response = await fetch(deleteUrl, { method: 'GET' });
2356
+ // imgBB delete URLs work via GET request
2357
+ return response.ok;
2358
+ } catch (error) {
2359
+ console.error('Failed to delete image:', error);
2360
+ return false;
2361
+ }
2362
+ }
2363
+
2364
+ // Get hosted photo URLs for eBay HTML description
2365
+ function getHostedPhotoUrlsForEbay() {
2366
+ return hostedPhotoUrls.map(img => ({
2367
+ full: img.url,
2368
+ display: img.displayUrl || img.url,
2369
+ thumb: img.thumb,
2370
+ medium: img.medium,
2371
+ viewer: img.viewerUrl
2372
+ }));
2373
+ }
2374
+ async function generateListingWithAI() {
2375
+ const artist = document.getElementById('artistInput').value.trim();
2376
+ const title = document.getElementById('titleInput').value.trim();
2377
+ const catNo = document.getElementById('catInput').value.trim();
2378
+ const year = document.getElementById('yearInput').value.trim();
2379
+
2380
+ if (!artist || !title) {
2381
+ showToast('Please enter at least artist and title', 'error');
2382
+ return;
2383
+ }
2384
+
2385
+ const messages = [
2386
+ {
2387
+ role: 'system',
2388
+ content: 'You are a vinyl record eBay listing expert. Generate optimized titles, descriptions, and pricing strategies. Always return JSON format with: titles (array), description (string), condition_notes (string), price_estimate (object with min, max, recommended), and tags (array).'
2389
+ },
2390
+ {
2391
+ role: 'user',
2392
+ content: `Generate an eBay listing for: ${artist} - ${title}${catNo ? ` (Catalog: ${catNo})` : ''}${year ? ` (${year})` : ''}. Include optimized title options, professional HTML description, condition guidance, price estimate in GBP, and relevant tags.`
2393
+ }
2394
+ ];
2395
+ const provider = localStorage.getItem('ai_provider') || 'openai';
2396
+ showToast(`Generating listing with ${provider === 'deepseek' ? 'DeepSeek' : 'OpenAI'}...`, 'success');
2397
+
2398
+ const result = await callAI(messages, 0.7);
2399
+ if (result) {
2400
+ try {
2401
+ const data = JSON.parse(result);
2402
+ // Populate the UI with AI-generated content
2403
+ if (data.titles) {
2404
+ renderTitleOptions(data.titles.map(t => ({
2405
+ text: t.length > 80 ? t.substring(0, 77) + '...' : t,
2406
+ chars: Math.min(t.length, 80),
2407
+ style: 'AI Generated'
2408
+ })));
2409
+ }
2410
+ if (data.description) {
2411
+ document.getElementById('htmlOutput').value = data.description;
2412
+ }
2413
+ if (data.tags) {
2414
+ const tagsContainer = document.getElementById('tagsOutput');
2415
+ tagsContainer.innerHTML = data.tags.map(t => `
2416
+ <span class="px-3 py-1.5 bg-pink-500/10 text-pink-400 rounded-full text-sm border border-pink-500/20">${t}</span>
2417
+ `).join('');
2418
+ }
2419
+ resultsSection.classList.remove('hidden');
2420
+ emptyState.classList.add('hidden');
2421
+ showToast('AI listing generated!', 'success');
2422
+ } catch (e) {
2423
+ // If not valid JSON, treat as plain text description
2424
+ document.getElementById('htmlOutput').value = result;
2425
+ resultsSection.classList.remove('hidden');
2426
+ emptyState.classList.add('hidden');
2427
+ }
2428
+ }
2429
+ }
2430
+
2431
+ function requestHelp() {
2432
+ alert(`VINYL PHOTO GUIDE:
2433
+
2434
+ ESSENTIAL SHOTS (need these):
2435
+ • Front cover - square, no glare, color accurate
2436
+ • Back cover - full frame, readable text
2437
+ • Both labels - close enough to read all text
2438
+ • Deadwax/runout - for pressing identification
2439
+
2440
+ CONDITION SHOTS:
2441
+ • Vinyl in raking light at angle (shows scratches)
2442
+ • Sleeve edges and corners
2443
+ • Any flaws clearly documented
2444
+
2445
+ OPTIONARY BUT HELPFUL:
2446
+ • Inner sleeve condition
2447
+ • Inserts, posters, extras
2448
+ • Hype stickers
2449
+ • Barcode area
2450
+
2451
+ TIPS:
2452
+ - Use natural daylight or 5500K bulbs
2453
+ - Avoid flash directly on glossy sleeves
2454
+ - Include scale reference if unusual size
2455
+ - Photograph flaws honestly - reduces returns`);
2456
+ }
2457
+ function showToast(message, type = 'success') {
2458
+ const existing = document.querySelector('.toast');
2459
+ if (existing) existing.remove();
2460
+
2461
+ const iconMap = {
2462
+ success: 'check',
2463
+ error: 'alert-circle',
2464
+ warning: 'alert-triangle'
2465
+ };
2466
+
2467
+ const colorMap = {
2468
+ success: 'text-green-400',
2469
+ error: 'text-red-400',
2470
+ warning: 'text-yellow-400'
2471
+ };
2472
+
2473
+ const toast = document.createElement('div');
2474
+ toast.className = `toast ${type} flex items-center gap-3`;
2475
+ toast.innerHTML = `
2476
+ <i data-feather="${iconMap[type] || 'info'}" class="w-5 h-5 ${colorMap[type] || 'text-blue-400'}"></i>
2477
+ <span class="text-sm text-gray-200">${message}</span>
2478
+ `;
2479
+ document.body.appendChild(toast);
2480
+ feather.replace();
2481
+
2482
+ requestAnimationFrame(() => toast.classList.add('show'));
2483
+ setTimeout(() => {
2484
+ toast.classList.remove('show');
2485
+ setTimeout(() => toast.remove(), 300);
2486
+ }, 3000);
2487
+ }
2488
+
2489
+ // Cleanup function to delete all hosted images for current listing
2490
+ async function cleanupHostedImages() {
2491
+ if (window.currentListingImages) {
2492
+ for (const img of window.currentListingImages) {
2493
+ if (img.deleteUrl) {
2494
+ await deleteHostedImage(img.deleteUrl);
2495
+ }
2496
+ }
2497
+ window.currentListingImages = [];
2498
+ }
2499
+ }
2500
+ // Initialize
2501
+ document.addEventListener('DOMContentLoaded', () => {
2502
+ console.log('VinylVault Pro initialized');
2503
+
2504
+ // Initialize drop zone
2505
+ initDropZone();
2506
+
2507
+ // Warn about unsaved changes when leaving page with hosted images
2508
+ window.addEventListener('beforeunload', (e) => {
2509
+ if (hostedPhotoUrls.length > 0 && !window.listingPublished) {
2510
+ // Optional: could add cleanup here or warn user
2511
+ }
2512
+ });
2513
+ });
2514
+ : '€';
2515
+
2516
+ // Mock comp research results
2517
+ const comps = {
2518
+ nm: { low: 45, high: 65, median: 52 },
2519
+ vgplus: { low: 28, high: 42, median: 34 },
2520
+ vg: { low: 15, high: 25, median: 19 }
2521
+ };
2522
+
2523
+ // Calculate recommended price based on goal
2524
+ let recommendedBin, strategy;
2525
+ switch(goal) {
2526
+ case 'quick':
2527
+ recommendedBin = Math.round(comps.vgplus.low * 0.9);
2528
+ strategy = 'BIN + Best Offer (aggressive)';
2529
+ break;
2530
+ case 'max':
2531
+ recommendedBin = Math.round(comps.nm.high * 1.1);
2532
+ strategy = 'BIN only, no offers, long duration';
2533
+ break;
2534
+ default:
2535
+ recommendedBin = comps.vgplus.median;
2536
+ strategy = 'BIN + Best Offer (standard)';
2537
+ }
2538
+
2539
+ // Fee calculation (eBay UK approx)
2540
+ const ebayFeeRate = 0.13; // 13% final value fee
2541
+ const paypalRate = 0.029; // 2.9% + 30p
2542
+ const fixedFee = 0.30;
2543
+ const shippingCost = 4.50; // Estimated
2544
+ const packingCost = 1.50;
2545
+
2546
+ const totalFees = (recommendedBin * ebayFeeRate) + (recommendedBin * paypalRate) + fixedFee;
2547
+ const breakEven = cost + totalFees + shippingCost + packingCost;
2548
+ const safeFloor = Math.ceil(breakEven * 1.05); // 5% buffer
2549
+
2550
+ // Generate titles
2551
+ const baseTitle = `${artist || 'ARTIST'} - ${title || 'TITLE'}`;
2552
+ const titles = generateTitles(baseTitle, catNo, year, goal);
2553
+
2554
+ // Render results
2555
+ renderTitleOptions(titles);
2556
+ renderPricingStrategy(recommendedBin, strategy, comps, currency, goal);
2557
+ renderFeeFloor(cost, totalFees, shippingCost, packingCost, safeFloor, currency);
2558
+ await renderHTMLDescription(data, titles[0]);
2559
+ renderTags(artist, title, catNo, year);
2560
+ renderShotList();
2561
+
2562
+ // Show results
2563
+ resultsSection.classList.remove('hidden');
2564
+ emptyState.classList.add('hidden');
2565
+ resultsSection.scrollIntoView({ behavior: 'smooth' });
2566
+
2567
+ currentAnalysis = {
2568
+ titles, recommendedBin, strategy, breakEven, safeFloor, currency
2569
+ };
2570
+ }
2571
+
2572
+ function generateTitles(base, catNo, year, goal) {
2573
  const titles = [];
2574
  const cat = catNo || 'CAT#';
2575
  const yr = year || 'YEAR';
 
2591
 
2592
  // Option 5: Genre tagged
2593
  titles.push(`${base} ${yr} ${format} ${genre} ${cat} VG+ Plays Great`);
2594
+
2595
+ return titles.map((t, i) => ({
2596
  text: t.length > 80 ? t.substring(0, 77) + '...' : t,
2597
  chars: Math.min(t.length, 80),
2598
  style: ['Classic Collector', 'Condition Forward', 'Rarity Focus', 'Clean Search', 'Genre Tagged'][i]
2599
  }));
2600
  }
 
2601
  function renderTitleOptions(titles) {
2602
  const container = document.getElementById('titleOptions');
2603
  container.innerHTML = titles.map((t, i) => `