crypto / index.html
mexicanamerican's picture
Add 1 files
05c4a6d verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Crypto Price Tracker with Charts</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% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.pulse {
animation: pulse 2s infinite;
}
.gradient-bg {
background: linear-gradient(135deg, #1e3a8a 0%, #0ea5e9 100%);
}
.card-hover: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);
}
.price-up {
color: #10b981;
}
.price-down {
color: #ef4444;
}
.loading-spinner {
border-top-color: #3b82f6;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.modal {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.modal-active {
opacity: 1;
visibility: visible;
transform: translate(-50%, -50%) scale(1);
}
.modal-inactive {
opacity: 0;
visibility: hidden;
transform: translate(-50%, -50%) scale(0.9);
}
.chart-tab.active {
background-color: rgba(255, 255, 255, 0.2);
font-weight: 600;
}
</style>
</head>
<body class="min-h-screen gradient-bg text-white">
<div class="container mx-auto px-4 py-12">
<header class="text-center mb-12">
<h1 class="text-4xl md:text-5xl font-bold mb-4">Crypto Price Tracker</h1>
<p class="text-xl opacity-90">Real-time cryptocurrency prices with interactive charts</p>
<div class="mt-6 flex justify-center">
<div class="relative w-full max-w-md">
<input
type="text"
id="searchInput"
placeholder="Search cryptocurrencies..."
class="w-full px-4 py-3 rounded-full bg-white/10 border border-white/20 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent text-white placeholder-white/70"
>
<button id="searchBtn" class="absolute right-3 top-3 text-white/70 hover:text-white">
<i class="fas fa-search"></i>
</button>
</div>
</div>
</header>
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-semibold">Top Cryptocurrencies</h2>
<div class="flex items-center space-x-2">
<span class="text-sm">Auto-refresh:</span>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="autoRefresh" class="sr-only peer" checked>
<div class="w-11 h-6 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-blue-500"></div>
</label>
</div>
</div>
<div id="loading" class="flex justify-center items-center py-12">
<div class="loading-spinner h-12 w-12 border-4 border-white/20 rounded-full"></div>
</div>
<div id="cryptoContainer" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 hidden">
<!-- Crypto cards will be inserted here -->
</div>
<div id="errorMessage" class="hidden text-center py-12">
<i class="fas fa-exclamation-triangle text-4xl mb-4 text-yellow-400"></i>
<h3 class="text-2xl font-semibold mb-2">Failed to load data</h3>
<p class="mb-4">We couldn't fetch cryptocurrency prices. Please check your connection and try again.</p>
<button id="retryBtn" class="px-6 py-2 bg-white/10 hover:bg-white/20 rounded-full border border-white/20 transition-all">
<i class="fas fa-sync-alt mr-2"></i> Retry
</button>
</div>
<div class="mt-12 text-center text-sm opacity-80">
<p>Data provided by CoinGecko API. Prices update every 60 seconds.</p>
<p class="mt-2">Last updated: <span id="lastUpdated">-</span></p>
</div>
</div>
<!-- Modal -->
<div id="coinModal" class="modal modal-inactive fixed inset-0 z-50">
<div class="absolute inset-0 bg-black/70 backdrop-blur-sm" id="modalBackdrop"></div>
<div class="absolute top-1/2 left-1/2 w-11/12 max-w-4xl -translate-x-1/2 -translate-y-1/2">
<div class="bg-gray-900 rounded-xl border border-white/10 overflow-hidden shadow-2xl">
<div class="flex justify-between items-center p-4 border-b border-white/10">
<div class="flex items-center">
<img id="modalCoinImage" src="" alt="" class="w-8 h-8 mr-3">
<h3 id="modalCoinName" class="text-xl font-bold"></h3>
<span id="modalCoinSymbol" class="ml-2 text-sm opacity-80"></span>
</div>
<button id="closeModal" class="text-white/50 hover:text-white">
<i class="fas fa-times text-xl"></i>
</button>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<div class="bg-white/5 p-4 rounded-lg border border-white/10">
<div class="text-sm opacity-80 mb-1">Current Price</div>
<div id="modalCurrentPrice" class="text-2xl font-bold"></div>
</div>
<div class="bg-white/5 p-4 rounded-lg border border-white/10">
<div class="text-sm opacity-80 mb-1">Market Cap</div>
<div id="modalMarketCap" class="text-2xl font-bold"></div>
</div>
<div class="bg-white/5 p-4 rounded-lg border border-white/10">
<div class="text-sm opacity-80 mb-1">24h Change</div>
<div id="modal24hChange" class="text-2xl font-bold"></div>
</div>
</div>
<div class="mb-4">
<div class="flex border-b border-white/10">
<button class="chart-tab px-4 py-2 active" data-period="24h">24 Hours</button>
<button class="chart-tab px-4 py-2" data-period="7d">7 Days</button>
<button class="chart-tab px-4 py-2" data-period="30d">30 Days</button>
</div>
</div>
<div class="h-80 mb-6">
<canvas id="detailedChart"></canvas>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
<div class="bg-white/5 p-3 rounded border border-white/10">
<div class="opacity-70">24h High</div>
<div id="24hHigh" class="text-lg mt-1"></div>
</div>
<div class="bg-white/5 p-3 rounded border border-white/10">
<div class="opacity-70">24h Low</div>
<div id="24hLow" class="text-lg mt-1"></div>
</div>
<div class="bg-white/5 p-3 rounded border border-white/10">
<div class="opacity-70">24h Volume</div>
<div id="24hVolume" class="text-lg mt-1"></div>
</div>
<div class="bg-white/5 p-3 rounded border border-white/10">
<div class="opacity-70">Circ. Supply</div>
<div id="circSupply" class="text-lg mt-1"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Configuration
const config = {
coins: ['bitcoin', 'ethereum', 'binancecoin', 'ripple', 'cardano', 'solana', 'dogecoin', 'polkadot', 'litecoin', 'chainlink'],
vsCurrency: 'usd',
autoRefreshInterval: 60000 // 60 seconds
};
// DOM Elements
const cryptoContainer = document.getElementById('cryptoContainer');
const loadingElement = document.getElementById('loading');
const errorMessage = document.getElementById('errorMessage');
const retryBtn = document.getElementById('retryBtn');
const searchInput = document.getElementById('searchInput');
const searchBtn = document.getElementById('searchBtn');
const autoRefreshToggle = document.getElementById('autoRefresh');
const lastUpdatedElement = document.getElementById('lastUpdated');
const coinModal = document.getElementById('coinModal');
const modalBackdrop = document.getElementById('modalBackdrop');
const closeModal = document.getElementById('closeModal');
const detailedChart = document.getElementById('detailedChart');
const chartTabs = document.querySelectorAll('.chart-tab');
// Chart instance
let chartInstance = null;
let currentCoinId = null;
// State
let refreshInterval;
let allCoinsData = [];
// Initialize
document.addEventListener('DOMContentLoaded', () => {
fetchCryptoData();
setupAutoRefresh();
setupEventListeners();
});
// Event Listeners
function setupEventListeners() {
retryBtn.addEventListener('click', fetchCryptoData);
searchBtn.addEventListener('click', filterCoins);
searchInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') filterCoins();
});
autoRefreshToggle.addEventListener('change', setupAutoRefresh);
modalBackdrop.addEventListener('click', closeCoinModal);
closeModal.addEventListener('click', closeCoinModal);
// Add event listeners to chart tabs
chartTabs.forEach(tab => {
tab.addEventListener('click', () => {
changeChartPeriod(tab.dataset.period);
});
});
}
// Auto Refresh
function setupAutoRefresh() {
clearInterval(refreshInterval);
if (autoRefreshToggle.checked) {
refreshInterval = setInterval(fetchCryptoData, config.autoRefreshInterval);
}
}
// Fetch Data
async function fetchCryptoData() {
try {
showLoading();
hideError();
const ids = config.coins.join(',');
const url = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=${config.vsCurrency}&ids=${ids}&order=market_cap_desc&per_page=100&page=1&sparkline=true&price_change_percentage=1h,24h,7d`;
const response = await fetch(url);
if (!response.ok) {
throw new Error('Network response was not ok');
}
allCoinsData = await response.json();
renderCryptoCards(allCoinsData);
updateLastUpdated();
} catch (error) {
console.error('Error fetching crypto data:', error);
showError();
} finally {
hideLoading();
}
}
// Render Crypto Cards
function renderCryptoCards(coinsData) {
cryptoContainer.innerHTML = '';
coinsData.forEach(coin => {
const priceChange24h = coin.price_change_percentage_24h;
const priceChangeClass = priceChange24h >= 0 ? 'price-up' : 'price-down';
const priceChangeIcon = priceChange24h >= 0 ? 'fa-arrow-up' : 'fa-arrow-down';
const card = document.createElement('div');
card.className = 'bg-white/5 rounded-xl p-6 border border-white/10 transition-all duration-300 card-hover cursor-pointer';
card.dataset.coinId = coin.id;
card.innerHTML = `
<div class="flex justify-between items-start mb-4">
<div class="flex items-center">
<img src="${coin.image}" alt="${coin.name}" class="w-10 h-10 mr-3 rounded-full">
<div>
<h3 class="font-bold text-lg">${coin.name}</h3>
<span class="text-sm opacity-80">${coin.symbol.toUpperCase()}</span>
</div>
</div>
<span class="text-xs px-2 py-1 rounded-full bg-white/10">#${coin.market_cap_rank}</span>
</div>
<div class="mb-4">
<div class="flex items-baseline mb-1">
<span class="text-2xl font-bold mr-2">$${coin.current_price.toLocaleString()}</span>
<span class="text-sm ${priceChangeClass}">
<i class="fas ${priceChangeIcon} mr-1"></i>${Math.abs(priceChange24h).toFixed(2)}%
</span>
</div>
<div class="text-sm opacity-80">Market Cap: $${coin.market_cap.toLocaleString()}</div>
</div>
<div class="h-16 mb-4">
<canvas class="w-full h-full" id="sparkline-${coin.id}"></canvas>
</div>
<div class="grid grid-cols-3 gap-2 text-center text-sm">
<div class="bg-white/5 p-2 rounded">
<div>1h</div>
<div class="${coin.price_change_percentage_1h_in_currency >= 0 ? 'price-up' : 'price-down'}">
${coin.price_change_percentage_1h_in_currency ? coin.price_change_percentage_1h_in_currency.toFixed(2) + '%' : '-'}
</div>
</div>
<div class="bg-white/5 p-2 rounded">
<div>24h</div>
<div class="${priceChangeClass}">
${priceChange24h.toFixed(2)}%
</div>
</div>
<div class="bg-white/5 p-2 rounded">
<div>7d</div>
<div class="${coin.price_change_percentage_7d_in_currency >= 0 ? 'price-up' : 'price-down'}">
${coin.price_change_percentage_7d_in_currency.toFixed(2)}%
</div>
</div>
</div>
`;
// Add click event to show modal with detailed info
card.addEventListener('click', () => showCoinModal(coin.id));
cryptoContainer.appendChild(card);
// Render sparkline after the card is added to DOM
setTimeout(() => renderSparkline(coin), 100);
});
showCryptoContainer();
}
// Render Sparkline Chart
function renderSparkline(coin) {
const canvas = document.getElementById(`sparkline-${coin.id}`);
if (!canvas) return;
const ctx = canvas.getContext('2d');
const sparklineData = coin.sparkline_in_7d.price;
const width = canvas.width;
const height = canvas.height;
// Calculate scale factors
const minPrice = Math.min(...sparklineData);
const maxPrice = Math.max(...sparklineData);
const scaleY = height / (maxPrice - minPrice);
const scaleX = width / (sparklineData.length - 1);
// Draw sparkline
ctx.beginPath();
ctx.moveTo(0, height - (sparklineData[0] - minPrice) * scaleY);
for (let i = 1; i < sparklineData.length; i++) {
ctx.lineTo(i * scaleX, height - (sparklineData[i] - minPrice) * scaleY);
}
ctx.strokeStyle = coin.price_change_percentage_7d_in_currency >= 0 ? '#10b981' : '#ef4444';
ctx.lineWidth = 2;
ctx.stroke();
}
// Show Coin Modal
async function showCoinModal(coinId) {
try {
currentCoinId = coinId;
// Get the basic coin data from our existing data
const coinData = allCoinsData.find(c => c.id === coinId);
if (!coinData) return;
// Get more detailed data from API
const response = await fetch(`https://api.coingecko.com/api/v3/coins/${coinId}/market_chart?vs_currency=${config.vsCurrency}&days=30`);
if (!response.ok) throw new Error('Could not fetch detailed data');
const detailedData = await response.json();
// Update modal with basic info
document.getElementById('modalCoinImage').src = coinData.image;
document.getElementById('modalCoinName').textContent = coinData.name;
document.getElementById('modalCoinSymbol').textContent = coinData.symbol.toUpperCase();
document.getElementById('modalCurrentPrice').textContent = `$${coinData.current_price.toLocaleString()}`;
document.getElementById('modalMarketCap').textContent = `$${coinData.market_cap.toLocaleString()}`;
const priceChange24h = coinData.price_change_percentage_24h;
const priceChangeClass = priceChange24h >= 0 ? 'price-up' : 'price-down';
const priceChangeIcon = priceChange24h >= 0 ? 'fa-arrow-up' : 'fa-arrow-down';
document.getElementById('modal24hChange').innerHTML = `
<span class="${priceChangeClass}">
<i class="fas ${priceChangeIcon} mr-1"></i>${Math.abs(priceChange24h).toFixed(2)}%
</span>
`;
// Update additional info
document.getElementById('24hHigh').textContent = `$${coinData.high_24h.toLocaleString()}`;
document.getElementById('24hLow').textContent = `$${coinData.low_24h.toLocaleString()}`;
document.getElementById('24hVolume').textContent = `$${coinData.total_volume.toLocaleString()}`;
document.getElementById('circSupply').textContent = `${coinData.circulating_supply.toLocaleString()} ${coinData.symbol.toUpperCase()}`;
// Create the initial chart (24h view)
createDetailedChart(detailedData.prices, '24h');
// Show the modal
coinModal.classList.remove('modal-inactive');
coinModal.classList.add('modal-active');
// Set the first tab as active
chartTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector('[data-period="24h"]').classList.add('active');
} catch (error) {
console.error('Error showing coin modal:', error);
alert('Failed to load detailed coin data. Please try again.');
}
}
// Close Coin Modal
function closeCoinModal() {
coinModal.classList.remove('modal-active');
coinModal.classList.add('modal-inactive');
// Destroy the chart if it exists
if (chartInstance) {
chartInstance.destroy();
chartInstance = null;
}
currentCoinId = null;
}
// Create Detailed Chart
function createDetailedChart(prices, period) {
// Filter data based on period
let filteredPrices = [];
const now = new Date().getTime();
if (period === '24h') {
const twentyFourHoursAgo = now - 24 * 60 * 60 * 1000;
filteredPrices = prices.filter(price => price[0] >= twentyFourHoursAgo);
} else if (period === '7d') {
const sevenDaysAgo = now - 7 * 24 * 60 * 60 * 1000;
filteredPrices = prices.filter(price => price[0] >= sevenDaysAgo);
} else { // 30d
filteredPrices = prices;
}
// Prepare data for chart
const labels = filteredPrices.map(price => {
const date = new Date(price[0]);
if (period === '24h') {
return date.toLocaleTimeString();
} else {
return date.toLocaleDateString();
}
});
const data = filteredPrices.map(price => price[1]);
// Get color based on price trend
const firstPrice = filteredPrices[0][1];
const lastPrice = filteredPrices[filteredPrices.length - 1][1];
const isPositive = lastPrice >= firstPrice;
const lineColor = isPositive ? '#10b981' : '#ef4444';
const bgColor = isPositive ? 'rgba(16, 185, 129, 0.1)' : 'rgba(239, 68, 68, 0.1)';
// Destroy previous chart if it exists
if (chartInstance) {
chartInstance.destroy();
}
// Create new chart
const ctx = detailedChart.getContext('2d');
chartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Price',
data: data,
borderColor: lineColor,
backgroundColor: bgColor,
borderWidth: 2,
fill: true,
tension: 0.4,
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
mode: 'index',
intersect: false,
callbacks: {
label: function(context) {
return `${context.dataset.label}: $${context.parsed.y.toFixed(2)}`;
}
}
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)',
display: false
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)',
maxRotation: 0,
autoSkip: true,
maxTicksLimit: 8
}
},
y: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'rgba(255, 255, 255, 0.7)',
callback: function(value) {
return '$' + value.toLocaleString();
}
}
}
}
}
});
}
// Change Chart Period
async function changeChartPeriod(period) {
if (!currentCoinId) return;
try {
// Set active tab
chartTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector(`[data-period="${period}"]`).classList.add('active');
// Get the updated data for the selected period
let days;
if (period === '24h') days = 1;
else if (period === '7d') days = 7;
else days = 30;
const response = await fetch(`https://api.coingecko.com/api/v3/coins/${currentCoinId}/market_chart?vs_currency=${config.vsCurrency}&days=${days}`);
if (!response.ok) throw new Error('Could not fetch chart data');
const detailedData = await response.json();
createDetailedChart(detailedData.prices, period);
} catch (error) {
console.error('Error changing chart period:', error);
alert('Failed to load chart data. Please try again.');
}
}
// Filter Coins
function filterCoins() {
const searchTerm = searchInput.value.toLowerCase();
if (!searchTerm) {
renderCryptoCards(allCoinsData);
return;
}
const filteredCoins = allCoinsData.filter(coin =>
coin.name.toLowerCase().includes(searchTerm) ||
coin.symbol.toLowerCase().includes(searchTerm)
);
if (filteredCoins.length === 0) {
cryptoContainer.innerHTML = `
<div class="col-span-full text-center py-12">
<i class="fas fa-search text-4xl mb-4 opacity-50"></i>
<h3 class="text-xl font-semibold">No cryptocurrencies found</h3>
<p class="opacity-80">Try a different search term</p>
</div>
`;
} else {
renderCryptoCards(filteredCoins);
}
}
// UI Helpers
function showLoading() {
loadingElement.classList.remove('hidden');
cryptoContainer.classList.add('hidden');
}
function hideLoading() {
loadingElement.classList.add('hidden');
}
function showCryptoContainer() {
cryptoContainer.classList.remove('hidden');
}
function showError() {
errorMessage.classList.remove('hidden');
cryptoContainer.classList.add('hidden');
}
function hideError() {
errorMessage.classList.add('hidden');
}
function updateLastUpdated() {
const now = new Date();
lastUpdatedElement.textContent = now.toLocaleTimeString();
}
</script>
</body>
</html>