Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

buy.kidlisp.com: full-bleed animated background cards with rich metadata

- Card strips use animated webp as full background-image with gradient overlay
- Show seller, original author, and listing date on each card
- Full tez addresses (or .tez domains when resolved), no truncation
- Larger piece name type (18px), solid gold buy buttons
- Removed sort controls, stats bar, and separate thumbnail column

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+74 -58
+74 -58
system/public/kidlisp.com/buy.html
··· 160 160 padding: 10px; 161 161 } 162 162 163 - /* ── Fresh card (horizontal tile) ── */ 163 + /* ── Fresh card (full-bleed background strip) ── */ 164 164 .card { 165 - background: var(--bg2); 165 + position: relative; 166 166 border: 1px solid var(--border); 167 167 border-radius: var(--radius); 168 168 overflow: hidden; ··· 171 171 box-shadow: 0 2px 8px var(--card-glow); 172 172 display: flex; 173 173 flex-direction: row; 174 - align-items: stretch; 174 + align-items: center; 175 + min-height: 100px; 176 + background-size: cover; 177 + background-position: center; 178 + background-color: var(--bg3); 179 + } 180 + .card::before { 181 + content: ''; 182 + position: absolute; 183 + inset: 0; 184 + background: linear-gradient(90deg, rgba(0,0,0,0.75) 0%, rgba(0,0,0,0.45) 50%, rgba(0,0,0,0.15) 100%); 185 + z-index: 1; 175 186 } 176 187 .card:hover { 177 188 border-color: var(--accent); ··· 179 190 box-shadow: 0 6px 20px var(--card-hover-glow); 180 191 } 181 192 182 - .card-thumb { 183 - width: 120px; 184 - min-height: 90px; 185 - flex-shrink: 0; 186 - background: var(--bg3); 187 - overflow: hidden; 188 - position: relative; 189 - } 190 - .card-thumb-placeholder { 191 - width: 100%; height: 100%; 192 - background: linear-gradient(135deg, var(--bg3), var(--bg2), var(--bg3)); 193 - background-size: 200% 200%; 194 - animation: shimmer 2s ease-in-out infinite; 195 - display: flex; 196 - align-items: center; 197 - justify-content: center; 198 - font-size: 24px; 199 - color: var(--text2); 200 - opacity: 0.5; 201 - } 202 193 @keyframes shimmer { 203 194 0% { background-position: 0% 50%; } 204 195 50% { background-position: 100% 50%; } 205 196 100% { background-position: 0% 50%; } 206 - } 207 - 208 - .card-thumb img { 209 - width: 100%; height: 100%; 210 - object-fit: cover; 211 - image-rendering: auto; 212 197 } 213 198 214 199 .card-body { 215 - padding: 10px 14px; 200 + position: relative; 201 + z-index: 2; 202 + padding: 14px 18px; 216 203 display: flex; 217 204 flex-direction: column; 218 205 justify-content: center; ··· 222 209 } 223 210 224 211 .card-code { 225 - font-size: 14px; 212 + font-size: 18px; 226 213 font-weight: 700; 227 214 font-family: var(--font-mono); 228 - color: var(--purple); 215 + color: #fff; 216 + text-shadow: 0 1px 4px rgba(0,0,0,0.6); 229 217 } 230 218 231 219 .card-price { 232 220 font-size: 16px; 233 221 font-weight: 700; 234 222 color: var(--gold); 235 - text-shadow: 0 0 8px rgba(255, 230, 109, 0.3); 223 + text-shadow: 0 1px 4px rgba(0,0,0,0.5); 236 224 } 237 225 238 226 .card-seller { 239 227 font-size: 10px; 240 - color: var(--text2); 228 + color: rgba(255,255,255,0.6); 241 229 overflow: hidden; 242 230 text-overflow: ellipsis; 243 231 white-space: nowrap; 232 + text-shadow: 0 1px 2px rgba(0,0,0,0.5); 244 233 } 245 234 246 235 /* ── Sold mini-cards (dense grid of stills) ── */ ··· 389 378 .modal-permalink:hover { text-decoration: underline; } 390 379 391 380 .buy-btn { 392 - background: linear-gradient(135deg, var(--accent), var(--neon-pink)); 393 - color: #000; 381 + background: var(--gold); 382 + color: #1a1528; 394 383 border: none; 395 - padding: 12px 24px; 384 + padding: 14px 32px; 396 385 border-radius: var(--radius); 397 386 font-family: var(--font-fun); 398 - font-size: 15px; 387 + font-size: 17px; 399 388 font-weight: 700; 400 389 cursor: pointer; 401 390 transition: opacity 0.15s, transform 0.15s, box-shadow 0.15s; 402 - box-shadow: 0 2px 12px rgba(245, 158, 11, 0.3); 391 + box-shadow: 0 2px 12px rgba(255, 230, 109, 0.25); 403 392 flex-shrink: 0; 404 393 } 405 - .buy-btn:hover { opacity: 0.9; transform: scale(1.03); box-shadow: 0 4px 20px rgba(245, 158, 11, 0.5); } 406 - .buy-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; box-shadow: none; } 394 + .buy-btn:hover { opacity: 0.9; transform: scale(1.03); box-shadow: 0 4px 20px rgba(255, 230, 109, 0.4); } 395 + .buy-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; } 407 396 408 397 .buy-status { 409 398 font-size: 11px; ··· 481 470 } 482 471 483 472 .card-buy-btn { 484 - background: linear-gradient(135deg, var(--accent), var(--neon-pink)); 485 - color: #000; 473 + position: relative; 474 + z-index: 2; 475 + background: var(--gold); 476 + color: #1a1528; 486 477 border: none; 487 - padding: 10px 16px; 488 - margin-right: 10px; 478 + padding: 12px 20px; 479 + margin-right: 14px; 489 480 font-family: var(--font-fun); 490 - font-size: 13px; 481 + font-size: 14px; 491 482 font-weight: 700; 492 483 cursor: pointer; 493 484 transition: opacity 0.15s, transform 0.15s; ··· 496 487 white-space: nowrap; 497 488 align-self: center; 498 489 flex-shrink: 0; 499 - box-shadow: 0 2px 8px rgba(245, 158, 11, 0.25); 500 490 } 501 - .card-buy-btn:hover { opacity: 0.9; transform: scale(1.02); } 502 - .card-buy-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } 491 + .card-buy-btn:hover { opacity: 0.85; transform: scale(1.03); } 492 + .card-buy-btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; } 503 493 504 494 /* ── Footer ── */ 505 495 footer { ··· 615 605 let currentTab = 'fresh'; 616 606 let selectedListing = null; 617 607 608 + // ── Address → .tez domain resolver ── 609 + const domainCache = {}; 610 + async function resolveSellerNames(addresses) { 611 + const unknown = [...new Set(addresses)].filter(a => !(a in domainCache)); 612 + if (!unknown.length) return; 613 + await Promise.all(unknown.map(async (addr) => { 614 + try { 615 + const res = await fetch(`https://api.tzkt.io/v1/domains?owner=${addr}&limit=1`); 616 + const data = await res.json(); 617 + domainCache[addr] = data?.[0]?.name || null; 618 + } catch { domainCache[addr] = null; } 619 + })); 620 + } 621 + function sellerName(addr) { 622 + if (!addr) return '?'; 623 + const domain = domainCache[addr]; 624 + return domain || addr; 625 + } 626 + 618 627 // ── DOM ── 619 628 const grid = document.getElementById('grid'); 620 629 const walletBtn = document.getElementById('wallet-btn'); ··· 710 719 price_xtz 711 720 timestamp 712 721 seller_address 713 - token { token_id name thumbnail_uri artifact_uri } 722 + token { token_id name thumbnail_uri artifact_uri creators { creator_address } } 714 723 } 715 724 }`; 716 725 ··· 797 806 798 807 grid.innerHTML = sorted.map(l => { 799 808 const name = l.token?.name || `#${l.token?.token_id}`; 800 - // Use animated IPFS thumbnail by default instead of static oven PNG 801 809 const animated = animatedWebpUrl(l.token?.thumbnail_uri); 802 810 const price = formatXTZ(l.price_xtz); 803 - const seller = l.seller_address?.slice(0, 8) + '…'; 811 + const seller = sellerName(l.seller_address); 812 + const creator = l.token?.creators?.[0]?.creator_address; 813 + const creatorLabel = creator ? sellerName(creator) : ''; 814 + const listed = new Date(l.timestamp).toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); 804 815 const askId = l.id || l.bigmap_key; 805 - return `<div class="card" data-id="${askId}"> 806 - <div class="card-thumb"> 807 - ${animated ? `<img src="${animated}" alt="${name}" loading="lazy" />` : `<div class="card-thumb-placeholder">✦</div>`} 808 - </div> 816 + const bgStyle = animated ? `background-image:url('${animated}')` : ''; 817 + return `<div class="card" data-id="${askId}" style="${bgStyle}"> 809 818 <div class="card-body"> 810 819 <div class="card-code">${name}</div> 811 820 <div class="card-price">${price} XTZ</div> 812 - <div class="card-seller">${seller}</div> 821 + <div class="card-seller">seller: ${seller}</div> 822 + ${creatorLabel ? `<div class="card-seller">author: ${creatorLabel}</div>` : ''} 823 + <div class="card-seller">listed: ${listed}</div> 813 824 </div> 814 825 <button class="card-buy-btn" data-ask="${askId}">${walletAddress ? 'Buy — ' + price + ' XTZ' : 'Buy'}</button> 815 826 </div>`; 816 827 }).join(''); 817 828 818 829 // Card click → modal 819 - grid.querySelectorAll('.card-thumb, .card-body').forEach(el => { 830 + grid.querySelectorAll('.card-body').forEach(el => { 820 831 el.addEventListener('click', () => { 821 832 const id = el.closest('.card').dataset.id; 822 833 const listing = listings.find(l => String(l.id || l.bigmap_key) === id); ··· 1101 1112 listings = freshData; 1102 1113 soldEvents = soldData; 1103 1114 liveDot.classList.remove('error'); 1115 + const allAddrs = [ 1116 + ...freshData.map(l => l.seller_address), 1117 + ...freshData.flatMap(l => (l.token?.creators || []).map(c => c.creator_address)), 1118 + ]; 1119 + await resolveSellerNames(allAddrs); 1104 1120 renderGrid(); 1105 1121 1106 1122 // Route from URL on first load (after listings are available)