Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/TEMPLATES/store_layouts/spotlight.html
Diff
/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/TEMPLATES/store_layouts/spotlight.html
added on local at 2026-07-01 13:47:54
Added
+244
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 797bd773db4f
to 797bd773db4f
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | <!-- ===================================================================== | |
| 2 | LAYOUT: Single Spotlight | |
| 3 | One huge featured product up top, smaller catalog strip below. | |
| 4 | Best for sellers with a flagship, weekly drops, or pre-orders. | |
| 5 | ===================================================================== --> | |
| 6 | ||
| 7 | <style>:root { $theme_css_vars }</style> | |
| 8 | ||
| 9 | [if:$preview_banner] | |
| 10 | <div style="position:sticky;top:0;z-index:9999;background:#0a0e1c;border-bottom:1px solid rgba(59,130,246,0.40);box-shadow:0 4px 16px rgba(0,0,0,0.55);padding:10px 20px;text-align:center;font-size:13px;color:#93c5fd;font-weight:600"> | |
| 11 | <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align:-2px"><circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="3"/></svg> | |
| 12 | $preview_banner | |
| 13 | </div> | |
| 14 | [/if] | |
| 15 | ||
| 16 | <header style="border-bottom:1px solid var(--col-border);padding:14px 24px;display:flex;align-items:center;gap:14px;background:var(--col-bg);flex-wrap:wrap"> | |
| 17 | <a href="/store.cgi?id=$store_id" style="display:flex;align-items:center;gap:10px;text-decoration:none;flex-shrink:0"> | |
| 18 | <div class="brand-mark" style="background:linear-gradient(130deg, var(--col-accent), var(--col-accent-bright));width:34px;height:34px;font-size:13px">$store_initials</div> | |
| 19 | <span style="font-family:var(--font-display);font-size:18px;font-weight:700;color:var(--col-text)">$store_name</span> | |
| 20 | </a> | |
| 21 | <div style="flex:1;display:flex;justify-content:center;min-width:0">$store_search_form</div> | |
| 22 | <nav style="margin-left:auto;display:flex;gap:6px;align-items:center;flex-wrap:wrap"> | |
| 23 | [if:$has_nav_pages] | |
| 24 | [loop:@nav_pages] | |
| 25 | <a href="$loop1.href" class="btn btn-ghost btn-sm">$loop1.title</a> | |
| 26 | [/loop] | |
| 27 | [/if] | |
| 28 | <a href="/store.cgi?id=$store_id#shop" class="btn btn-ghost btn-sm">All designs</a> | |
| 29 | <a href="$cart_href" class="btn btn-primary btn-sm" style="position:relative" id="we-cart-link"> | |
| 30 | $header_cart_label | |
| 31 | [if:$has_cart_items]<span class="we-cart-badge" style="margin-left:6px;display:inline-block;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:#fff;color:var(--col-accent);font-size:11px;font-weight:800;line-height:18px">$cart_count</span>[/if] | |
| 32 | </a> | |
| 33 | </nav> | |
| 34 | <div style="display:inline-flex;gap:8px;align-items:center;margin-left:8px">$store_signin_link $store_cart_link</div> | |
| 35 | </header> | |
| 36 | ||
| 37 | [if:$is_specific_page] | |
| 38 | [if:$page_not_found] | |
| 39 | <section style="padding:120px 32px;text-align:center;max-width:600px;margin:0 auto"> | |
| 40 | <h1 class="hero-title" style="font-size:48px">Page not found</h1> | |
| 41 | <a href="/store.cgi?id=$store_id" class="btn btn-primary btn-lg">Back to home</a> | |
| 42 | </section> | |
| 43 | [/if] | |
| 44 | [if:!$page_not_found] | |
| 45 | <section style="padding:80px 32px [if:$is_collection_page]20px[/if][if:!$is_collection_page]60px[/if];max-width:[if:$is_collection_page]1400px[/if][if:!$is_collection_page]900px[/if];margin:0 auto"> | |
| 46 | <h1 class="hero-title" style="font-size:48px">$page_title</h1> | |
| 47 | <div class="text-secondary" style="font-size:17px;line-height:1.7;margin-top:24px;white-space:pre-wrap">$page_body</div> | |
| 48 | </section> | |
| 49 | ||
| 50 | [if:$is_collection_page] | |
| 51 | <section id="shop" style="padding:20px 32px 80px;max-width:1400px;margin:0 auto"> | |
| 52 | [if:$has_products] | |
| 53 | <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:16px"> | |
| 54 | [loop:@products] | |
| 55 | <div class="we-tile" data-we-listing-id="$loop1.id" style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:10px;overflow:hidden;transition:transform 0.15s,border-color 0.15s;position:relative"> | |
| 56 | <div style="aspect-ratio:1;background:url('$loop1.hero_image') $loop1.hero_image_pos/cover"></div> | |
| 57 | <div style="padding:14px"> | |
| 58 | <div style="font-size:14px;font-weight:600;color:var(--col-text);margin-bottom:6px">$loop1.title</div> | |
| 59 | <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px"> | |
| 60 | <span style="font-size:15px;font-weight:700;color:var(--col-accent-bright)">$loop1.price</span> | |
| 61 | <span style="font-size:11px;color:var(--col-text-3)">$loop1.rating_n</span> | |
| 62 | </div> | |
| 63 | <button type="button" class="btn btn-primary btn-sm we-add-to-cart" data-mid="$loop1.id" data-sid="$store_id" style="width:100%">Add to cart</button> | |
| 64 | </div> | |
| 65 | [if:$is_editor_owner] | |
| 66 | <button type="button" class="we-feature-btn" data-we-feature-id="$loop1.id" title="Make this the featured product">★ Feature</button> | |
| 67 | [/if] | |
| 68 | </div> | |
| 69 | [/loop] | |
| 70 | </div> | |
| 71 | [/if] | |
| 72 | [if:!$has_products] | |
| 73 | <div class="card" style="text-align:center;padding:60px 24px;color:var(--col-text-2)"> | |
| 74 | Nothing to show yet. Designs added on the Models page will appear here. | |
| 75 | </div> | |
| 76 | [/if] | |
| 77 | $pagination_html | |
| 78 | </section> | |
| 79 | [/if] | |
| 80 | [/if] | |
| 81 | [/if] | |
| 82 | ||
| 83 | [if:!$is_specific_page] | |
| 84 | <!-- Hero spotlight: ONE huge featured product. featured_product is set | |
| 85 | in store.cgi to the first product in the catalog (or empty when | |
| 86 | there are no products). Kept outside any iteration because the | |
| 87 | template engine cannot run conditionals inside iterations. --> | |
| 88 | [if:$has_featured] | |
| 89 | <section $featured_image_attrs style="position:relative;min-height:80vh;background:url('$featured_product.hero_image') $featured_product.hero_image_pos/cover;display:flex;align-items:flex-end;border-bottom:1px solid var(--col-border)"> | |
| 90 | <div style="position:absolute;inset:0;background:linear-gradient(0deg, var(--col-bg) 0%, rgba(0,0,0,0) 60%);pointer-events:none"></div> | |
| 91 | <div style="position:relative;z-index:2;padding:60px 48px;max-width:900px"> | |
| 92 | <span style="display:inline-flex;align-items:center;gap:8px;background:var(--col-accent);color:#fff;padding:6px 14px;border-radius:999px;font-size:11px;letter-spacing:2px;text-transform:uppercase;font-weight:700"> | |
| 93 | ★ $featured_badge_label | |
| 94 | </span> | |
| 95 | <h1 style="font-family:var(--font-display);font-size:clamp(48px,7vw,96px);font-weight:800;letter-spacing:-0.03em;line-height:0.95;color:var(--col-text);margin:18px 0 16px">$featured_product.title</h1> | |
| 96 | <p style="font-size:20px;color:var(--col-text-2);max-width:600px;line-height:1.5">$store_tagline</p> | |
| 97 | <div style="display:flex;gap:14px;align-items:center;margin-top:32px;flex-wrap:wrap"> | |
| 98 | <span style="font-size:36px;font-weight:800;color:var(--col-accent-bright)">$featured_product.price</span> | |
| 99 | <button type="button" class="btn btn-primary btn-lg we-add-to-cart" data-mid="$featured_product.id" data-sid="$store_id">$cta_primary_label</button> | |
| 100 | <a href="#shop" class="btn btn-secondary btn-lg">$cta_secondary_label</a> | |
| 101 | </div> | |
| 102 | </div> | |
| 103 | </section> | |
| 104 | [/if] | |
| 105 | ||
| 106 | [if:!$has_featured] | |
| 107 | <section style="min-height:50vh;display:flex;align-items:center;justify-content:center;background:var(--col-bg-2);border-bottom:1px solid var(--col-border);padding:80px 48px;text-align:center"> | |
| 108 | <div style="max-width:600px"> | |
| 109 | <h1 style="font-family:var(--font-display);font-size:clamp(40px,5vw,64px);font-weight:800;color:var(--col-text);margin:0 0 16px">$store_name</h1> | |
| 110 | <p style="font-size:18px;color:var(--col-text-2);line-height:1.6">$store_tagline</p> | |
| 111 | <p class="text-dim" style="margin-top:32px;font-size:14px">The featured release will appear here once designs are added.</p> | |
| 112 | </div> | |
| 113 | </section> | |
| 114 | [/if] | |
| 115 | ||
| 116 | <!-- Product strip -- small, dense grid of remaining products --> | |
| 117 | <section id="shop" style="padding:60px 32px 80px;max-width:1400px;margin:0 auto"> | |
| 118 | <div style="display:flex;justify-content:space-between;align-items:flex-end;margin-bottom:32px"> | |
| 119 | <h2 style="font-family:var(--font-display);font-size:28px;color:var(--col-text);margin:0">$section_heading</h2> | |
| 120 | <span style="color:var(--col-text-3);font-size:13px">$product_count designs total</span> | |
| 121 | </div> | |
| 122 | ||
| 123 | [if:$has_secondary] | |
| 124 | <div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:14px"> | |
| 125 | [loop:@secondary_products] | |
| 126 | <div class="we-tile" data-we-listing-id="$loop1.id" style="background:var(--col-surface-1);border:1px solid var(--col-border);border-radius:8px;overflow:hidden;transition:transform 0.15s,border-color 0.15s;position:relative"> | |
| 127 | <div style="aspect-ratio:1;background:url('$loop1.hero_image') $loop1.hero_image_pos/cover"></div> | |
| 128 | <div style="padding:12px"> | |
| 129 | <div style="font-size:13px;font-weight:600;color:var(--col-text);margin-bottom:6px">$loop1.title</div> | |
| 130 | <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px"> | |
| 131 | <span style="font-size:14px;font-weight:700;color:var(--col-accent-bright)">$loop1.price</span> | |
| 132 | <span style="font-size:11px;color:var(--col-text-3)">$loop1.rating_n</span> | |
| 133 | </div> | |
| 134 | <button type="button" class="btn btn-primary btn-sm we-add-to-cart" data-mid="$loop1.id" data-sid="$store_id" style="width:100%">Add to cart</button> | |
| 135 | </div> | |
| 136 | [if:$is_editor_owner] | |
| 137 | <button type="button" class="we-feature-btn" data-we-feature-id="$loop1.id" title="Make this the featured product">★ Feature</button> | |
| 138 | [/if] | |
| 139 | </div> | |
| 140 | [/loop] | |
| 141 | </div> | |
| 142 | [/if] | |
| 143 | ||
| 144 | [if:!$has_secondary] | |
| 145 | <div class="card" style="text-align:center;padding:48px 24px"> | |
| 146 | <div style="color:var(--col-text-2)">No additional designs yet.</div> | |
| 147 | </div> | |
| 148 | [/if] | |
| 149 | </section> | |
| 150 | [/if] | |
| 151 | ||
| 152 | [if:$has_marketplace_links] | |
| 153 | <section style="background:var(--col-bg-2);border-top:1px solid var(--col-border);padding:36px 32px;text-align:center"> | |
| 154 | <div style="font-size:11px;letter-spacing:1.5px;text-transform:uppercase;color:var(--col-text-3);font-weight:700;margin-bottom:14px">Also find $store_name on</div> | |
| 155 | <div style="display:flex;justify-content:center;gap:10px;flex-wrap:wrap"> | |
| 156 | [loop:@marketplace_links] | |
| 157 | <a href="$loop1.href" target="_blank" rel="noopener" class="btn btn-secondary btn-sm" style="display:inline-flex;align-items:center;gap:8px"> | |
| 158 | <span style="display:inline-grid;place-items:center;width:22px;height:22px;border-radius:5px;background:linear-gradient(130deg,var(--col-accent),var(--col-accent-bright));color:#fff;font-size:11px;font-weight:700">$loop1.icon</span> | |
| 159 | $loop1.name | |
| 160 | </a> | |
| 161 | [/loop] | |
| 162 | </div> | |
| 163 | </section> | |
| 164 | [/if] | |
| 165 | ||
| 166 | <footer style="background:var(--col-bg-2);border-top:1px solid var(--col-border);padding:30px 32px;text-align:center"> | |
| 167 | <div class="text-xs text-dim" style="letter-spacing:1.5px;text-transform:uppercase">$store_name · $footer_byline</div> | |
| 168 | </footer> | |
| 169 | ||
| 170 | <script> | |
| 171 | /* Add-to-cart for any element with .we-add-to-cart + data-mid + data-sid. | |
| 172 | POSTs to /cart_add.cgi, updates the header cart badge, and flashes | |
| 173 | the button to "Added to cart" briefly so the buyer sees feedback. */ | |
| 174 | (function() { | |
| 175 | document.addEventListener('click', function(e) { | |
| 176 | var btn = e.target.closest('.we-add-to-cart'); | |
| 177 | if (!btn) return; | |
| 178 | e.preventDefault(); | |
| 179 | var mid = btn.getAttribute('data-mid'); | |
| 180 | var sid = btn.getAttribute('data-sid'); | |
| 181 | if (!mid || !sid) return; | |
| 182 | var originalText = btn.textContent; | |
| 183 | btn.disabled = true; | |
| 184 | btn.textContent = 'Adding...'; | |
| 185 | var fd = new FormData(); | |
| 186 | fd.append('model_id', mid); | |
| 187 | fd.append('storefront_id', sid); | |
| 188 | fetch('/cart_add.cgi', { method: 'POST', body: fd, credentials: 'same-origin' }) | |
| 189 | .then(function(r){ | |
| 190 | return r.text().then(function(body){ | |
| 191 | try { return JSON.parse(body); } | |
| 192 | catch (e) { | |
| 193 | var snippet = body ? body.replace(/<[^>]*>/g,' ').replace(/\s+/g,' ').trim().slice(0, 200) : '(empty body)'; | |
| 194 | throw new Error('server returned ' + r.status + ' ' + r.statusText + ' (' + snippet + ')'); | |
| 195 | } | |
| 196 | }); | |
| 197 | }) | |
| 198 | .then(function(data){ | |
| 199 | if (!data || !data.success) throw new Error(data && data.error || 'add failed'); | |
| 200 | btn.textContent = 'Added ✓'; | |
| 201 | /* Update header badge in place. */ | |
| 202 | var link = document.getElementById('we-cart-link'); | |
| 203 | if (link) { | |
| 204 | var badge = link.querySelector('.we-cart-badge'); | |
| 205 | if (badge) { | |
| 206 | badge.textContent = data.count; | |
| 207 | } else { | |
| 208 | badge = document.createElement('span'); | |
| 209 | badge.className = 'we-cart-badge'; | |
| 210 | badge.style.cssText = 'margin-left:6px;display:inline-block;min-width:18px;height:18px;padding:0 5px;border-radius:9px;background:#fff;color:var(--col-accent);font-size:11px;font-weight:800;line-height:18px'; | |
| 211 | badge.textContent = data.count; | |
| 212 | link.appendChild(badge); | |
| 213 | } | |
| 214 | } | |
| 215 | setTimeout(function(){ btn.disabled = false; btn.textContent = originalText; }, 1500); | |
| 216 | }) | |
| 217 | .catch(function(err){ | |
| 218 | btn.disabled = false; | |
| 219 | btn.textContent = originalText; | |
| 220 | appToast({ message: 'Add to cart failed: ' + err.message, type: 'error' }); | |
| 221 | }); | |
| 222 | }); | |
| 223 | })(); | |
| 224 | ||
| 225 | // Make product cards clickable. Spotlight uses <div class="we-tile"> | |
| 226 | // rather than <a> wrappers (because of the inline Add-to-Cart and | |
| 227 | // Feature buttons), so we add a click handler that navigates to the | |
| 228 | // listing detail page -- except when the click was on one of the | |
| 229 | // inner buttons (which have their own handlers). | |
| 230 | (function(){ | |
| 231 | document.addEventListener('click', function(e){ | |
| 232 | if (e.target.closest('button, a, input, textarea, select')) return; | |
| 233 | var tile = e.target.closest('[data-we-listing-id]'); | |
| 234 | if (!tile) return; | |
| 235 | var id = tile.getAttribute('data-we-listing-id'); | |
| 236 | if (!id) return; | |
| 237 | window.location.href = '/listing_details.cgi?id=' + encodeURIComponent(id); | |
| 238 | }); | |
| 239 | // Cursor affordance. | |
| 240 | document.querySelectorAll('[data-we-listing-id]').forEach(function(t){ | |
| 241 | t.style.cursor = 'pointer'; | |
| 242 | }); | |
| 243 | })(); | |
| 244 | </script> |