PageSourceSearch

https://crypto-graveyard-projects.vercel.app/script.js

js crypto-graveyard-projects.vercel.app collected 2026-10-03 06:30:04 UTC 16,380 bytes, 436 lines download raw bytes

1// ===================================
2// Web3 Graveyard - Main JavaScript
3// Infinite Scroll + Brutalist Design
4// ===================================
5
6// State Management
7const state = {
8    projects: [],
9    filteredProjects: [],
10    displayedCount: 0,
11    itemsPerBatch: 9,
12    isLoading: false,
13    currentCategory: 'all',
14    searchQuery: ''
15};
16
17// DOM Elements
18const elements = {
19    searchInput: document.getElementById('searchInput'),
20    resultCount: document.getElementById('resultCount'),
21    categoryFilters: document.getElementById('categoryFilters'),
22    projectsGrid: document.getElementById('projectsGrid'),
23    loadingIndicator: document.getElementById('loadingIndicator'),
24    modalOverlay: document.getElementById('modalOverlay'),
25    modalContent: document.getElementById('modalContent'),
26    modalClose: document.getElementById('modalClose')
27};
28
29// Initialize Application
30function init() {
31    if (typeof cryptoGraveyardData !== 'undefined') {
32        state.projects = cryptoGraveyardData.projects;
33        state.filteredProjects = [...state.projects];
34
35        // Load initial batch
36        loadMoreProjects();
37
38        // Setup event listeners
39        setupEventListeners();
40        setupInfiniteScroll();
41    } else {
42        console.error('Data not loaded');
43    }
44}
45
46// Setup Event Listeners
47function setupEventListeners() {
48    // Search functionality
49    elements.searchInput.addEventListener('input', debounce(handleSearch, 300));
50
51    // Category filter buttons
52    elements.categoryFilters.addEventListener('click', (e) => {
53        const btn = e.target.closest('.filter-btn');
54        if (btn) {
55            handleCategoryFilter(btn);
56        }
57    });
58
59    // Modal functionality
60    elements.modalClose.addEventListener('click', closeModal);
61    elements.modalOverlay.addEventListener('click', (e) => {
62        if (e.target === elements.modalOverlay) closeModal();
63    });
64
65    // Keyboard navigation
66    document.addEventListener('keydown', (e) => {
67        if (e.key === 'Escape') closeModal();
68    });
69
70    // Smooth scroll for hero buttons
71    const exploreBtn = document.getElementById('exploreBtn');
72    const lessonsBtn = document.getElementById('lessonsBtn');
73
74    const scrollToContent = (e) => {
75        e.preventDefault();
76        const target = document.getElementById('searchSection');
77        if (target) {
78            target.scrollIntoView({ behavior: 'smooth', block: 'start' });
79        }
80    };
81
82    if (exploreBtn) {
83        exploreBtn.addEventListener('click', scrollToContent);
84    }
85
86    if (lessonsBtn) {
87        lessonsBtn.addEventListener('click', scrollToContent);
88    }
89}
90
91// Setup Infinite Scroll
92function setupInfiniteScroll() {
93    const observer = new IntersectionObserver((entries) => {
94        entries.forEach(entry => {
95            if (entry.isIntersecting && !state.isLoading) {
96                loadMoreProjects();
97            }
98        });
99    }, {
100        rootMargin: '200px'
101    });
102
103    observer.observe(elements.loadingIndicator);
104}
105
106// Debounce Function
107function debounce(func, wait) {
108    let timeout;
109    return function executedFunction(...args) {
110        const later = () => {
111            clearTimeout(timeout);
112            func(...args);
113        };
114        clearTimeout(timeout);
115        timeout = setTimeout(later, wait);
116    };
117}
118
119// Handle Search
120function handleSearch(e) {
121    state.searchQuery = e.target.value.toLowerCase().trim();
122    resetAndFilter();
123}
124
125// Handle Category Filter
126function handleCategoryFilter(button) {
127    // Update active state
128    document.querySelectorAll('.filter-btn').forEach(btn => {
129        btn.classList.remove('active');
130    });
131    button.classList.add('active');
132
133    state.currentCategory = button.dataset.filter;
134    resetAndFilter();
135}
136
137// Reset and Filter
138function resetAndFilter() {
139    state.displayedCount = 0;
140    elements.projectsGrid.innerHTML = '';
141
142    // Apply filters
143    state.filteredProjects = state.projects.filter(project => {
144        // Search filter
145        const matchesSearch = state.searchQuery === '' ||
146            project.name.toLowerCase().includes(state.searchQuery) ||
147            project.description.toLowerCase().includes(state.searchQuery) ||
148            project.category.toLowerCase().includes(state.searchQuery) ||
149            project.failureReasons.some(r => r.toLowerCase().includes(state.searchQuery));
150
151        // Category filter
152        const matchesCategory = state.currentCategory === 'all' ||
153            project.category === state.currentCategory;
154
155        return matchesSearch && matchesCategory;
156    });
157
158    // Sort by losses (highest first)
159    state.filteredProjects.sort((a, b) => {
160        return parseLoss(b.estimatedLosses) - parseLoss(a.estimatedLosses);
161    });
162
163    updateResultCount();
164    loadMoreProjects();
165}
166
167// Parse Loss Value
168function parseLoss(lossString) {
169    if (!lossString || lossString === 'N/A' || lossString === 'Undisclosed') return 0;
170
171    const match = lossString.match(/\$?([\d.]+)\s*(billion|million|B|M)?/i);
172    if (!match) return 0;
173
174    let value = parseFloat(match[1]);
175    const unit = (match[2] || '').toLowerCase();
176
177    if (unit === 'billion' || unit === 'b') value *= 1000000000;
178    else if (unit === 'million' || unit === 'm') value *= 1000000;
179
180    return value;
181}
182
183// Load More Projects
184function loadMoreProjects() {
185    if (state.isLoading) return;
186    if (state.displayedCount >= state.filteredProjects.length) {
187        elements.loadingIndicator.classList.remove('visible');
188        return;
189    }
190
191    state.isLoading = true;
192    elements.loadingIndicator.classList.add('visible');
193
194    // Simulate loading delay for effect
195    setTimeout(() => {
196        const start = state.displayedCount;
197        const end = Math.min(start + state.itemsPerBatch, state.filteredProjects.length);
198        const projectsToAdd = state.filteredProjects.slice(start, end);
199
200        projectsToAdd.forEach(project => {
201            const card = createProjectCard(project);
202            elements.projectsGrid.appendChild(card);
203        });
204
205        state.displayedCount = end;
206        state.isLoading = false;
207
208        if (state.displayedCount >= state.filteredProjects.length) {
209            elements.loadingIndicator.classList.remove('visible');
210        }
211    }, 300);
212}
213
214// Create Project Card
215function createProjectCard(project) {
216    const card = document.createElement('article');
217    card.className = 'project-card';
218    card.dataset.id = project.id;
219
220    const year = project.shutdownDate ? new Date(project.shutdownDate).getFullYear() : 'N/A';
221    const burned = formatBurned(project.estimatedLosses);
222    const ratings = generateRatings(project);
223
224    // SVG Icons
225    const tagIcon = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.36.86.58 1.41.58.55 0 1.05-.22 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z"/></svg>';
226    const skullIcon = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12c0 3.69 2.47 6.86 6 8.25V22h8v-1.75c3.53-1.39 6-4.56 6-8.25 0-5.52-4.48-10-10-10zm-2 16h-1v-2h1v2zm6 0h-1v-2h1v2zm1-5.5c0 .28-.22.5-.5.5h-9c-.28 0-.5-.22-.5-.5v-1c0-.28.22-.5.5-.5H8V9c0-2.21 1.79-4 4-4s4 1.79 4 4v2h.5c.28 0 .5.22.5.5v1z"/></svg>';
227    const wrenchIcon = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"/></svg>';
228    const chartIcon = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"/></svg>';
229    const targetIcon = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8zm-5-9h4V7h2v4h4v2h-4v4h-2v-4H7z"/></svg>';
230
231    card.innerHTML = `
232        <div class="card-header">
233            <div class="card-top">
234                <h3 class="card-name">${project.name}</h3>
235                <span class="card-year">${tagIcon}${year}</span>
236            </div>
237            <p class="card-description">${project.description}</p>
238        </div>
239        <div class="card-body">
240            <div class="card-meta">
241                <span class="card-burned">${burned} BURNED</span>
242                <span class="card-category">${skullIcon}${formatCategory(project.category)}</span>
243            </div>
244            <div class="card-hover-text">HOVER FOR DETAILS</div>
245        </div>
246        <div class="card-footer">
247            <div class="card-ratings">
248                <div class="rating-item">
249                    <span class="rating-label rebuild">${wrenchIcon}REBUILD</span>
250                    <div class="rating-bars">${createRatingBars(ratings.rebuild)}</div>
251                </div>
252                <div class="rating-item">
253                    <span class="rating-label scale">${chartIcon}SCALE</span>
254                    <div class="rating-bars">${createRatingBars(ratings.scale)}</div>
255                </div>
256                <div class="rating-item">
257                    <span class="rating-label market">${targetIcon}MARKET</span>
258                    <div class="rating-bars">${createRatingBars(ratings.market)}</div>
259                </div>
260            </div>
261        </div>
262    `;
263
264    card.addEventListener('click', () => openModal(project));
265    return card;
266}
267
268// Format Burned Amount
269function formatBurned(losses) {
270    if (!losses || losses === 'N/A' || losses === 'Undisclosed') return '???';
271
272    const match = losses.match(/\$?([\d.]+)\s*(billion|million|B|M|\+)?/i);
273    if (!match) return '???';
274
275    const value = parseFloat(match[1]);
276    const unit = (match[2] || '').toLowerCase();
277
278    if (unit === 'billion' || unit === 'b' || unit === 'billion+') {
279        return `$${value}B`;
280    } else if (unit === 'million' || unit === 'm' || unit === 'million+') {
281        return `$${value}M`;
282    } else if (value >= 1000) {
283        return `$${(value / 1000).toFixed(1)}B`;
284    }
285    return `$${value}M`;
286}
287
288// Format Category
289function formatCategory(category) {
290    const categoryMap = {
291        'Exchange': 'EXCHANGE',
292        'DeFi': 'DEFI',
293        'NFT': 'NFT',
294        'Stablecoin': 'STABLECOIN',
295        'CryptoLender': 'LENDER',
296        'Bridge': 'BRIDGE',
297        'GameFi': 'GAMEFI',
298        'RugPull': 'RUG PULL',
299        'Infrastructure': 'INFRA',
300        'Layer1Blockchain': 'LAYER 1'
301    };
302    return categoryMap[category] || category.toUpperCase();
303}
304
305// Generate Ratings (pseudo-ratings based on project data)
306function generateRatings(project) {
307    // Generate ratings based on failure type and losses
308    const isRugPull = project.category === 'RugPull' ||
309        project.failureReasons.some(r => r.toLowerCase().includes('rug') || r.toLowerCase().includes('fraud') || r.toLowerCase().includes('ponzi'));
310
311    const hasRecovery = project.legalOutcome &&
312        (project.legalOutcome.toLowerCase().includes('return') ||
313         project.legalOutcome.toLowerCase().includes('recover') ||
314         project.legalOutcome.toLowerCase().includes('refund'));
315
316    const loss = parseLoss(project.estimatedLosses);
317    const lossScore = loss > 1000000000 ? 1 : loss > 100000000 ? 2 : loss > 10000000 ? 3 : 4;
318
319    return {
320        rebuild: isRugPull ? 1 : (hasRecovery ? 4 : 2 + Math.floor(Math.random() * 2)),
321        scale: Math.min(5, lossScore + Math.floor(Math.random() * 2)),
322        market: isRugPull ? 1 : 2 + Math.floor(Math.random() * 3)
323    };
324}
325
326// Create Rating Bars
327function createRatingBars(rating) {
328    let bars = '';
329    for (let i = 0; i < 5; i++) {
330        if (i < rating) {
331            bars += '<div class="rating-bar filled"></div>';
332        } else {
333            bars += '<div class="rating-bar"></div>';
334        }
335    }
336    return bars;
337}
338
339// Update Result Count
340function updateResultCount() {
341    const count = state.filteredProjects.length;
342    elements.resultCount.textContent = `${count} CORPSE${count !== 1 ? 'S' : ''} FOUND`;
343}
344
345// Format Date
346function formatDate(dateString) {
347    if (!dateString) return 'N/A';
348    const date = new Date(dateString);
349    return date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' });
350}
351
352// Open Modal
353function openModal(project) {
354    elements.modalContent.innerHTML = createModalContent(project);
355    elements.modalOverlay.classList.add('visible');
356    document.body.style.overflow = 'hidden';
357}
358
359// Close Modal
360function closeModal() {
361    elements.modalOverlay.classList.remove('visible');
362    document.body.style.overflow = '';
363}
364
365// Create Modal Content
366function createModalContent(project) {
367    return `
368        <div class="modal-header">
369            <span class="modal-category-badge">${formatCategory(project.category)}</span>
370            <h2 class="modal-title">${project.name}</h2>
371            <p class="modal-dates">${formatDate(project.launchDate)} → ${formatDate(project.shutdownDate)}</p>
372        </div>
373        <div class="modal-body">
374            <div class="modal-section">
375                <h4 class="modal-section-title">WHAT HAPPENED</h4>
376                <p class="modal-section-content">${project.description}</p>
377            </div>
378
379            <div class="modal-section">
380                <h4 class="modal-section-title">THE DAMAGE</h4>
381                <div class="modal-stats-grid">
382                    <div class="modal-stat">
383                        <div class="modal-stat-label">ESTIMATED LOSSES</div>
384                        <div class="modal-stat-value loss">${project.estimatedLosses || 'N/A'}</div>
385                    </div>
386                    <div class="modal-stat">
387                        <div class="modal-stat-label">PEAK VALUATION</div>
388                        <div class="modal-stat-value">${project.peakValuation || 'N/A'}</div>
389                    </div>
390                    <div class="modal-stat">
391                        <div class="modal-stat-label">FUNDING RAISED</div>
392                        <div class="modal-stat-value">
392${project.fundingRaised || 'N/A'}</div>
393                    </div>
394                    <div class="modal-stat">
395                        <div class="modal-stat-label">AFFECTED USERS</div>
396                        <div class="modal-stat-value">${project.affectedUsers || 'N/A'}</div>
397                    </div>
398                </div>
399            </div>
400
401            <div class="modal-section">
402                <h4 class="modal-section-title">CAUSE OF DEATH</h4>
403                <div class="failure-reasons">
404                    ${project.failureReasons.map(reason =>
405                        `<span class="failure-reason">${reason}</span>`
406                    ).join('')}
407                </div>
408            </div>
409
410            <div class="modal-section">
411                <h4 class="modal-section-title">LEGAL OUTCOME</h4>
412                <div class="modal-legal">
413                    ${project.legalOutcome || 'No legal action taken or outcome unknown.'}
414                </div>
415            </div>
416
417            ${project.revivalIdeas &&
418              project.revivalIdeas[0] !== 'N/A - was never a legitimate project' &&
419              project.revivalIdeas[0] !== 'N/A - was allegedly fraudulent from inception' &&
420              project.revivalIdeas[0] !== 'N/A - company wound down' &&
421              project.revivalIdeas[0] !== 'N/A - was fraudulent from inception' ? `
422            <div class="modal-section">
423                <h4 class="modal-section-title">REVIVAL IDEAS</h4>
424                <ul class="revival-ideas">
425                    ${project.revivalIdeas.map(idea =>
426                        `<li><svg viewBox="0 0 24 24" fill="currentColor"><path d="M9 21c0 .55.45 1 1 1h4c.55 0 1-.45 1-1v-1H9v1zm3-19C8.14 2 5 5.14 5 9c0 2.38 1.19 4.47 3 5.74V17c0 .55.45 1 1 1h6c.55 0 1-.45 1-1v-2.26c1.81-1.27 3-3.36 3-5.74 0-3.86-3.14-7-7-7zm2.85 11.1l-.85.6V16h-4v-2.3l-.85-.6C7.8 12.16 7 10.63 7 9c0-2.76 2.24-5 5-5s5 2.24 5 5c0 1.63-.8 3.16-2.15 4.1z"/></svg>${idea}</li>`
427                    ).join('')}
428                </ul>
429            </div>
430            ` : ''}
431        </div>
432    `;
433}
434
435// Initialize when DOM is ready
436document.addEventListener('DOMContentLoaded', init);

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.