Download index.html from SpectrumInfinite/spectrum-infinite-flyerforge: direct link, hf CLI and curl.
- Browser
- Download file 32.2 kB
-
https://huggingface.co/spaces/SpectrumInfinite/spectrum-infinite-flyerforge/resolve/main/index.html
- Command line
-
hf download hf://spaces/SpectrumInfinite/spectrum-infinite-flyerforge/index.html
-
curl -L -o index.html https://huggingface.co/spaces/SpectrumInfinite/spectrum-infinite-flyerforge/resolve/main/index.html
32.2 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Product Card</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| <style> | |
| .animate-pulse { | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .line-clamp-1 { | |
| display: -webkit-box; | |
| -webkit-line-clamp: 1; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .line-clamp-4 { | |
| display: -webkit-box; | |
| -webkit-line-clamp: 4; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .aspect-video { | |
| position: relative; | |
| padding-bottom: 56.25%; /* 16:9 aspect ratio */ | |
| } | |
| .aspect-video > * { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 p-6"> | |
| <div id="product-card-container" class="max-w-md mx-auto"> | |
| <!-- Product card will be rendered here --> | |
| </div> | |
| <script> | |
| // Initialize Lucide icons | |
| lucide.createIcons(); | |
| // Sample product data | |
| const sampleProduct = { | |
| id: 'prod-123', | |
| tempId: 'temp-123', | |
| productName: 'Premium Carabiner', | |
| productNumber: '#2065', | |
| description: 'High-quality aluminum carabiner with safety lock. Perfect for climbing and outdoor activities.', | |
| price: { | |
| min: 12.99, | |
| currency: 'USD', | |
| minimumQuantity: 10 | |
| }, | |
| heroImage: 'https://images.unsplash.com/photo-1524593689594-aae2f26b75ab?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=800&q=80', | |
| availableColors: ['Black', 'Blue', 'Red'], | |
| status: 'success', | |
| scrapeError: null, | |
| pricingTableHTML: '<table>...</table>' | |
| }; | |
| // Mock store functions | |
| const mockStore = { | |
| updateProduct: (id, updates) => { | |
| console.log('Updating product:', id, updates); | |
| // In a real app, this would update the Zustand store | |
| Object.assign(sampleProduct, updates); | |
| renderProductCard(sampleProduct); | |
| } | |
| }; | |
| // Format price helper | |
| function formatPrice(priceInfo) { | |
| if (!priceInfo) return ''; | |
| return new Intl.NumberFormat('en-US', { | |
| style: 'currency', | |
| currency: priceInfo.currency || 'USD' | |
| }).format(priceInfo.min); | |
| } | |
| // Toast notification | |
| function showToast(title, description, variant = 'default') { | |
| const toast = document.createElement('div'); | |
| toast.className = `fixed top-4 right-4 p-4 rounded-md shadow-lg ${ | |
| variant === 'destructive' ? 'bg-red-100 text-red-900' : 'bg-green-100 text-green-900' | |
| }`; | |
| toast.innerHTML = ` | |
| <div class="font-medium">${title}</div> | |
| <div class="text-sm">${description}</div> | |
| `; | |
| document.body.appendChild(toast); | |
| setTimeout(() => toast.remove(), 3000); | |
| } | |
| // Product Card Component | |
| function ProductCard({ product, onUploadClick, onRemoveClick }) { | |
| const [isEditing, setIsEditing] = useState(false); | |
| const [manualData, setManualData] = useState({ | |
| productName: product.productName || '', | |
| productNumber: product.productNumber || '', | |
| description: product.description || '', | |
| startingPrice: typeof product.price === 'object' && product.price?.min ? product.price.min.toString() : '', | |
| currency: typeof product.price === 'object' && product.price?.currency ? product.price.currency : 'USD', | |
| minimumQuantity: typeof product.price === 'object' && product.price?.minimumQuantity ? product.price.minimumQuantity.toString() : '', | |
| availableColors: product.availableColors?.join(', ') || '', | |
| }); | |
| // Effect to update manualData if product changes | |
| useEffect(() => { | |
| if (!isEditing) { | |
| setManualData({ | |
| productName: product.productName || '', | |
| productNumber: product.productNumber || '', | |
| description: product.description || '', | |
| startingPrice: typeof product.price === 'object' && product.price?.min ? product.price.min.toString() : '', | |
| currency: typeof product.price === 'object' && product.price?.currency ? product.price.currency : 'USD', | |
| minimumQuantity: typeof product.price === 'object' && product.price?.minimumQuantity ? product.price.minimumQuantity.toString() : '', | |
| availableColors: product.availableColors?.join(', ') || '', | |
| }); | |
| } | |
| }, [product, isEditing]); | |
| const isLoading = product.status === 'pending' || product.status === 'scraping'; | |
| const isError = product.status === 'error'; | |
| const needsImageUpload = product.status === 'image_upload_required'; | |
| const isSuccess = product.status === 'success'; | |
| const handleEditToggle = () => { | |
| if (!isEditing) { | |
| setManualData({ | |
| productName: product.productName || '', | |
| productNumber: product.productNumber || '', | |
| description: product.description || '', | |
| startingPrice: typeof product.price === 'object' && product.price?.min ? product.price.min.toString() : '', | |
| currency: typeof product.price === 'object' && product.price?.currency ? product.price.currency : 'USD', | |
| minimumQuantity: typeof product.price === 'object' && product.price?.minimumQuantity ? product.price.minimumQuantity.toString() : '', | |
| availableColors: product.availableColors?.join(', ') || '', | |
| }); | |
| } | |
| setIsEditing(!isEditing); | |
| }; | |
| const handleManualInputChange = (e) => { | |
| const { name, value } = e.target; | |
| setManualData(prev => ({ ...prev, [name]: value })); | |
| }; | |
| const handleManualSave = () => { | |
| // Basic validation | |
| if (!manualData.productName || !manualData.productNumber || !manualData.startingPrice || !manualData.availableColors) { | |
| showToast("Missing Information", "Please fill in Product Name, Number, Starting Price, and Colors.", "destructive"); | |
| return; | |
| } | |
| const priceValue = parseFloat(manualData.startingPrice); | |
| const minQtyValue = parseInt(manualData.minimumQuantity, 10); | |
| const priceInfo = !isNaN(priceValue) ? { | |
| min: priceValue, | |
| currency: manualData.currency || 'USD', | |
| minimumQuantity: !isNaN(minQtyValue) ? minQtyValue : undefined | |
| } : null; | |
| const colorsArray = manualData.availableColors.split(',').map(color => color.trim()).filter(Boolean); | |
| mockStore.updateProduct(product.id || product.tempId, { | |
| productName: manualData.productName || undefined, | |
| productNumber: manualData.productNumber || undefined, | |
| description: manualData.description || undefined, | |
| price: priceInfo, | |
| availableColors: colorsArray.length > 0 ? colorsArray : undefined, | |
| status: (isError || needsImageUpload) | |
| ? (product.heroImage && manualData.productName && manualData.productNumber && priceInfo && colorsArray.length > 0 ? 'success' : 'image_upload_required') | |
| : (product.heroImage ? 'success' : 'image_upload_required'), | |
| scrapeError: undefined, | |
| }); | |
| setIsEditing(false); | |
| showToast("Product Updated", "Manual details saved."); | |
| }; | |
| const handleImageFileChange = (event) => { | |
| const file = event.target.files?.[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onloadend = () => { | |
| const imageDataUrl = reader.result; | |
| mockStore.updateProduct(product.id || product.tempId, { | |
| heroImage: imageDataUrl, | |
| status: (product.status === 'image_upload_required' || product.status === 'error') && | |
| product.productName && product.productNumber && product.price && | |
| product.availableColors?.length ? 'success' : product.status, | |
| }); | |
| showToast("Image Uploaded", "Preview updated."); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| event.target.value = ''; | |
| }; | |
| const needsManualInputHighlight = (field) => { | |
| return isEditing && !manualData[field] && ['productName', 'productNumber', 'startingPrice', 'availableColors'].includes(field); | |
| }; | |
| const renderStatusBadge = () => { | |
| switch (product.status) { | |
| case 'pending': return ` | |
| <span class="absolute top-2 right-2 inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"> | |
| Pending | |
| </span>`; | |
| case 'scraping': return ` | |
| <span class="absolute top-2 right-2 inline-flex items-center rounded-md bg-white px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10 animate-pulse"> | |
| Scraping... | |
| </span>`; | |
| case 'success': return ` | |
| <span class="absolute top-2 right-2 inline-flex items-center rounded-md bg-green-500 px-2 py-1 text-xs font-medium text-white ring-1 ring-inset ring-green-600/20"> | |
| <i data-lucide="check-circle" class="w-3 h-3 mr-1"></i> Ready | |
| </span>`; | |
| case 'error': return ` | |
| <span class="absolute top-2 right-2 inline-flex items-center rounded-md bg-red-500 px-2 py-1 text-xs font-medium text-white ring-1 ring-inset ring-red-600/10"> | |
| <i data-lucide="alert-circle" class="w-3 h-3 mr-1"></i> Error | |
| </span>`; | |
| case 'image_upload_required': return ` | |
| <span class="absolute top-2 right-2 inline-flex items-center rounded-md bg-gray-100 px-2 py-1 text-xs font-medium text-gray-600 ring-1 ring-inset ring-gray-500/10"> | |
| <i data-lucide="image-plus" class="w-3 h-3 mr-1"></i> Need Info | |
| </span>`; | |
| default: return ''; | |
| } | |
| }; | |
| return ` | |
| <div class="relative w-full transition-all duration-300 ease-in-out shadow-md hover:shadow-lg flex flex-col h-full overflow-hidden bg-white rounded-lg"> | |
| ${renderStatusBadge()} | |
| ${!isLoading ? ` | |
| <button | |
| class="absolute top-10 right-2 h-7 w-7 z-10 rounded-full flex items-center justify-center ${isEditing ? 'bg-blue-500 text-white' : 'bg-white border border-gray-300 text-gray-700'}" | |
| onclick="${isEditing ? 'handleManualSave()' : 'handleEditToggle()'}" | |
| title="${isEditing ? 'Save Changes' : 'Edit Product Details'}" | |
| > | |
| <i data-lucide="${isEditing ? 'save' : 'edit-3'}" class="w-4 h-4"></i> | |
| </button> | |
| ` : ''} | |
| ${isEditing ? ` | |
| <button | |
| class="absolute top-20 right-2 h-7 w-7 z-10 rounded-full flex items-center justify-center bg-white text-gray-500 hover:text-red-500" | |
| onclick="setIsEditing(false)" | |
| title="Cancel Edit" | |
| > | |
| <i data-lucide="x" class="w-4 h-4"></i> | |
| </button> | |
| ` : ''} | |
| <div class="p-4 pb-2"> | |
| ${isLoading ? ` | |
| <div class="h-6 w-3/4 mb-1 bg-gray-200 rounded"></div> | |
| <div class="h-4 w-1/2 bg-gray-200 rounded"></div> | |
| ` : isEditing ? ` | |
| <div class="space-y-1"> | |
| <input | |
| name="productNumber" | |
| value="${manualData.productNumber}" | |
| onchange="handleManualInputChange(event)" | |
| placeholder="Product Number (e.g., #2065)" | |
| class="text-sm font-medium w-full px-3 py-2 border ${needsManualInputHighlight('productNumber') ? 'border-red-500 ring-1 ring-red-500' : 'border-gray-300'} rounded-md" | |
| aria-label="Product Number" | |
| /> | |
| <input | |
| name="productName" | |
| value="${manualData.productName}" | |
| onchange="handleManualInputChange(event)" | |
| placeholder="Product Name (e.g., Chaz Carabiner...)" | |
| class="text-lg font-semibold w-full px-3 py-2 border ${needsManualInputHighlight('productName') ? 'border-red-500 ring-1 ring-red-500' : 'border-gray-300'} rounded-md" | |
| aria-label="Product Name" | |
| /> | |
| </div> | |
| ` : ` | |
| <h3 class="text-lg font-semibold text-gray-900 leading-tight pr-16"> | |
| ${product.productNumber ? `<span class="text-gray-500 font-medium mr-1">${product.productNumber}</span>` : ''} | |
| ${product.productName || '<span class="italic text-gray-500">Product Name Missing</span>'} | |
| </h3> | |
| `} | |
| </div> | |
| <div class="px-4 pt-0 pb-3 flex-grow flex flex-col"> | |
| <div class="aspect-video w-full bg-gray-100 rounded-md mb-3 flex items-center justify-center overflow-hidden relative group"> | |
| ${isLoading ? ` | |
| <div class="h-full w-full bg-gray-200 rounded"></div> | |
| ` : (isError || needsImageUpload || (isSuccess && !product.heroImage)) && !isEditing ? ` | |
| <div class="text-center text-gray-500 p-4 w-full h-full flex flex-col items-center justify-center bg-gray-100/50"> | |
| <i data-lucide="image-plus" class="w-10 h-10 mb-2"></i> | |
| <p class="text-sm font-medium mb-2">Image unavailable</p> | |
| <input | |
| type="file" | |
| id="file-upload-${product.id || product.tempId}" | |
| class="hidden" | |
| accept="image/png, image/jpeg, image/webp, image/gif" | |
| onchange="handleImageFileChange(event)" | |
| /> | |
| <label for="file-upload-${product.id || product.tempId}" class="cursor-pointer inline-flex items-center rounded-md bg-gray-200 px-3 py-1.5 text-sm font-medium text-gray-700 hover:bg-gray-300"> | |
| <i data-lucide="image-plus" class="w-4 h-4 mr-1"></i> Upload Image | |
| </label> | |
| <p class="text-xs mt-1">(Recommended: 400x300px or 4:3 ratio)</p> | |
| ${isError && product.scrapeError ? ` | |
| <p class="text-xs text-red-500 mt-2 px-2">${product.scrapeError}</p> | |
| ` : ''} | |
| ${!isEditing && (isError || needsImageUpload) ? ` | |
| <div class="mt-2 text-xs text-red-500 space-y-0.5"> | |
| ${!product.productName ? '<p>Product Name required.</p>' : ''} | |
| ${!product.productNumber ? '<p>Product Number required.</p>' : ''} | |
| ${!product.price ? '<p>Pricing required.</p>' : ''} | |
| ${!(product.availableColors && product.availableColors.length > 0) ? '<p>Colors required.</p>' : ''} | |
| </div> | |
| ` : ''} | |
| <button onclick="onRemoveClick('${product.id || product.tempId}')" class="absolute bottom-1 right-1 text-xs h-6 px-1 text-gray-500 hover:text-red-500 z-10 opacity-0 group-hover:opacity-100 transition-opacity"> | |
| Remove | |
| </button> | |
| </div> | |
| ` : product.heroImage ? ` | |
| <img | |
| src="${product.heroImage}" | |
| alt="${product.productName || 'Product Image'}" | |
| class="w-full h-full object-cover" | |
| /> | |
| <div class="absolute inset-0 bg-black/0 group-hover:bg-black/30 transition-colors duration-200"> | |
| ${!isEditing ? ` | |
| <button onclick="onRemoveClick('${product.id || product.tempId}')" class="absolute bottom-1 right-1 text-xs h-6 px-1 bg-black/50 text-white hover:bg-red-500 hover:text-white opacity-0 group-hover:opacity-100 transition-opacity z-10"> | |
| Remove | |
| </button> | |
| ` : ''} | |
| </div> | |
| ` : ` | |
| <div class="h-full w-full bg-gray-200 rounded"></div> | |
| `} | |
| </div> | |
| <div class="mb-3 flex-grow"> | |
| ${isLoading ? ` | |
| <div class="h-4 w-full mb-1 bg-gray-200 rounded"></div> | |
| <div class="h-4 w-5/6 bg-gray-200 rounded"></div> | |
| ` : isEditing ? ` | |
| <textarea | |
| name="description" | |
| value="${manualData.description}" | |
| onchange="handleManualInputChange(event)" | |
| placeholder="Product Description" | |
| class="text-sm w-full px-3 py-2 border border-gray-300 rounded-md h-24" | |
| rows="3" | |
| ></textarea> | |
| ` : product.description ? ` | |
| <p class="text-sm text-gray-500 line-clamp-4">${product.description}</p> | |
| ` : !isError && !isEditing ? ` | |
| <p class="text-sm text-gray-500 italic">No description available.</p> | |
| ` : ''} | |
| </div> | |
| <div class="mt-auto space-y-1"> | |
| ${isLoading ? ` | |
| <div class="h-5 w-1/2 bg-gray-200 rounded"></div> | |
| ` : isEditing ? ` | |
| <div class="grid grid-cols-2 gap-2"> | |
| <div class="space-y-0.5"> | |
| <label class="text-xs text-gray-500">Min. Qty</label> | |
| <input | |
| name="minimumQuantity" | |
| type="number" | |
| value="${manualData.minimumQuantity}" | |
| onchange="handleManualInputChange(event)" | |
| placeholder="e.g. 250" | |
| class="text-sm w-full px-3 py-2 border ${needsManualInputHighlight('minimumQuantity') ? 'border-red-500 ring-1 ring-red-500' : 'border-gray-300'} rounded-md h-9" | |
| aria-label="Minimum Quantity" | |
| /> | |
| </div> | |
| <div class="space-y-0.5"> | |
| <label class="text-xs text-gray-500">Starts at</label> | |
| <div class="flex items-center gap-1"> | |
| <input | |
| name="startingPrice" | |
| type="number" | |
| step="0.01" | |
| value="${manualData.startingPrice}" | |
| onchange="handleManualInputChange(event)" | |
| placeholder="e.g. 1.73" | |
| class="text-sm font-semibold w-20 px-3 py-2 border ${needsManualInputHighlight('startingPrice') ? 'border-red-500 ring-1 ring-red-500' : 'border-gray-300'} rounded-md h-9" | |
| aria-label="Starting Price" | |
| /> | |
| <input | |
| name="currency" | |
| value="${manualData.currency}" | |
| onchange="handleManualInputChange(event)" | |
| placeholder="USD" | |
| class="text-xs w-12 px-3 py-2 border border-gray-300 rounded-md h-9" | |
| aria-label="Currency" | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| ` : typeof product.price === 'object' && product.price ? ` | |
| <p class="text-lg font-semibold text-blue-600"> | |
| Starts at ${formatPrice(product.price)} | |
| ${product.price.minimumQuantity ? ` | |
| <span class="text-sm font-normal text-gray-500 ml-1">(Min. ${product.price.minimumQuantity})</span> | |
| ` : ''} | |
| </p> | |
| ` : typeof product.price === 'string' ? ` | |
| <p class="text-lg font-semibold text-blue-600">${product.price}</p> | |
| ` : !isError && !isEditing ? ` | |
| <p class="text-sm text-gray-500 italic">Pricing not available.</p> | |
| ` : ''} | |
| </div> | |
| <div class="mt-2 space-y-1"> | |
| ${isLoading ? ` | |
| <div class="h-4 w-3/4 bg-gray-200 rounded"></div> | |
| ` : isEditing ? ` | |
| <div class="flex items-center gap-1"> | |
| <i data-lucide="palette" class="w-4 h-4 text-gray-500 flex-shrink-0"></i> | |
| <input | |
| name="availableColors" | |
| value="${manualData.availableColors}" | |
| onchange="handleManualInputChange(event)" | |
| placeholder="Colors (comma-separated)" | |
| class="text-sm w-full px-3 py-2 border ${needsManualInputHighlight('availableColors') ? 'border-red-500 ring-1 ring-red-500' : 'border-gray-300'} rounded-md h-9" | |
| aria-label="Available Colors" | |
| /> | |
| </div> | |
| ` : product.availableColors && product.availableColors.length > 0 ? ` | |
| <div class="flex items-center gap-1 text-sm text-gray-500"> | |
| <i data-lucide="palette" class="w-4 h-4 flex-shrink-0"></i> | |
| <span class="line-clamp-1">${product.availableColors.join(', ')}</span> | |
| </div> | |
| ` : !isError && !isEditing ? ` | |
| <p class="text-sm text-gray-500 italic">Color info not available.</p> | |
| ` : ''} | |
| </div> | |
| ${product.pricingTableHTML && !isLoading && !isError && !isEditing ? ` | |
| <p class="text-xs text-gray-500 mt-1 italic"> | |
| Detailed pricing available. | |
| </p> | |
| ` : ''} | |
| </div> | |
| </div> | |
| `; | |
| } | |
| // React-like useState for vanilla JS | |
| function useState(initialValue) { | |
| let value = initialValue; | |
| const listeners = []; | |
| const setValue = (newValue) => { | |
| if (typeof newValue === 'function') { | |
| value = newValue(value); | |
| } else { | |
| value = newValue; | |
| } | |
| listeners.forEach(listener => listener(value)); | |
| }; | |
| const getValue = () => value; | |
| const subscribe = (listener) => { | |
| listeners.push(listener); | |
| return () => { | |
| const index = listeners.indexOf(listener); | |
| if (index !== -1) listeners.splice(index, 1); | |
| }; | |
| }; | |
| return [getValue, setValue, subscribe]; | |
| } | |
| // Render function | |
| function renderProductCard(product) { | |
| const container = document.getElementById('product-card-container'); | |
| // Create state variables | |
| const [isEditing, setIsEditing] = useState(false); | |
| // Create the card | |
| container.innerHTML = ProductCard({ | |
| product, | |
| onUploadClick: (id) => { | |
| document.getElementById(`file-upload-${id}`)?.click(); | |
| }, | |
| onRemoveClick: (id) => { | |
| showToast("Product Removed", `Product ${id} has been removed`); | |
| } | |
| }); | |
| // Update Lucide icons | |
| lucide.createIcons(); | |
| // Set up event handlers | |
| window.handleManualInputChange = (e) => { | |
| const { name, value } = e.target; | |
| setManualData(prev => ({ ...prev, [name]: value })); | |
| }; | |
| window.handleImageFileChange = (e) => { | |
| const file = e.target.files?.[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onloadend = () => { | |
| const imageDataUrl = reader.result; | |
| mockStore.updateProduct(sampleProduct.id || sampleProduct.tempId, { | |
| heroImage: imageDataUrl, | |
| status: 'success', | |
| }); | |
| showToast("Image Uploaded", "Preview updated."); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| e.target.value = ''; | |
| }; | |
| window.handleManualSave = () => { | |
| // Basic validation | |
| if (!manualData.productName || !manualData.productNumber || !manualData.startingPrice || !manualData.availableColors) { | |
| showToast("Missing Information", "Please fill in Product Name, Number, Starting Price, and Colors.", "destructive"); | |
| return; | |
| } | |
| const priceValue = parseFloat(manualData.startingPrice); | |
| const minQtyValue = parseInt(manualData.minimumQuantity, 10); | |
| const priceInfo = !isNaN(priceValue) ? { | |
| min: priceValue, | |
| currency: manualData.currency || 'USD', | |
| minimumQuantity: !isNaN(minQtyValue) ? minQtyValue : undefined | |
| } : null; | |
| const colorsArray = manualData.availableColors.split(',').map(color => color.trim()).filter(Boolean); | |
| mockStore.updateProduct(sampleProduct.id || sampleProduct.tempId, { | |
| productName: manualData.productName || undefined, | |
| productNumber: manualData.productNumber || undefined, | |
| description: manualData.description || undefined, | |
| price: priceInfo, | |
| availableColors: colorsArray.length > 0 ? colorsArray : undefined, | |
| status: 'success', | |
| }); | |
| setIsEditing(false); | |
| showToast("Product Updated", "Manual details saved."); | |
| }; | |
| window.setIsEditing = (value) => { | |
| setIsEditing(value); | |
| renderProductCard(sampleProduct); | |
| }; | |
| } | |
| // Initial render | |
| renderProductCard(sampleProduct); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=SpectrumInfinite/spectrum-infinite-flyerforge" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |