Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Crypto Tracker Pro - Analyse des cryptomonnaies</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <style> | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.5; } | |
| } | |
| .animate-pulse { | |
| animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; | |
| } | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%); | |
| } | |
| .coin-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| .dark-mode { | |
| background-color: #1a202c; | |
| color: #f7fafc; | |
| } | |
| .dark-mode .header { | |
| background-color: #2d3748; | |
| } | |
| .dark-mode .coin-card { | |
| background-color: #2d3748; | |
| border-color: #4a5568; | |
| } | |
| .chart-container { | |
| position: relative; | |
| height: 200px; | |
| width: 100%; | |
| } | |
| .trend-indicator { | |
| display: inline-flex; | |
| align-items: center; | |
| padding: 2px 8px; | |
| border-radius: 12px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| } | |
| .trend-up { | |
| background-color: rgba(16, 185, 129, 0.1); | |
| color: #10b981; | |
| } | |
| .trend-down { | |
| background-color: rgba(239, 68, 68, 0.1); | |
| color: #ef4444; | |
| } | |
| .trend-neutral { | |
| background-color: rgba(156, 163, 175, 0.1); | |
| color: #9ca3af; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 font-sans"> | |
| <div class="min-h-screen"> | |
| <!-- Header --> | |
| <header class="gradient-bg text-white shadow-lg"> | |
| <div class="container mx-auto px-4 py-6"> | |
| <div class="flex justify-between items-center"> | |
| <div class="flex items-center space-x-2"> | |
| <i class="fab fa-bitcoin text-3xl text-yellow-400"></i> | |
| <h1 class="text-2xl font-bold">Crypto Tracker Pro</h1> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <button id="refreshBtn" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg flex items-center"> | |
| <i class="fas fa-sync-alt mr-2"></i> Actualiser | |
| </button> | |
| <button id="darkModeToggle" class="bg-gray-800 hover:bg-gray-700 px-4 py-2 rounded-lg"> | |
| <i class="fas fa-moon"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mt-8"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div> | |
| <h2 class="text-xl font-semibold">Analyse technique des cryptomonnaies</h2> | |
| <p class="text-blue-200">Tendances, moyennes mobiles et indicateurs techniques</p> | |
| </div> | |
| <div class="mt-4 md:mt-0 relative"> | |
| <input type="text" id="searchInput" placeholder="Rechercher une crypto..." | |
| class="px-4 py-2 rounded-full w-full md:w-64 focus:outline-none focus:ring-2 focus:ring-blue-400 text-gray-800"> | |
| <i class="fas fa-search absolute right-3 top-3 text-gray-500"></i> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="container mx-auto px-4 py-8"> | |
| <!-- Market Overview --> | |
| <div class="bg-white rounded-xl shadow-md p-6 mb-8 dark-mode"> | |
| <h2 class="text-xl font-bold mb-4">Indicateurs du marché</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-4"> | |
| <div class="bg-blue-50 p-4 rounded-lg dark:bg-gray-700"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <p class="text-gray-600 dark:text-gray-300">Capitalisation totale</p> | |
| <h3 id="totalMarketCap" class="text-2xl font-bold">Chargement...</h3> | |
| <p id="marketCapChange" class="text-sm mt-1">Chargement...</p> | |
| </div> | |
| <i class="fas fa-chart-line text-blue-500 text-3xl"></i> | |
| </div> | |
| </div> | |
| <div class="bg-green-50 p-4 rounded-lg dark:bg-gray-700"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <p class="text-gray-600 dark:text-gray-300">Volume 24h</p> | |
| <h3 id="totalVolume" class="text-2xl font-bold">Chargement...</h3> | |
| <p id="volumeChange" class="text-sm mt-1">Chargement...</p> | |
| </div> | |
| <i class="fas fa-exchange-alt text-green-500 text-3xl"></i> | |
| </div> | |
| </div> | |
| <div class="bg-purple-50 p-4 rounded-lg dark:bg-gray-700"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <p class="text-gray-600 dark:text-gray-300">Dominance BTC</p> | |
| <h3 id="btcDominance" class="text-2xl font-bold">Chargement...</h3> | |
| <p id="btcDominanceChange" class="text-sm mt-1">Chargement...</p> | |
| </div> | |
| <i class="fab fa-bitcoin text-purple-500 text-3xl"></i> | |
| </div> | |
| </div> | |
| <div class="bg-yellow-50 p-4 rounded-lg dark:bg-gray-700"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <p class="text-gray-600 dark:text-gray-300">Indice de peur et avidité</p> | |
| <h3 id="fearGreed" class="text-2xl font-bold">Chargement...</h3> | |
| <p id="fearGreedText" class="text-sm mt-1">Chargement...</p> | |
| </div> | |
| <i class="fas fa-brain text-yellow-500 text-3xl"></i> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Crypto List --> | |
| <div class="mb-8"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-bold">Analyse des cryptomonnaies</h2> | |
| <div class="flex space-x-2"> | |
| <button id="sortMarketCap" class="bg-blue-500 text-white px-3 py-1 rounded text-sm"> | |
| Par capitalisation | |
| </button> | |
| <button id="sortVolume" class="bg-gray-200 hover:bg-gray-300 px-3 py-1 rounded text-sm dark:bg-gray-700 dark:hover:bg-gray-600"> | |
| Par volume | |
| </button> | |
| <button id="sortChange" class="bg-gray-200 hover:bg-gray-300 px-3 py-1 rounded text-sm dark:bg-gray-700 dark:hover:bg-gray-600"> | |
| Par variation | |
| </button> | |
| <button id="sortTrend" class="bg-gray-200 hover:bg-gray-300 px-3 py-1 rounded text-sm dark:bg-gray-700 dark:hover:bg-gray-600"> | |
| Par tendance | |
| </button> | |
| </div> | |
| </div> | |
| <div id="cryptoList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <!-- Les cartes de cryptomonnaies seront ajoutées ici par JavaScript --> | |
| <div class="animate-pulse bg-gray-200 rounded-xl p-6 h-96 dark:bg-gray-700"></div> | |
| <div class="animate-pulse bg-gray-200 rounded-xl p-6 h-96 dark:bg-gray-700"></div> | |
| <div class="animate-pulse bg-gray-200 rounded-xl p-6 h-96 dark:bg-gray-700"></div> | |
| </div> | |
| </div> | |
| <!-- Favorites Section --> | |
| <div id="favoritesSection" class="mb-8 hidden"> | |
| <h2 class="text-xl font-bold mb-4">Vos favoris</h2> | |
| <div id="favoritesList" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> | |
| <!-- Les favoris seront ajoutés ici --> | |
| </div> | |
| </div> | |
| <!-- Loading Indicator --> | |
| <div id="loadingIndicator" class="text-center py-8"> | |
| <i class="fas fa-circle-notch fa-spin text-4xl text-blue-500"></i> | |
| <p class="mt-4">Chargement des données...</p> | |
| </div> | |
| </main> | |
| <!-- Footer --> | |
| <footer class="bg-gray-800 text-white py-6"> | |
| <div class="container mx-auto px-4"> | |
| <div class="flex flex-col md:flex-row justify-between items-center"> | |
| <div class="mb-4 md:mb-0"> | |
| <p>© 2023 Crypto Tracker Pro. Tous droits réservés.</p> | |
| <p class="text-gray-400 text-sm">Données fournies par CoinGecko API et TradingView</p> | |
| </div> | |
| <div class="flex space-x-4"> | |
| <a href="#" class="hover:text-blue-300"><i class="fab fa-twitter"></i></a> | |
| <a href="#" class="hover:text-blue-300"><i class="fab fa-github"></i></a> | |
| <a href="#" class="hover:text-blue-300"><i class="fab fa-telegram"></i></a> | |
| </div> | |
| </div> | |
| </div> | |
| </footer> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Configuration | |
| const apiUrl = 'https://api.coingecko.com/api/v3'; | |
| const favoritesKey = 'cryptoFavorites'; | |
| // Éléments DOM | |
| const cryptoList = document.getElementById('cryptoList'); | |
| const favoritesList = document.getElementById('favoritesList'); | |
| const favoritesSection = document.getElementById('favoritesSection'); | |
| const loadingIndicator = document.getElementById('loadingIndicator'); | |
| const refreshBtn = document.getElementById('refreshBtn'); | |
| const darkModeToggle = document.getElementById('darkModeToggle'); | |
| const searchInput = document.getElementById('searchInput'); | |
| const totalMarketCap = document.getElementById('totalMarketCap'); | |
| const totalVolume = document.getElementById('totalVolume'); | |
| const btcDominance = document.getElementById('btcDominance'); | |
| const marketCapChange = document.getElementById('marketCapChange'); | |
| const volumeChange = document.getElementById('volumeChange'); | |
| const btcDominanceChange = document.getElementById('btcDominanceChange'); | |
| const fearGreed = document.getElementById('fearGreed'); | |
| const fearGreedText = document.getElementById('fearGreedText'); | |
| const sortMarketCap = document.getElementById('sortMarketCap'); | |
| const sortVolume = document.getElementById('sortVolume'); | |
| const sortChange = document.getElementById('sortChange'); | |
| const sortTrend = document.getElementById('sortTrend'); | |
| // Variables d'état | |
| let cryptoData = []; | |
| let favorites = JSON.parse(localStorage.getItem(favoritesKey)) || []; | |
| let currentSort = 'market_cap'; | |
| let isDarkMode = localStorage.getItem('darkMode') === 'true'; | |
| let previousGlobalData = null; | |
| // Initialisation | |
| if (isDarkMode) { | |
| document.body.classList.add('dark-mode'); | |
| darkModeToggle.innerHTML = '<i class="fas fa-sun"></i>'; | |
| } | |
| fetchData(); | |
| // Événements | |
| refreshBtn.addEventListener('click', fetchData); | |
| darkModeToggle.addEventListener('click', toggleDarkMode); | |
| searchInput.addEventListener('input', filterCryptos); | |
| sortMarketCap.addEventListener('click', () => sortCryptos('market_cap')); | |
| sortVolume.addEventListener('click', () => sortCryptos('volume')); | |
| sortChange.addEventListener('click', () => sortCryptos('price_change_percentage_24h')); | |
| sortTrend.addEventListener('click', () => sortCryptos('trend_strength')); | |
| // Fonctions | |
| async function fetchData() { | |
| try { | |
| loadingIndicator.classList.remove('hidden'); | |
| cryptoList.innerHTML = ` | |
| <div class="animate-pulse bg-gray-200 rounded-xl p-6 h-96 dark:bg-gray-700"></div> | |
| <div class="animate-pulse bg-gray-200 rounded-xl p-6 h-96 dark:bg-gray-700"></div> | |
| <div class="animate-pulse bg-gray-200 rounded-xl p-6 h-96 dark:bg-gray-700"></div> | |
| `; | |
| // Récupérer les données globales | |
| const globalResponse = await fetch(`${apiUrl}/global`); | |
| const globalData = await globalResponse.json(); | |
| updateGlobalData(globalData.data); | |
| // Récupérer l'indice de peur et avidité (simulé) | |
| updateFearGreedIndex(); | |
| // Récupérer les cryptomonnaies avec les données de prix historiques | |
| const response = await fetch(`${apiUrl}/coins/markets?vs_currency=eur&order=market_cap_desc&per_page=30&page=1&sparkline=true&price_change_percentage=24h,7d,30d`); | |
| cryptoData = await response.json(); | |
| // Ajouter des données techniques simulées (moyennes mobiles et tendances) | |
| cryptoData = await enhanceWithTechnicalData(cryptoData); | |
| renderCryptoList(cryptoData); | |
| updateFavoritesSection(); | |
| } catch (error) { | |
| console.error('Erreur lors de la récupération des données:', error); | |
| cryptoList.innerHTML = ` | |
| <div class="col-span-3 bg-red-100 border-l-4 border-red-500 text-red-700 p-4"> | |
| <p>Erreur lors du chargement des données. Veuillez réessayer.</p> | |
| </div> | |
| `; | |
| } finally { | |
| loadingIndicator.classList.add('hidden'); | |
| } | |
| } | |
| async function enhanceWithTechnicalData(cryptoData) { | |
| // Simuler des données techniques pour chaque crypto | |
| return cryptoData.map(crypto => { | |
| // Générer des moyennes mobiles simulées (7 et 30 jours) | |
| const currentPrice = crypto.current_price; | |
| const volatility = currentPrice * 0.1; // 10% de volatilité | |
| // Générer des données de prix historiques simulées si sparkline n'est pas disponible | |
| const sparklineData = crypto.sparkline_in_7d?.price || Array(7).fill(0).map((_, i) => { | |
| const daysAgo = 7 - i; | |
| const trendFactor = crypto.price_change_percentage_7d_in_currency / 100; | |
| return currentPrice * (1 - trendFactor * (daysAgo / 7) + (Math.random() * 0.1 - 0.05)); | |
| }); | |
| // Calculer les moyennes mobiles | |
| const ma7 = calculateMovingAverage(sparklineData.slice(-7)); | |
| const ma30 = calculateMovingAverage(sparklineData); | |
| // Déterminer la tendance | |
| const trendStrength = calculateTrendStrength(sparklineData); | |
| const trendDirection = trendStrength > 0.5 ? 'up' : trendStrength < -0.5 ? 'down' : 'neutral'; | |
| return { | |
| ...crypto, | |
| technicals: { | |
| ma7, | |
| ma30, | |
| trend_strength: trendStrength, | |
| trend_direction: trendDirection, | |
| sparkline: sparklineData | |
| } | |
| }; | |
| }); | |
| } | |
| function calculateMovingAverage(data) { | |
| if (!data || data.length === 0) return 0; | |
| const sum = data.reduce((a, b) => a + b, 0); | |
| return sum / data.length; | |
| } | |
| function calculateTrendStrength(data) { | |
| if (!data || data.length < 2) return 0; | |
| const first = data[0]; | |
| const last = data[data.length - 1]; | |
| const change = last - first; | |
| const range = Math.max(...data) - Math.min(...data); | |
| if (range === 0) return 0; | |
| return change / range; // Retourne une valeur entre -1 et 1 | |
| } | |
| function updateGlobalData(data) { | |
| totalMarketCap.textContent = formatCurrency(data.total_market_cap.eur); | |
| totalVolume.textContent = formatCurrency(data.total_volume.eur); | |
| btcDominance.textContent = `${data.market_cap_percentage.btc.toFixed(1)}%`; | |
| // Calculer les variations si nous avons des données précédentes | |
| if (previousGlobalData) { | |
| const marketCapChangeValue = ((data.total_market_cap.eur - previousGlobalData.total_market_cap.eur) / previousGlobalData.total_market_cap.eur) * 100; | |
| const volumeChangeValue = ((data.total_volume.eur - previousGlobalData.total_volume.eur) / previousGlobalData.total_volume.eur) * 100; | |
| const btcDominanceChangeValue = data.market_cap_percentage.btc - previousGlobalData.market_cap_percentage.btc; | |
| marketCapChange.innerHTML = formatChange(marketCapChangeValue); | |
| volumeChange.innerHTML = formatChange(volumeChangeValue); | |
| btcDominanceChange.innerHTML = formatChange(btcDominanceChangeValue, true); | |
| } | |
| previousGlobalData = data; | |
| } | |
| function updateFearGreedIndex() { | |
| // Simuler l'indice de peur et avidité (0-100) | |
| const index = Math.floor(Math.random() * 100); | |
| fearGreed.textContent = index; | |
| let text = ""; | |
| let colorClass = ""; | |
| if (index >= 75) { | |
| text = "Extrême avidité"; | |
| colorClass = "text-red-500"; | |
| } else if (index >= 55) { | |
| text = "Avidité"; | |
| colorClass = "text-yellow-500"; | |
| } else if (index >= 45) { | |
| text = "Neutre"; | |
| colorClass = "text-gray-500"; | |
| } else if (index >= 25) { | |
| text = "Peur"; | |
| colorClass = "text-blue-500"; | |
| } else { | |
| text = "Extrême peur"; | |
| colorClass = "text-green-500"; | |
| } | |
| fearGreed.className = `text-2xl font-bold ${colorClass}`; | |
| fearGreedText.innerHTML = `<span class="${colorClass}">${text}</span>`; | |
| } | |
| function renderCryptoList(data) { | |
| cryptoList.innerHTML = ''; | |
| if (data.length === 0) { | |
| cryptoList.innerHTML = ` | |
| <div class="col-span-3 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700 p-4"> | |
| <p>Aucune cryptomonnaie ne correspond à votre recherche.</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| data.forEach(crypto => { | |
| const isFavorite = favorites.includes(crypto.id); | |
| const priceChange = crypto.price_change_percentage_24h; | |
| const changeClass = priceChange >= 0 ? 'text-green-500' : 'text-red-500'; | |
| const changeIcon = priceChange >= 0 ? 'fa-arrow-up' : 'fa-arrow-down'; | |
| // Données techniques | |
| const ma7 = crypto.technicals?.ma7 || 0; | |
| const ma30 = crypto.technicals?.ma30 || 0; | |
| const trendDirection = crypto.technicals?.trend_direction || 'neutral'; | |
| const trendStrength = crypto.technicals?.trend_strength || 0; | |
| const sparklineData = crypto.technicals?.sparkline || []; | |
| // Créer un canvas pour le graphique | |
| const canvasId = `chart-${crypto.id}`; | |
| const card = document.createElement('div'); | |
| card.className = 'coin-card bg-white rounded-xl shadow-md p-6 transition-all duration-300 dark-mode'; | |
| card.innerHTML = ` | |
| <div class="flex justify-between items-start mb-4"> | |
| <div class="flex items-center"> | |
| <img src="${crypto.image}" alt="${crypto.name}" class="w-10 h-10 mr-3"> | |
| <div> | |
| <h3 class="font-bold">${crypto.name}</h3> | |
| <p class="text-gray-500 text-sm">${crypto.symbol.toUpperCase()}</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center"> | |
| <span class="trend-indicator trend-${trendDirection} mr-2"> | |
| ${trendDirection === 'up' ? '↑' : trendDirection === 'down' ? '↓' : '→'} | |
| ${Math.abs(trendStrength * 100).toFixed(0)}% | |
| </span> | |
| <button class="favorite-btn text-${isFavorite ? 'yellow' : 'gray'}-400 hover:text-yellow-500 focus:outline-none" data-id="${crypto.id}"> | |
| <i class="fas fa-star"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="chart-container mb-4"> | |
| <canvas id="${canvasId}"></canvas> | |
| </div> | |
| <div class="flex justify-between items-end mb-2"> | |
| <div> | |
| <p class="text-gray-500 text-sm">Prix</p> | |
| <p class="text-xl font-bold">${formatCurrency(crypto.current_price)}</p> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-gray-500 text-sm">24h</p> | |
| <p class="${changeClass} font-semibold"> | |
| <i class="fas ${changeIcon} mr-1"></i> | |
| ${Math.abs(priceChange).toFixed(2)}% | |
| </p> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 text-sm mb-2"> | |
| <div> | |
| <p class="text-gray-500">MA7</p> | |
| <p class="${crypto.current_price > ma7 ? 'text-green-500' : 'text-red-500'}"> | |
| ${formatCurrency(ma7)} | |
| </p> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-gray-500">MA30</p> | |
| <p class="${crypto.current_price > ma30 ? 'text-green-500' : 'text-red-500'}"> | |
| ${formatCurrency(ma30)} | |
| </p> | |
| </div> | |
| </div> | |
| <div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600"> | |
| <div class="flex justify-between text-sm"> | |
| <div> | |
| <p class="text-gray-500">Capitalisation</p> | |
| <p>${formatCurrency(crypto.market_cap)}</p> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-gray-500">Volume</p> | |
| <p>${formatCurrency(crypto.total_volume)}</p> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| cryptoList.appendChild(card); | |
| // Rendre le graphique après que l'élément a été ajouté au DOM | |
| setTimeout(() => { | |
| renderSparklineChart(canvasId, sparklineData, crypto.current_price, ma7, ma30); | |
| }, 100); | |
| }); | |
| // Ajouter les événements aux boutons favoris | |
| document.querySelectorAll('.favorite-btn').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const cryptoId = this.getAttribute('data-id'); | |
| toggleFavorite(cryptoId); | |
| }); | |
| }); | |
| } | |
| function renderSparklineChart(canvasId, sparklineData, currentPrice, ma7, ma30) { | |
| const ctx = document.getElementById(canvasId).getContext('2d'); | |
| const chartColor = isDarkMode ? '#3b82f6' : '#2563eb'; | |
| const bgColor = isDarkMode ? 'rgba(59, 130, 246, 0.1)' : 'rgba(37, 99, 235, 0.1)'; | |
| // Préparer les données pour le graphique | |
| const labels = Array(sparklineData.length).fill(''); | |
| const ma7Data = Array(sparklineData.length).fill(ma7); | |
| const ma30Data = Array(sparklineData.length).fill(ma30); | |
| new Chart(ctx, { | |
| type: 'line', | |
| data: { | |
| labels: labels, | |
| datasets: [ | |
| { | |
| label: 'Prix', | |
| data: sparklineData, | |
| borderColor: chartColor, | |
| backgroundColor: bgColor, | |
| borderWidth: 2, | |
| fill: true, | |
| tension: 0.4 | |
| }, | |
| { | |
| label: 'MA7', | |
| data: ma7Data, | |
| borderColor: '#10b981', | |
| borderWidth: 1, | |
| borderDash: [5, 5], | |
| pointRadius: 0, | |
| tension: 0 | |
| }, | |
| { | |
| label: 'MA30', | |
| data: ma30Data, | |
| borderColor: '#8b5cf6', | |
| borderWidth: 1, | |
| borderDash: [5, 5], | |
| pointRadius: 0, | |
| tension: 0 | |
| } | |
| ] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: false, | |
| plugins: { | |
| legend: { | |
| display: false | |
| }, | |
| tooltip: { | |
| enabled: false | |
| } | |
| }, | |
| scales: { | |
| x: { | |
| display: false | |
| }, | |
| y: { | |
| display: false, | |
| suggestedMin: Math.min(...sparklineData) * 0.95, | |
| suggestedMax: Math.max(...sparklineData) * 1.05 | |
| } | |
| }, | |
| elements: { | |
| point: { | |
| radius: 0 | |
| } | |
| } | |
| } | |
| }); | |
| } | |
| function toggleFavorite(cryptoId) { | |
| const index = favorites.indexOf(cryptoId); | |
| if (index === -1) { | |
| favorites.push(cryptoId); | |
| } else { | |
| favorites.splice(index, 1); | |
| } | |
| localStorage.setItem(favoritesKey, JSON.stringify(favorites)); | |
| renderCryptoList(cryptoData); | |
| updateFavoritesSection(); | |
| } | |
| function updateFavoritesSection() { | |
| if (favorites.length === 0) { | |
| favoritesSection.classList.add('hidden'); | |
| return; | |
| } | |
| favoritesSection.classList.remove('hidden'); | |
| const favoriteCryptos = cryptoData.filter(crypto => favorites.includes(crypto.id)); | |
| if (favoriteCryptos.length === 0) { | |
| favoritesList.innerHTML = ` | |
| <div class="col-span-3 bg-blue-100 border-l-4 border-blue-500 text-blue-700 p-4"> | |
| <p>Vos cryptomonnaies favorites n'ont pas été trouvées dans les résultats actuels.</p> | |
| </div> | |
| `; | |
| return; | |
| } | |
| favoritesList.innerHTML = ''; | |
| favoriteCryptos.forEach(crypto => { | |
| const priceChange = crypto.price_change_percentage_24h; | |
| const changeClass = priceChange >= 0 ? 'text-green-500' : 'text-red-500'; | |
| const changeIcon = priceChange >= 0 ? 'fa-arrow-up' : 'fa-arrow-down'; | |
| // Données techniques | |
| const ma7 = crypto.technicals?.ma7 || 0; | |
| const ma30 = crypto.technicals?.ma30 || 0; | |
| const trendDirection = crypto.technicals?.trend_direction || 'neutral'; | |
| const trendStrength = crypto.technicals?.trend_strength || 0; | |
| const sparklineData = crypto.technicals?.sparkline || []; | |
| const canvasId = `fav-chart-${crypto.id}`; | |
| const card = document.createElement('div'); | |
| card.className = 'coin-card bg-white rounded-xl shadow-md p-6 transition-all duration-300 dark-mode'; | |
| card.innerHTML = ` | |
| <div class="flex justify-between items-start mb-4"> | |
| <div class="flex items-center"> | |
| <img src="${crypto.image}" alt="${crypto.name}" class="w-10 h-10 mr-3"> | |
| <div> | |
| <h3 class="font-bold">${crypto.name}</h3> | |
| <p class="text-gray-500 text-sm">${crypto.symbol.toUpperCase()}</p> | |
| </div> | |
| </div> | |
| <div class="flex items-center"> | |
| <span class="trend-indicator trend-${trendDirection} mr-2"> | |
| ${trendDirection === 'up' ? '↑' : trendDirection === 'down' ? '↓' : '→'} | |
| ${Math.abs(trendStrength * 100).toFixed(0)}% | |
| </span> | |
| <button class="favorite-btn text-yellow-400 hover:text-yellow-500 focus:outline-none" data-id="${crypto.id}"> | |
| <i class="fas fa-star"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="chart-container mb-4"> | |
| <canvas id="${canvasId}"></canvas> | |
| </div> | |
| <div class="flex justify-between items-end mb-2"> | |
| <div> | |
| <p class="text-gray-500 text-sm">Prix</p> | |
| <p class="text-xl font-bold">${formatCurrency(crypto.current_price)}</p> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-gray-500 text-sm">24h</p> | |
| <p class="${changeClass} font-semibold"> | |
| <i class="fas ${changeIcon} mr-1"></i> | |
| ${Math.abs(priceChange).toFixed(2)}% | |
| </p> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-2 text-sm mb-2"> | |
| <div> | |
| <p class="text-gray-500">MA7</p> | |
| <p class="${crypto.current_price > ma7 ? 'text-green-500' : 'text-red-500'}"> | |
| ${formatCurrency(ma7)} | |
| </p> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-gray-500">MA30</p> | |
| <p class="${crypto.current_price > ma30 ? 'text-green-500' : 'text-red-500'}"> | |
| ${formatCurrency(ma30)} | |
| </p> | |
| </div> | |
| </div> | |
| <div class="mt-4 pt-4 border-t border-gray-200 dark:border-gray-600"> | |
| <div class="flex justify-between text-sm"> | |
| <div> | |
| <p class="text-gray-500">Capitalisation</p> | |
| <p>${formatCurrency(crypto.market_cap)}</p> | |
| </div> | |
| <div class="text-right"> | |
| <p class="text-gray-500">Volume</p> | |
| <p>${formatCurrency(crypto.total_volume)}</p> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| favoritesList.appendChild(card); | |
| // Rendre le graphique après que l'élément a été ajouté au DOM | |
| setTimeout(() => { | |
| renderSparklineChart(canvasId, sparklineData, crypto.current_price, ma7, ma30); | |
| }, 100); | |
| }); | |
| // Ajouter les événements aux boutons favoris | |
| document.querySelectorAll('.favorite-btn').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| const cryptoId = this.getAttribute('data-id'); | |
| toggleFavorite(cryptoId); | |
| }); | |
| }); | |
| } | |
| function filterCryptos() { | |
| const searchTerm = searchInput.value.toLowerCase(); | |
| if (!searchTerm) { | |
| renderCryptoList(cryptoData); | |
| return; | |
| } | |
| const filtered = cryptoData.filter(crypto => | |
| crypto.name.toLowerCase().includes(searchTerm) || | |
| crypto.symbol.toLowerCase().includes(searchTerm) | |
| ); | |
| renderCryptoList(filtered); | |
| } | |
| function sortCryptos(sortBy) { | |
| currentSort = sortBy; | |
| // Mettre à jour l'état des boutons | |
| document.querySelectorAll('[id^="sort"]').forEach(btn => { | |
| btn.classList.remove('bg-blue-500', 'text-white'); | |
| btn.classList.add('bg-gray-200', 'dark:bg-gray-700'); | |
| }); | |
| const activeBtn = document.getElementById(`sort${sortBy.charAt(0).toUpperCase() + sortBy.slice(1).replace('_', ' ').split(' ')[0]}`); | |
| if (activeBtn) { | |
| activeBtn.classList.remove('bg-gray-200', 'dark:bg-gray-700'); | |
| activeBtn.classList.add('bg-blue-500', 'text-white'); | |
| } | |
| // Trier les données | |
| const sorted = [...cryptoData].sort((a, b) => { | |
| if (sortBy === 'price_change_percentage_24h') { | |
| return b[sortBy] - a[sortBy]; | |
| } else if (sortBy === 'trend_strength') { | |
| return (b.technicals?.trend_strength || 0) - (a.technicals?.trend_strength || 0); | |
| } else { | |
| return b[sortBy] - a[sortBy]; | |
| } | |
| }); | |
| renderCryptoList(sorted); | |
| } | |
| function toggleDarkMode() { | |
| isDarkMode = !isDarkMode; | |
| document.body.classList.toggle('dark-mode'); | |
| if (isDarkMode) { | |
| darkModeToggle.innerHTML = '<i class="fas fa-sun"></i>'; | |
| localStorage.setItem('darkMode', 'true'); | |
| } else { | |
| darkModeToggle.innerHTML = '<i class="fas fa-moon"></i>'; | |
| localStorage.setItem('darkMode', 'false'); | |
| } | |
| // Re-rendre les graphiques avec les nouvelles couleurs | |
| renderCryptoList(cryptoData); | |
| if (favoritesSection && !favoritesSection.classList.contains('hidden')) { | |
| updateFavoritesSection(); | |
| } | |
| } | |
| function formatCurrency(value) { | |
| return new Intl.NumberFormat('fr-FR', { | |
| style: 'currency', | |
| currency: 'EUR', | |
| minimumFractionDigits: value < 1 ? 4 : 2, | |
| maximumFractionDigits: value < 1 ? 6 : 2 | |
| }).format(value); | |
| } | |
| function formatChange(value, isPercentage = false) { | |
| if (value === undefined || value === null) return ''; | |
| const absValue = Math.abs(value); | |
| const formattedValue = isPercentage ? absValue.toFixed(2) : absValue.toFixed(1); | |
| const arrow = value >= 0 ? '↑' : '↓'; | |
| const colorClass = value >= 0 ? 'text-green-500' : 'text-red-500'; | |
| return `<span class="${colorClass}">${arrow} ${formattedValue}${isPercentage ? '%' : ''}</span>`; | |
| } | |
| // Actualiser les données toutes les minutes | |
| setInterval(fetchData, 60000); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=bourahima/crypto-trafic" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |