PageSourceSearch

https://captainethereum.info/nfts/nft-gallery.js

js captainethereum.info collected 2026-10-08 10:51:12 UTC 18,607 bytes, 631 lines download raw bytes

1/**
2 * NFT Gallery for Captain Ethereum Pre-Series
3 * This script fetches and displays all NFTs from the contract
4 */
5
6// DOM Elements
7const loadingOverlay = document.getElementById('loadingOverlay');
8const statusMessage = document.getElementById('statusMessage');
9const charactersGrid = document.getElementById('charactersGrid');
10const nftModal = document.getElementById('nftModal');
11const closeModal = document.getElementById('closeModal');
12const modalImage = document.getElementById('modalImage');
13const modalName = document.getElementById('modalName');
14const modalRole = document.getElementById('modalRole');
15const modalDescription = document.getElementById('modalDescription');
16const modalMetadata = document.getElementById('modalMetadata');
17const modalPowers = document.getElementById('modalPowers');
18const modalFaction = document.getElementById('modalFaction');
19
20// Contract configuration
21const CONTRACT_ADDRESS = '0x13BaE33c02b91390b9c3A22E4B1B1F4a065DBD78'; // Replace with your actual contract address
22const CONTRACT_ABI = [
23    {
24        "inputs": [],
25        "stateMutability": "nonpayable",
26        "type": "constructor"
27    },
28    {
29        "inputs": [],
30        "name": "InvalidCharacterData",
31        "type": "error"
32    },
33    {
34        "inputs": [],
35        "name": "InvalidRecipient",
36        "type": "error"
37    },
38    {
39        "anonymous": false,
40        "inputs": [
41            {
42                "indexed": true,
43                "internalType": "address",
44                "name": "account",
45                "type": "address"
46            },
47            {
48                "indexed": true,
49                "internalType": "address",
50                "name": "operator",
51                "type": "address"
52            },
53            {
54                "indexed": false,
55                "internalType": "bool",
56                "name": "approved",
57                "type": "bool"
58            }
59        ],
60        "name": "ApprovalForAll",
61        "type": "event"
62    },
63    {
64        "anonymous": false,
65        "inputs": [
66            {
67                "indexed": true,
68                "internalType": "address",
69                "name": "previousOwner",
70                "type": "address"
71            },
72            {
73                "indexed": true,
74                "internalType": "address",
75                "name": "newOwner",
76                "type": "address"
77            }
78        ],
79        "name": "OwnershipTransferred",
80        "type": "event"
81    },
82    {
83        "anonymous": false,
84        "inputs": [
85            {
86                "indexed": true,
87                "internalType": "address",
88                "name": "operator",
89                "type": "address"
90            },
91            {
92                "indexed": true,
93                "internalType": "address",
94                "name": "from",
95                "type": "address"
96            },
97            {
98                "indexed": true,
99                "internalType": "address",
100                "name": "to",
101                "type": "address"
102            },
103            {
104                "indexed": false,
105                "internalType": "uint256[]",
106                "name": "ids",
107                "type": "uint256[]"
108            },
109            {
110                "indexed": false,
111                "internalType": "uint256[]",
112                "name": "values",
113                "type": "uint256[]"
114            }
115        ],
116        "name": "TransferBatch",
117        "type": "event"
118    },
119    {
120        "anonymous": false,
121        "inputs": [
122            {
123                "indexed": true,
124                "internalType": "address",
125                "name": "operator",
126                "type": "address"
127            },
128            {
129                "indexed": true,
130                "internalType": "address",
131                "name": "from",
132                "type": "address"
133            },
134            {
135                "indexed": true,
136                "internalType": "address",
137                "name": "to",
138                "type": "address"
139            },
140            {
141                "indexed": false,
142                "internalType": "uint256",
143                "name": "id",
144                "type": "uint256"
145            },
146            {
147                "indexed": false,
148                "internalType": "uint256",
149                "name": "value",
150                "type": "uint256"
151            }
152        ],
153        "name": "TransferSingle",
154        "type": "event"
155    },
156    {
157        "anonymous": false,
158        "inputs": [
159            {
160                "indexed": false,
161                "internalType": "string",
162                "name": "value",
163                "type": "string"
164            },
165            {
166                "indexed": true,
167                "internalType": "uint256",
168                "name": "id",
169                "type": "uint256"
170            }
171        ],
172        "name": "URI",
173        "type": "event"
174    },
175    {
176        "inputs": [
177            {
178                "internalType": "address",
179                "name": "account",
180                "type": "address"
181            },
182            {
183                "internalType": "uint256",
184                "name": "id",
185                "type": "uint256"
186            }
187        ],
188        "name": "balanceOf",
189        "outputs": [
190            {
191                "internalType": "uint256",
192                "name": "",
193                "type": "uint256"
194            }
195        ],
196        "stateMutability": "view",
197        "type": "function"
198    },
199    {
200        "inputs": [
201            {
202                "internalType": "address[]",
203                "name": "accounts",
204                "type": "address[]"
205            },
206            {
207                "internalType": "uint256[]",
208                "name": "ids",
209                "type": "uint256[]"
210            }
211        ],
212        "name": "balanceOfBatch",
213        "outputs": [
214            {
215                "internalType": "uint256[]",
216                "name": "",
217                "type": "uint256[]"
218            }
219        ],
220        "stateMutability": "view",
221        "type": "function"
222    },
223    {
224        "inputs": [
225            {
226                "internalType": "uint256",
227                "name": "",
228                "type": "uint256"
229            }
230        ],
231        "name": "characters",
232        "outputs": [
233            {
234                "internalType": "string",
235                "name": "name",
236                "type": "string"
237            },
238            {
239                "internalType": "string",
240                "name": "description",
241                "type": "string"
242            },
243            {
244                "internalType": "string",
245                "name": "base64Image",
246                "type": "string"
247            },
248            {
249                "internalType": "string",
250                "name": "faction",
251                "type": "string"
252            }
253        ],
254        "stateMutability": "view",
255        "type": "function"
256    },
257    {
258        "inputs": [],
259        "name": "name",
260        "outputs": [
261            {
262                "internalType": "string",
263                "name": "",
264                "type": "string"
265            }
266        ],
267        "stateMutability": "view",
268        "type": "function"
269    },
270    {
271        "inputs": [],
272        "name": "nextTokenId",
273        "outputs": [
274            {
275                "internalType": "uint256",
276                "name": "",
277                "type": "uint256"
278            }
279        ],
280        "stateMutability": "view",
281        "type": "function"
282    },
283    {
284        "inputs": [
285            {
286                "internalType": "uint256",
287                "name": "tokenId",
288                "type": "uint256"
289            }
290        ],
291        "name": "uri",
292        "outputs": [
293            {
294                "internalType": "string",
295                "name": "",
296                "type": "string"
297            }
298        ],
299        "stateMutability": "view",
300        "type": "function"
301    }
302];
303
304// Web3 initialization
305let web3;
306let contract;
307
308// Initialize Ethereum connection
309async function initWeb3() {
310    // Use Infura or other provider
311    const providerUrl = 'https://mainnet.infura.io/v3/99527ad2c780409d8cc89a32fc315bf7'; // Replace with your Infura key or other provider
312    
313    try {
314        web3 = new Web3(new Web3.providers.HttpProvider(providerUrl));
315        contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS);
316        
317        await loadNFTs();
318    } catch (error) {
319        console.error('Error initializing Web3:', error);
320        statusMessage.textContent = 'Error connecting to blockchain. Please check console for details.';
321        loadingOverlay.classList.add('hidden');
322    }
323}
324
325// Load all NFTs from contract
326async function loadNFTs() {
327    try {
328        // Get total NFTs minted
329        const nextTokenId = await contract.methods.nextTokenId().call();
330        
331        if (parseInt(nextTokenId) === 0) {
332            displayEmptyState();
333            return;
334        }
335        
336        // Array to hold all token promises
337        const tokenPromises = [];
338        
339        // Fetch data for all tokens
340        for (let i = 0; i < nextTokenId; i++) {
341            tokenPromises.push(fetchTokenData(i));
342        }
343        
344        // Wait for all tokens to be fetched
345        const tokenData = await Promise.all(tokenPromises);
346        
347        // Display tokens
348        displayNFTs(tokenData);
349        
350    } catch (error) {
351        console.error('Error loading NFTs:', error);
352        statusMessage.textContent = 'Error loading NFTs. Please check console for details.';
353    }
354    
355    // Hide loading overlay
356    loadingOverlay.classList.add('hidden');
357}
358
359// Fetch metadata for a specific token
360async function fetchTokenData(tokenId) {
361    try {
362        // Get token URI
363        const uri = await contract.methods.uri(tokenId).call();
364        
365        // Parse data URI format (data:application/json;base64,...)
366        const jsonBase64 = uri.split('base64,')[1];
367        const jsonString = atob(jsonBase64);
368        const metadata = JSON.parse(jsonString);
369        
370        // Get additional character data from contract
371        const characterData = await contract.methods.characters(tokenId).call();
372        
373        return {
374            id: tokenId,
375            metadata: metadata,
376            characterData: characterData
377        };
378    } catch (error) {
379        console.error(`Error fetching data for token ${tokenId}:`, error);
380        return null;
381    }
382}
383
384// Display NFTs in grid
385function displayNFTs(tokenData) {
386    // Filter out any null values from failed fetches
387    const validTokens = tokenData.filter(token => token !== null);
388    
389    if (validTokens.length === 0) {
390        displayEmptyState();
391        return;
392    }
393    
394    // Clear status message
395    statusMessage.style.display = 'none';
396    
397    // Clear existing grid
398    charactersGrid.innerHTML = '';
399    
400    // Add each token to the grid
401    validTokens.forEach(token => {
402        const { id, metadata, characterData } = token;
403        
404        // Create card element
405        const card = document.createElement('div');
406        card.className = 'character-card';
407        card.setAttribute('data-token-id', id);
408        
409        // Get role from first core trait if available
410        const role = metadata.attributes && 
411                    metadata.attributes.find(attr => attr.trait_type === 'Core Trait 1')?.value || 
412                    'Unknown Role';
413        
414        // Create card HTML
415        card.innerHTML = `
416            <div class="character-image">
417                ${getSvgFromBase64(metadata.image)}
418            </div>
419            <div class="character-content">
420                <h3 class="character-name">${metadata.name}</h3>
421                <p class="character-role">${role}</p>
422                <p class="character-description">${truncateText(metadata.description, 100)}</p>
423            </div>
424        `;
425        
426        // Add card to grid
427        charactersGrid.appendChild(card);
428        
429        // Add click event to open modal
430        card.addEventListener('click', () => {
431            openNFTModal(token);
432        });
433    });
434}
435
436// Display empty state when no NFTs are found
437function displayEmptyState() {
438    charactersGrid.innerHTML = `
439        <div class="empty-state">
440            <h3>No NFTs Found</h3>
441            <p>There are currently no Captain Ethereum Pre-Series NFTs in this collection.</p>
442        </div>
443    `;
444    statusMessage.style.display = 'none';
445}
446
447// Extract SVG from base64 image data
448function getSvgFromBase64(base64Image) {
449    if (!base64Image) return '<div>Image not available</div>';
450    
451    // If it's already SVG data
452    if (base64Image.startsWith('<svg') || base64Image.includes('<svg')) {
453        return base64Image;
454    }
455    
456    // If it's a data URI
457    if (base64Image.startsWith('data:')) {
458        // For SVG
459        if (base64Image.includes('data:image/svg+xml;')) {
460            try {
461                const svgBase64 = base64Image.split('base64,')[1];
462                return atob(svgBase64);
463            } catch (e) {
464                console.error('Error decoding SVG:', e);
465                return '<div>Error loading image</div>';
466            }
467        }
468        
469        // For other image formats, use an img tag
470        return `<img src="${base64Image}" alt="NFT" />`;
471    }
472    
473    // If it's a URL
474    return `<img src="${base64Image}" alt="NFT" />`;
475}
476
477// Open modal with NFT details
478function openNFTModal(token) {
479    const { id, metadata, characterData } = token;
480    
481    // Set modal content
482    modalImage.innerHTML = getSvgFromBase64(metadata.image);
483    modalName.textContent = metadata.name;
484    
485    // Get role from first core trait if available
486    const role = metadata.attributes && 
487                metadata.attributes.find(attr => attr.trait_type === 'Core Trait 1')?.value || 
488                'Unknown Role';
489    modalRole.textContent = role;
490    
491    // Set description
492    modalDescription.textContent = metadata.description;
493    
494    // Set faction
495    modalFaction.textContent = characterData.faction || 'Unknown';
496    
497    // Clear previous metadata
498    modalMetadata.innerHTML = '';
499    
500    // Add core traits to metadata
501    const coreTraits = metadata.attributes.filter(attr => 
502        attr.trait_type.startsWith('Core Trait') && 
503        attr.value && 
504        attr.value !== 'NA' && 
505        attr.value !== '-1' && 
506        attr.value !== 'Unknown'
507    );
508    
509    if (coreTraits.length > 0) {
510        coreTraits.forEach(trait => {
511            const traitElement = document.createElement('div');
512            traitElement.className = 'metadata-item';
513            traitElement.innerHTML = `
514                <span class="metadata-label">${trait.trait_type}</span>
515                <span class="metadata-value">${trait.value}</span>
516            `;
517            modalMetadata.appendChild(traitElement);
518        });
519    }
520    
521    // Add token ID to metadata
522    const tokenIdElement = document.createElement('div');
523    tokenIdElement.className = 'metadata-item';
524    tokenIdElement.innerHTML = `
525        <span class="metadata-label">Token ID</span>
526        <span class="metadata-value">${id}</span>
527    `;
528    modalMetadata.appendChild(tokenIdElement);
529    
530    // Clear previous powers
531    modalPowers.innerHTML = '';
532    
533    // Add powers
534    const powers = metadata.attributes.filter(attr => attr.trait_type === 'Power');
535    
536    if (powers.length > 0) {
537        powers.forEach(power => {
538            const powerElement = document.createElement('li');
539            powerElement.textContent = power.value;
540            modalPowers.appendChild(powerElement);
541        });
542    } else {
543        const noPowersElement = document.createElement('li');
544        noPowersElement.textContent = 'No special powers';
545        modalPowers.appendChild(noPowersElement);
546    }
547    
548    // Show modal
549    nftModal.style.display = 'block';
550}
551
552// Close modal
553function closeNFTModal() {
554    nftModal.style.display = 'none';
555}
556
557// Helper function to truncate text
558function truncateText(text, maxLength) {
559    if (!text) return '';
560    
561    if (text.length <= maxLength) return text;
562    
563    return text.substring(0, maxLength) + '...';
564}
565
566// Add background Ethereum symbols
567function addBackgroundSymbols() {
568    const heroSection = document.querySelector('.hero');
569    const bgAnimation = document.createElement('div');
570    bgAnimation.className = 'bg-animation';
571    
572    // Add Ethereum symbols
573    for (let i = 0; i < 20; i++) {
574        const symbol = document.createElement('div');
575        symbol.className = 'eth-symbol';
576        symbol.innerHTML = `
577            <svg width="24" height="36" viewBox="0 0 24 36" fill="white" xmlns="http://www.w3.org/2000/svg">
578                <path d="M11.9978 0L11.7783 0.741708V24.3193L11.9978 24.5387L23.9957 17.3821L11.9978 0Z" />
579                <path d="M11.9979 0L0 17.3821L11.9979 24.5387V13.1195V0Z" />
580                <path d="M11.9978 26.5754L11.8739 26.7262V35.1721L11.9978 35.5364L24.0001 19.4225L11.9978 26.5754Z" />
581                <path d="M11.9979 35.5364V26.5754L0 19.4225L11.9979 35.5364Z" />
582                <path d="M11.9978 24.5386L23.9956 17.382L11.9978 13.1194V24.5386Z" />
583                <path d="M0 17.382L11.9979 24.5386V13.1194L0 17.382Z" />
584            </svg>
585        `;
586        
587        // Random positioning
588        const size = Math.random() * 30 + 10; // 10-40px
589        symbol.style.width = `${size}px`;
590        symbol.style.height = `${size * 1.5}px`; // Maintain aspect ratio
591        
592        symbol.style.left = `${Math.random() * 100}%`;
593        symbol.style.bottom = `-${size}px`;
594        
595        // Animation duration and delay
596        const duration = Math.random() * 15 + 10; // 10-25s
597        const delay = Math.random() * 15;
598        symbol.style.animationDuration = `${duration}s`;
599        symbol.style.animationDelay = `${delay}s`;
600        
601        bgAnimation.appendChild(symbol);
602    }
603    
604    heroSection.appendChild(bgAnimation);
605}
606
607// Event listeners
608document.addEventListener('DOMContentLoaded', () => {
609    // Initialize
610    initWeb3();
611    
612    // Add background animation
613    addBackgroundSymbols();
614    
615    // Modal close button
616    closeModal.addEventListener('click', closeNFTModal);
617    
618    // Close modal when clicking outside
619    window.addEventListener('click', (event) => {
620        if (event.target === nftModal) {
621            closeNFTModal();
622        }
623    });
624    
625    // Close modal with escape key
626    document.addEventListener('keydown', (event) => {
627        if (event.key === 'Escape') {
628            closeNFTModal();
629        }
630    });
631});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.