PageSourceSearch

https://www.bingeclock.com/js/internal/vendingCards.js?v=12135

js bingeclock.com collected 2026-09-25 20:12:16 UTC 17,203 bytes, 474 lines download raw bytes

1/**
2 * Wolf Card Vending System - Frontend
3 * 
4 * Allows users to sell their cards to dummy users for points.
5 * Features dynamic pricing based on edition numbers and daily market modifiers.
6 */
7
8import { playBingeSound } from 'internal/audioManager';
9import { apiMyRewards, apiWolfQuote, apiWolfAccept } from 'internal/endpoints';
10import { contentLoadingGeneric } from 'internal/loading';
11import { showMiniModal } from 'internal/modals';
12import { dataApiCall, dataApiPostOrPut } from 'internal/requestHandling';
13import { showFullscreenOverlay, closeFullscreenOverlay } from 'internal/fullscreenOverlay';
14import { fetchAndDisplayPoints } from 'internal/points';
15
16// Store current vending state
17let userCards = [];
18let selectedCard = null;
19let currentQuote = null;
20let quoteTimer = null;
21
22// Badge shape mapping
23const BADGE_SHAPES = {
24    1: 'badge-series-1',  // Circle
25    2: 'badge-series-2',  // Square
26    3: 'badge-series-3',  // Hexagon
27    4: 'badge-series-4',  // Shield
28    5: 'badge-series-5'   // Star
29};
30
31function getBadgeClass(seriesNumber) {
32    return BADGE_SHAPES[seriesNumber] || BADGE_SHAPES[1];
33}
34
35/**
36 * Add parallax effect to card on mouse move
37 */
38function addCardParallax(cardElement) {
39    cardElement.addEventListener('mousemove', (e) => {
40        const rect = cardElement.getBoundingClientRect();
41        const x = e.clientX - rect.left;
42        const y = e.clientY - rect.top;
43        const centerX = rect.width / 2;
44        const centerY = rect.height / 2;
45        const rotateX = (y - centerY) / 25;
46        const rotateY = (centerX - x) / 25;
47        
48        const frame = cardElement.querySelector('.card-frame');
49        if (frame) {
50            frame.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
51        }
52    });
53    
54    cardElement.addEventListener('mouseleave', () => {
55        const frame = cardElement.querySelector('.card-frame');
56        if (frame) {
57            frame.style.transform = '';
58        }
59    });
60}
61
62/**
63 * Main entry point - Show wolf vending modal
64 */
65export async function showWolfVendingModal() {
66    const loadingContent = `
67        <div class="wolf-vending-fullscreen">
68            <h2 class="wolf-vending-title">Sell Cards to Wolf Market</h2>
69            <div class="wolf-vending-content">
70                ${contentLoadingGeneric}
71            </div>
72        </div>
73    `;
74    
75    showFullscreenOverlay(loadingContent, resetVendingState, []);
76    
77    try {
78        // Fetch cards and ensure minimum 1 second loading for smooth UX
79        const [cardsData] = await Promise.all([
80            dataApiCall(apiMyRewards()),
81            new Promise(resolve => setTimeout(resolve, 1000))
82        ]);
83        
84        userCards = cardsData.all_rewards || [];
85        
86        // Filter to only cards with copies
87        userCards = userCards.filter(card => card.copies && card.copies.length > 0);
88        
89        if (userCards.length === 0) {
90            const noCardsContent = `
91                <div class="wolf-vending-fullscreen">
92                    <h2 class="wolf-vending-title">Sell Cards to Wolf Market</h2>
93                    <div class="wolf-vending-content">
94                        <p class="wolf-vending-message">You don't have any cards to sell yet.</p>
95                    </div>
96                </div>
97            `;
98            showFullscreenOverlay(noCardsContent, resetVendingState, []);
99            return;
100        }
101        
102        renderCardSelector();
103    } catch (error) {
104        console.error('Error loading cards:', error);
105        const errorContent = `
106            <div class="wolf-vending-fullscreen">
107                <h2 class="wolf-vending-title">Sell Cards to Wolf Market</h2>
108                <div class="wolf-vending-content">
109                    <p class="wolf-vending-error">Unable to load your cards. Please try again later.</p>
110                </div>
111            </div>
112        `;
113        showFullscreenOverlay(errorContent, resetVendingState, []);
114    }
115}
116
117/**
118 * Render card selector with dropdown
119 */
120function renderCardSelector() {
121    // Build dropdown options - flatten all card editions
122    let cardOptions = '<option value="">-- Select a card to sell --</option>';
123    
124    userCards.forEach(card => {
125        card.copies.forEach(copy => {
126            const optionValue = `${card.urlname}|${copy.edition_number}`;
127            cardOptions += `<option value="${optionValue}">${card.title} (No. ${copy.edition_number})</option>`;
128        });
129    });
130    
131    const overlayContent = `
132        <div class="wolf-vending-fullscreen">
133            <h2 class="wolf-vending-title">Sell Cards to Wolf Market</h2>
134            <div class="wolf-vending-modal-content">
135                <p class="wolf-vending-instructions">Select a card to get a price quote from Wolf Market.</p>
136                <div class="wolf-vending-footer">
137                    <label class="trade-footer-label">Your card:</label>
138                    <select id="wolf-card-select" class="user-card-select" onchange="onSelectCard(this.value)">
139                        ${cardOptions}
140                    </select>
141                    <button id="get-quote-btn" class="confirm-trade-btn" disabled onclick="requestQuote()">Get Quote</button>
142                    <button class="cancel-trade-btn" onclick="closeFullscreenOverlay()">Cancel</button>
143                </div>
144            </div>
145        </div>
146    `;
147    
148    showFullscreenOverlay(overlayContent, resetVendingState, ['.wolf-vending-footer', '.user-card-select', '.confirm-trade-btn', '.cancel-trade-btn']);
149}
150
151/**
152 * Handle card dropdown selection
153 */
154export function onSelectCard(value) {
155    if (!value) {
156        selectedCard = null;
157    } else {
158        const [urlname, editionNumber] = value.split('|');
159        const cardData = userCards.find(c => c.urlname === urlname);
160        
161        if (cardData) {
162            selectedCard = {
163                urlname,
164                edition_number: parseInt(editionNumber),
165                title: cardData.title,
166                tagline: cardData.tagline,
167                secret_image: cardData.secret_image,
168                series_number: cardData.series_number,
169                card_number: cardData.card_number
170            };
171        }
172    }
173    
174    // Update get quote button state
175    const getQuoteBtn = document.getElementById('get-quote-btn');
176    if (getQuoteBtn) {
177        getQuoteBtn.disabled = !selectedCard;
178    }
179}
180
181/**
182 * Request a quote for the selected card
183 */
184export async function requestQuote() {
185    if (!selectedCard) {
186        showMiniModal('Error', 'Please select a card');
187        return;
188    }
189    
190    await selectCardForQuote(selectedCard.urlname, selectedCard.edition_number);
191}
192
193/**
194 * User selected a card - get quote
195 */
196async function selectCardForQuote(urlname, editionNumber) {
197    // Find the card data
198    const cardData = userCards.find(c => c.urlname === urlname);
199    if (!cardData) {
200        showMiniModal('Error', 'Card not found');
201        return;
202    }
203    
204    selectedCard = {
205        urlname,
206        edition_number: editionNumber,
207        title: cardData.title,
208        tagline: cardData.tagline,
209        secret_image: cardData.secret_image,
210        series_number: cardData.series_number,
211        card_number: cardData.card_number
212    };
213    
214    // Show loading state
215    const loadingContent = `
216        <div class="wolf-vending-fullscreen">
217            <h2 class="wolf-vending-title">Getting Quote...</h2>
218            <div class="wolf-vending-content">
219                ${contentLoadingGeneric}
220            </div>
221        </div>
222    `;
223    showFullscreenOverlay(loadingContent, resetVendingState, []);
224    
225    try {
226        // Get quote with minimum 1 second loading for smooth UX
227        const [response] = await Promise.all([
228            dataApiPostOrPut(apiWolfQuote(), {
229                urlname: urlname,
230                edition_number: editionNumber
231            }, 'POST'),
232            new Promise(resolve => setTimeout(resolve, 1000))
233        ]);
234        
235        const result = await response.json();
236        
237        if (result.error) {
238            showMiniModal('Error', result.error);
239            renderCardSelector(); // Go back to card selection
240            return;
241        }
242        
243        currentQuote = result;
244        displayQuote(result);
245        
246    } catch (error) {
247        console.error('Error getting quote:', error);
248        showMiniModal('Error', 'Unable to get quote. Please try again.');
249        renderCardSelector(); // Go back to card selection
250    }
251}
252
253/**
254 * Display the quote with countdown timer
255 */
256function displayQuote(quoteData) {
257    const secretImage = `https://bc-rewards.nyc3.cdn.digitaloceanspaces.com/bc-rewards/${quoteData.secret_image}.png`;
258    const seriesNumber = quoteData.series_number !== undefined && quoteData.series_number !== null ? quoteData.series_number : 1;
259    const badgeClass = getBadgeClass(seriesNumber);
260    const cardNumber = selectedCard && selectedCard.card_number !== undefined && selectedCard.card_number !== null ? selectedCard.card_number : quoteData.edition_number;
261    
262    // Determine if modifier is positive or negative
263    // From seller's perspective: > 1.0 is GOOD (more points), < 1.0 is BAD (less points)
264    const modifierValue = parseFloat(quoteData.daily_modifier);
265    const isBonus = modifierValue > 1.0;
266    const modifierClass = isBonus ? 'market-discount' : 'market-premium';
267    const modifierArrow = isBonus ? '↑' : '↓';
268    
269    const overlayContent = `
270        <div class="wolf-vending-fullscreen">
271            <h2 class="wolf-vending-title">Wolf Market Quote</h2>
272            <div class="wolf-vending-modal-content">
273                <p class="wolf-vending-instructions">Review your quote and accept to complete the sale.</p>
274                <div class="wolf-vending-quote-display">
275                    <div class="wolf-vending-card-container">
276                        <div class="trade-card shelf-card trading-card">
277                            <div class="card-frame">
278                                <div class="card-badges-container">
279                                    <div class="card-edition-badge ${badgeClass}">#${cardNumber}</div>
280                                    <div class="card-series-label">Series ${seriesNumber}</div>
281                                </div>
282                                <div class="card-art-frame">
283                                    <img src="${secretImage}" alt="${quoteData.title}" class="card-art">
284                                </div>
285                                <div class="card-info">
286                                    <h3 class="card-title">${quoteData.title}</h3>
287                                </div>
288                            </div>
289                        </div>
290                    </div>
291                    <div class="wolf-vending-quote-details">
292                        <div class="wolf-vending-quote-pricing">
293                            <div class="wolf-vending-quote-row">
294                                <span class="wolf-vending-quote-label">Base Price:</span>
295                                <span class="wolf-vending-quote-value">${quoteData.base_price} pts</span>
296                            </div>
297                            <div class="wolf-vending-quote-row">
298                                <span class="wolf-vending-quote-label">Market Today:</span>
299                                <span class="wolf-vending-quote-value ${modifierClass}">${quoteData.daily_modifier_text} ${modifierArrow}</span>
300                            </div>
301                            <div class="wolf-vending-quote-row wolf-vending-quote-final">
302                                <span class="wolf-vending-quote-label">You'll Receive:</span>
303                                <span class="wolf-vending-quote-value wolf-vending-final-price">${quoteData.quoted_price} pts</span>
304                            </div>
305                        </div>
306                        <div class="wolf-vending-quote-timer">
307                            <span class="wolf-vending-timer-label">Quote expires in:</span>
308                            <span class="wolf-vending-timer-value" id="wolf-quote-countdown">5:00</span>
309                        </div>
310                    </div>
311                </div>
312                <div class="wolf-vending-footer">
313                    <button class="confirm-trade-btn" onclick="acceptWolfQuote()">
314                        Accept & Sell
315                    </button>
316                    <button class="cancel-trade-btn" onclick="cancelWolfQuote()">
317                        Cancel
318                    </button>
319                </div>
320            </div>
321        </div>
322    `;
323    
324    showFullscreenOverlay(overlayContent, resetVendingState, ['.trade-card', '.wolf-vending-footer', '.confirm-trade-btn', '.cancel-trade-btn']);
325    
326    // Start countdown timer
327    startQuoteCountdown(quoteData.expires_at);
328    
329    // Add parallax to card
330    setTimeout(() => {
331        const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
332        if (!prefersReducedMotion) {
333            const card = document.querySelector('.trade-card');
334            if (card) {
335                addCardParallax(card);
336            }
337        }
338    }, 100);
339}
340
341/**
342 * Start countdown timer for quote expiration
343 */
344function startQuoteCountdown(expiresAt) {
345    // Clear any existing timer
346    if (quoteTimer) {
347        clearInterval(quoteTimer);
348    }
349    
350    const expiryTime = new Date(expiresAt).getTime();
351    
352    quoteTimer = setInterval(() => {
353        const now = new Date().getTime();
354        const timeLeft = expiryTime - now;
355        
356        if (timeLeft <= 0) {
357            clearInterval(quoteTimer);
358            showMiniModal('Quote Expired', 'This quote has expired. Please get a new quote.');
359            closeFullscreenOverlay();
360            resetVendingState();
361            return;
362        }
363        
364        // Calculate minutes and seconds
365        const minutes = Math.floor(timeLeft / 60000);
366        const seconds = Math.floor((timeLeft % 60000) / 1000);
367        
368        // Update display
369        const timerElement = document.getElementById('wolf-quote-countdown');
370        if (timerElement) {
371            timerElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
372            
373            // Add warning class if less than 1 minute left
374            if (timeLeft < 60000) {
375                timerElement.classList.add('wolf-vending-timer-warning');
376            }
377        }
378    }, 1000);
379}
380
381/**
382 * Accept the quote and complete the sale
383 */
384export async function acceptWolfQuote() {
385    if (!currentQuote) {
386        showMiniModal('Error', 'No active quote');
387        return;
388    }
389    
390    // Disable button
391    const acceptBtn = document.querySelector('.wolf-vending-accept-btn');
392    if (acceptBtn) {
393        acceptBtn.disabled = true;
394        acceptBtn.textContent = 'Processing...';
395    }
396    
397    try {
398        const response = await dataApiPostOrPut(apiWolfAccept(), {
399            quote_id: currentQuote.quote_id
400        }, 'POST');
401        
402        const result = await response.json();
403        
404        if (result.error) {
405            showMiniModal('Sale Failed', result.error);
406            if (acceptBtn) {
407                acceptBtn.disabled = false;
408                acceptBtn.textContent = 'Accept & Sell';
409            }
410            return;
411        }
412        
413        // Success!
414        clearInterval(quoteTimer);
415        closeFullscreenOverlay();
416        
417        // Update points display
418        fetchAndDisplayPoints();
419
420        //playBingeSound('sale_made');
421        
422        // Show success message
423        const successHtml = `
424            <div class="wolf-vending-success-container">
425                <p class="wolf-vending-success-message">
426                    Successfully sold <strong>${result.title} (No. ${result.edition_number})</strong> for <strong>${result.cost} points</strong>!
427                </p>
428                <p class="wolf-vending-success-balance">New Balance: ${result.remaining_points}</p>
429            </div>
430        `;
431        
432        showMiniModal('Card Sold!', successHtml);
433        
434        resetVendingState();
435        
436    } catch (error) {
437        console.error('Error accepting quote:', error);
438        showMiniModal('Sale Failed', 'An error occurred. Please try again.');
439        if (acceptBtn) {
440            acceptBtn.disabled = false;
441            acceptBtn.textContent = 'Accept & Sell';
442        }
443    }
444}
445
446/**
447 * Cancel the current quote
448 */
449export function cancelWolfQuote() {
450    clearInterval(quoteTimer);
451    currentQuote = null;
452    renderCardSelector(); // Go back to card selection
453}
454
455/**
456 * Reset vending state
457 */
458function resetVendingState() {
459    if (quoteTimer) {
460        clearInterval(quoteTimer);
461        quoteTimer = null;
462    }
463    selectedCard = null;
464    currentQuote = null;
465    userCards = [];
466}
467
468// Expose functions to window for onclick handlers
469window.showWolfVendingModal = showWolfVendingModal;
470window.onSelectCard = onSelectCard;
471window.requestQuote = requestQuote;
472window.acceptWolfQuote = acceptWolfQuote;
473window.cancelWolfQuote = cancelWolfQuote;
474

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.