2025-05-21 21:34:22 +03:00
< script lang = "ts" >
import * as Card from '$lib/components/ui/card';
2025-05-27 16:19:57 +03:00
import * as Select from '$lib/components/ui/select';
2025-05-21 21:34:22 +03:00
import { Badge } from '$lib/components/ui/badge';
2025-05-23 16:26:02 +03:00
import { Button } from '$lib/components/ui/button';
import * as Avatar from '$lib/components/ui/avatar';
import * as HoverCard from '$lib/components/ui/hover-card';
2025-05-23 19:48:23 +03:00
import TradeModal from '$lib/components/self/TradeModal.svelte';
2025-05-24 19:28:38 +03:00
import CommentSection from '$lib/components/self/CommentSection.svelte';
2025-05-25 18:44:06 +03:00
import UserProfilePreview from '$lib/components/self/UserProfilePreview.svelte';
2025-05-27 14:12:29 +03:00
import CoinSkeleton from '$lib/components/self/skeletons/CoinSkeleton.svelte';
2025-05-26 13:05:47 +03:00
import { TrendingUp , TrendingDown , DollarSign , Coins , ChartColumn } from 'lucide-svelte';
2025-05-23 16:26:02 +03:00
import {
createChart,
ColorType,
type IChartApi,
2025-05-23 19:48:23 +03:00
CandlestickSeries,
HistogramSeries
2025-05-23 16:26:02 +03:00
} from 'lightweight-charts';
2025-05-21 21:34:22 +03:00
import { onMount } from 'svelte';
2025-05-23 16:26:02 +03:00
import { goto } from '$app/navigation';
import { toast } from 'svelte-sonner';
2025-05-23 19:48:23 +03:00
import CoinIcon from '$lib/components/self/CoinIcon.svelte';
import { USER_DATA } from '$lib/stores/user-data';
import { fetchPortfolioData } from '$lib/stores/portfolio-data';
2025-05-27 14:54:19 +03:00
import { getPublicUrl , getTimeframeInSeconds } from '$lib/utils.js';
import { websocketController , type PriceUpdate , isConnectedStore } from '$lib/stores/websocket';
2025-05-30 13:15:00 +03:00
import SEO from '$lib/components/self/SEO.svelte';
2025-05-31 14:02:10 +03:00
import { page } from '$app/state';
2025-05-21 21:34:22 +03:00
2025-05-23 16:26:02 +03:00
const { data } = $props();
2025-05-31 14:02:10 +03:00
let coinSymbol = $derived(data.coinSymbol);
2025-05-23 16:26:02 +03:00
let coin = $state< any > (null);
let loading = $state(true);
let chartData = $state< any [ ] > ([]);
2025-05-23 19:48:23 +03:00
let volumeData = $state< any [ ] > ([]);
let userHolding = $state(0);
let buyModalOpen = $state(false);
let sellModalOpen = $state(false);
let selectedTimeframe = $state('1m');
2025-05-27 14:54:19 +03:00
let lastPriceUpdateTime = 0;
2025-05-23 16:26:02 +03:00
2025-05-27 16:19:57 +03:00
const timeframeOptions = [
{ value : '1m' , label : '1 minute' } ,
{ value : '5m' , label : '5 minutes' } ,
{ value : '15m' , label : '15 minutes' } ,
{ value : '1h' , label : '1 hour' } ,
{ value : '4h' , label : '4 hours' } ,
{ value : '1d' , label : '1 day' }
];
2025-05-23 16:26:02 +03:00
onMount(async () => {
2025-05-23 19:48:23 +03:00
await loadCoinData();
await loadUserHolding();
2025-05-26 15:06:45 +03:00
websocketController.setCoin(coinSymbol.toUpperCase());
2025-05-27 14:54:19 +03:00
websocketController.subscribeToPriceUpdates(coinSymbol.toUpperCase(), handlePriceUpdate);
});
$effect(() => {
return () => {
websocketController.unsubscribeFromPriceUpdates(coinSymbol.toUpperCase());
};
2025-05-23 19:48:23 +03:00
});
2025-05-31 14:02:10 +03:00
// Handle route changes to update coin data
$effect(() => {
const currentCoinSymbol = page.params.coinSymbol;
if (currentCoinSymbol && currentCoinSymbol !== coinSymbol) {
loading = true;
coin = null;
chartData = [];
volumeData = [];
userHolding = 0;
// Unsubscribe from the old coin's updates
websocketController.unsubscribeFromPriceUpdates(coinSymbol.toUpperCase());
// Update the data prop which will trigger coinSymbol to update via $derived
data.coinSymbol = currentCoinSymbol;
// Load new coin data and set up new subscriptions
loadCoinData().then(() => {
loadUserHolding();
websocketController.setCoin(currentCoinSymbol.toUpperCase());
websocketController.subscribeToPriceUpdates(
currentCoinSymbol.toUpperCase(),
handlePriceUpdate
);
});
}
});
2025-05-23 19:48:23 +03:00
async function loadCoinData() {
2025-05-23 16:26:02 +03:00
try {
2025-05-23 19:48:23 +03:00
const response = await fetch(`/api/coin/${ coinSymbol } ?timeframe=${ selectedTimeframe } `);
2025-05-23 16:26:02 +03:00
if (!response.ok) {
2025-05-23 19:48:23 +03:00
toast.error(response.status === 404 ? 'Coin not found' : 'Failed to load coin data');
2025-05-23 16:26:02 +03:00
return;
}
const result = await response.json();
coin = result.coin;
2025-05-23 19:48:23 +03:00
chartData = result.candlestickData || [];
volumeData = result.volumeData || [];
2025-05-23 16:26:02 +03:00
} catch (e) {
console.error('Failed to fetch coin data:', e);
toast.error('Failed to load coin data');
} finally {
loading = false;
}
2025-05-23 19:48:23 +03:00
}
async function loadUserHolding() {
if (!$USER_DATA) return;
2025-05-21 21:34:22 +03:00
2025-05-23 19:48:23 +03:00
try {
const response = await fetch('/api/portfolio/total');
if (response.ok) {
const result = await response.json();
const holding = result.coinHoldings.find((h: any) => h.symbol === coinSymbol.toUpperCase());
userHolding = holding ? holding.quantity : 0;
2025-05-21 21:34:22 +03:00
}
2025-05-23 19:48:23 +03:00
} catch (e) {
console.error('Failed to load user holding:', e);
}
}
async function handleTradeSuccess() {
await Promise.all([loadCoinData(), loadUserHolding(), fetchPortfolioData()]);
}
2025-05-27 14:54:19 +03:00
function handlePriceUpdate(priceUpdate: PriceUpdate) {
if (coin && priceUpdate.coinSymbol === coinSymbol.toUpperCase()) {
// throttle updates to prevent excessive UI updates, 1s interval
const now = Date.now();
if (now - lastPriceUpdateTime < 1000 ) {
return;
}
lastPriceUpdateTime = now;
coin = {
...coin,
currentPrice: priceUpdate.currentPrice,
marketCap: priceUpdate.marketCap,
change24h: priceUpdate.change24h,
volume24h: priceUpdate.volume24h,
...(priceUpdate.poolCoinAmount !== undefined && {
poolCoinAmount: priceUpdate.poolCoinAmount
}),
...(priceUpdate.poolBaseCurrencyAmount !== undefined && {
poolBaseCurrencyAmount: priceUpdate.poolBaseCurrencyAmount
})
};
updateChartRealtime(priceUpdate.currentPrice);
}
}
function updateChartRealtime(newPrice: number) {
if (!candlestickSeries || !chart || chartData.length === 0) return;
const timeframeSeconds = getTimeframeInSeconds(selectedTimeframe);
const currentTime = Math.floor(Date.now() / 1000);
const currentCandleTime = Math.floor(currentTime / timeframeSeconds) * timeframeSeconds;
const lastCandle = chartData[chartData.length - 1];
if (lastCandle && lastCandle.time === currentCandleTime) {
const updatedCandle = {
time: currentCandleTime,
open: lastCandle.open,
high: Math.max(lastCandle.high, newPrice),
low: Math.min(lastCandle.low, newPrice),
close: newPrice
};
candlestickSeries.update(updatedCandle);
chartData[chartData.length - 1] = updatedCandle;
} else if (currentCandleTime > (lastCandle?.time || 0)) {
const newCandle = {
time: currentCandleTime,
open: newPrice,
high: newPrice,
low: newPrice,
close: newPrice
};
candlestickSeries.update(newCandle);
chartData.push(newCandle);
}
}
2025-05-23 19:48:23 +03:00
async function handleTimeframeChange(timeframe: string) {
selectedTimeframe = timeframe;
loading = true;
if (chart) {
chart.remove();
chart = null;
}
await loadCoinData();
loading = false;
}
2025-05-21 21:34:22 +03:00
2025-05-27 16:19:57 +03:00
let currentTimeframeLabel = $derived(
timeframeOptions.find((option) => option.value === selectedTimeframe)?.label || '1 minute'
);
2025-05-23 19:48:23 +03:00
2025-05-23 16:26:02 +03:00
let chartContainer = $state< HTMLDivElement > ();
let chart: IChartApi | null = null;
2025-05-27 14:54:19 +03:00
let candlestickSeries: any = null;
let volumeSeries: any = null;
2025-05-21 21:34:22 +03:00
2025-05-23 16:26:02 +03:00
$effect(() => {
2025-05-23 19:48:23 +03:00
if (chart && chartData.length > 0) {
chart.remove();
chart = null;
}
if (chartContainer && chartData.length > 0) {
2025-05-23 16:26:02 +03:00
chart = createChart(chartContainer, {
layout: {
textColor: '#666666',
background: { type : ColorType . Solid , color : 'transparent' } ,
2025-05-23 19:48:23 +03:00
attributionLogo: false,
panes: {
separatorColor: '#2B2B43',
separatorHoverColor: 'rgba(107, 114, 142, 0.3)',
enableResize: true
}
2025-05-23 16:26:02 +03:00
},
grid: {
vertLines: { color : '#2B2B43' } ,
horzLines: { color : '#2B2B43' }
},
rightPriceScale: {
2025-05-23 19:48:23 +03:00
borderVisible: false,
scaleMargins: { top : 0.1 , bottom : 0.1 } ,
alignLabels: true,
entireTextOnly: false
2025-05-23 16:26:02 +03:00
},
timeScale: {
borderVisible: false,
2025-05-23 19:48:23 +03:00
timeVisible: true,
barSpacing: 20,
rightOffset: 5,
minBarSpacing: 8
2025-05-23 16:26:02 +03:00
},
crosshair: {
2025-05-23 19:48:23 +03:00
mode: 1,
vertLine: { color : '#758696' , width : 1 , style : 2 , visible : true , labelVisible : true } ,
horzLine: { color : '#758696' , width : 1 , style : 2 , visible : true , labelVisible : true }
2025-05-23 16:26:02 +03:00
}
2025-05-21 21:34:22 +03:00
});
2025-05-27 14:54:19 +03:00
candlestickSeries = chart.addSeries(CandlestickSeries, {
2025-05-23 16:26:02 +03:00
upColor: '#26a69a',
downColor: '#ef5350',
2025-05-23 19:48:23 +03:00
borderVisible: true,
borderUpColor: '#26a69a',
borderDownColor: '#ef5350',
2025-05-23 16:26:02 +03:00
wickUpColor: '#26a69a',
2025-05-23 19:48:23 +03:00
wickDownColor: '#ef5350',
priceFormat: { type : 'price' , precision : 8 , minMove : 0.00000001 }
2025-05-23 16:26:02 +03:00
});
2025-05-27 14:54:19 +03:00
volumeSeries = chart.addSeries(
2025-05-23 21:45:41 +03:00
HistogramSeries,
{
priceFormat: { type : 'volume' } ,
priceScaleId: 'volume'
},
1
);
2025-05-23 16:26:02 +03:00
2025-05-23 19:48:23 +03:00
const processedChartData = chartData.map((candle) => {
if (candle.open === candle.close) {
const basePrice = candle.open;
const variation = basePrice * 0.001;
return {
...candle,
high: Math.max(candle.high, basePrice + variation),
low: Math.min(candle.low, basePrice - variation)
};
}
return candle;
});
candlestickSeries.setData(processedChartData);
volumeSeries.setData(generateVolumeData(chartData, volumeData));
const volumePane = chart.panes()[1];
if (volumePane) volumePane.setHeight(100);
2025-05-21 21:34:22 +03:00
2025-05-23 19:48:23 +03:00
chart.timeScale().fitContent();
const handleResize = () => chart?.applyOptions({ width : chartContainer?.clientWidth } );
2025-05-23 16:26:02 +03:00
window.addEventListener('resize', handleResize);
handleResize();
2025-05-23 19:48:23 +03:00
candlestickSeries.priceScale().applyOptions({ borderColor : '#71649C' } );
volumeSeries.priceScale().applyOptions({ borderColor : '#71649C' } );
chart.timeScale().applyOptions({ borderColor : '#71649C' } );
2025-05-23 16:26:02 +03:00
return () => {
window.removeEventListener('resize', handleResize);
if (chart) {
chart.remove();
chart = null;
}
};
}
2025-05-21 21:34:22 +03:00
});
2025-05-23 16:26:02 +03:00
function formatPrice(price: number): string {
2025-05-23 21:45:41 +03:00
if (price < 0.000001 ) {
return price.toFixed(8);
} else if (price < 0.01 ) {
2025-05-23 16:26:02 +03:00
return price.toFixed(6);
} else if (price < 1 ) {
return price.toFixed(4);
} else {
return price.toFixed(2);
}
}
function formatMarketCap(value: number): string {
2025-05-23 21:45:41 +03:00
const num = Number(value);
if (isNaN(num)) return '$0.00';
if (num >= 1e9) return `$${( num / 1 e9 ). toFixed ( 2 )} B`;
if (num >= 1e6) return `$${( num / 1 e6 ). toFixed ( 2 )} M`;
if (num >= 1e3) return `$${( num / 1 e3 ). toFixed ( 2 )} K`;
return `$${ num . toFixed ( 2 )} `;
2025-05-23 16:26:02 +03:00
}
function formatSupply(value: number): string {
2025-05-23 21:45:41 +03:00
const num = Number(value);
if (isNaN(num)) return '0';
if (num >= 1e9) return `${( num / 1 e9 ). toFixed ( 2 )} B`;
if (num >= 1e6) return `${( num / 1 e6 ). toFixed ( 2 )} M`;
if (num >= 1e3) return `${( num / 1 e3 ). toFixed ( 2 )} K`;
return num.toLocaleString();
2025-05-23 16:26:02 +03:00
}
2025-05-27 16:19:57 +03:00
function generateVolumeData(candlestickData: any[], volumeData: any[]) {
return candlestickData.map((candle, index) => {
// Find corresponding volume data for this time period
const volumePoint = volumeData.find((v) => v.time === candle.time);
const volume = volumePoint ? volumePoint.volume : 0;
return {
time: candle.time,
value: volume,
color: candle.close >= candle.open ? '#26a69a' : '#ef5350'
};
});
}
2025-05-21 21:34:22 +03:00
< / script >
2025-05-30 13:15:00 +03:00
< SEO
title={ coin
? `${ coin . name } (*${ coin . symbol } ) - Rugplay`
: `Loading ${ coinSymbol . toUpperCase ()} - Rugplay Game`}
description={ coin
? `Trade ${ coin . name } (*${ coin . symbol } ) in the Rugplay simulation game. Current price: $${ formatPrice ( coin . currentPrice )} , Market cap: ${ formatMarketCap ( coin . marketCap )} , 24h change: ${ coin . change24h >= 0 ? '+' : '' } ${ coin . change24h . toFixed ( 2 )} %.`
: `Virtual cryptocurrency trading page for ${ coinSymbol . toUpperCase ()} in the Rugplay simulation game.`}
keywords={ coin
? `${ coin . name } cryptocurrency game, *${ coin . symbol } virtual trading, ${ coin . symbol } price simulation, cryptocurrency trading game, virtual coin ${ coin . symbol } `
: `${ coinSymbol } virtual cryptocurrency, crypto trading simulation, virtual coin trading`}
2025-05-30 13:50:06 +03:00
image={ coin ? . icon ? getPublicUrl ( coin . icon ) : '/rugplay.svg' }
2025-05-30 13:15:00 +03:00
imageAlt={ coin ? `$ { coin . name } ( $ { coin . symbol }) logo ` : ` $ { coinSymbol } cryptocurrency logo ` }
/>
2025-05-23 16:26:02 +03:00
2025-05-23 19:48:23 +03:00
{ #if coin }
< TradeModal bind:open = { buyModalOpen } type="BUY" { coin } onSuccess = { handleTradeSuccess } / >
< TradeModal
bind:open={ sellModalOpen }
type="SELL"
{ coin }
{ userHolding }
onSuccess={ handleTradeSuccess }
/>
{ /if }
2025-05-27 14:12:29 +03:00
< div class = "container mx-auto max-w-7xl p-6" >
{ #if loading }
< CoinSkeleton / >
{ :else if ! coin }
2025-05-23 16:26:02 +03:00
< div class = "flex h-96 items-center justify-center" >
< div class = "text-center" >
< div class = "text-muted-foreground mb-4 text-xl" > Coin not found< / div >
< Button onclick = {() => goto ( '/' )} > Go Home </ Button >
< / div >
< / div >
2025-05-27 14:12:29 +03:00
{ : else }
2025-05-23 16:26:02 +03:00
<!-- Header Section -->
2025-05-21 21:34:22 +03:00
< header class = "mb-8" >
2025-05-27 16:19:57 +03:00
< div class = "mb-4 flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between" >
< div class = "flex items-center gap-3 sm:gap-4" >
2025-05-23 19:48:23 +03:00
< CoinIcon
icon={ coin . icon }
symbol={ coin . symbol }
name={ coin . name }
2025-05-27 16:19:57 +03:00
size={ 12 }
class="border sm:size-16"
2025-05-23 19:48:23 +03:00
/>
2025-05-27 16:19:57 +03:00
< div class = "min-w-0 flex-1" >
< h1 class = "text-2xl font-bold sm:text-4xl" > { coin . name } </ h1 >
< div class = "mt-1 flex flex-wrap items-center gap-2" >
< Badge variant = "outline" class = "text-sm sm:text-lg" > *{ coin . symbol } </ Badge >
2025-05-27 14:54:19 +03:00
{ #if $isConnectedStore }
2025-05-27 15:06:37 +03:00
< Badge
variant="outline"
class="animate-pulse border-green-500 text-xs text-green-500"
>
2025-05-27 14:54:19 +03:00
● LIVE
< / Badge >
{ /if }
2025-05-23 16:26:02 +03:00
{ #if ! coin . isListed }
< Badge variant = "destructive" > Delisted< / Badge >
{ /if }
< / div >
< / div >
< / div >
2025-05-27 16:19:57 +03:00
< div class = "flex flex-col items-start gap-2 sm:items-end sm:text-right" >
2025-05-27 14:54:19 +03:00
< div class = "relative" >
2025-05-27 16:19:57 +03:00
< p class = "text-2xl font-bold sm:text-3xl" >
2025-05-27 14:54:19 +03:00
${ formatPrice ( coin . currentPrice )}
< / p >
< / div >
2025-05-27 16:19:57 +03:00
< div class = "flex items-center gap-2" >
2025-05-23 16:26:02 +03:00
{ #if coin . change24h >= 0 }
< TrendingUp class = "h-4 w-4 text-green-500" / >
{ : else }
< TrendingDown class = "h-4 w-4 text-red-500" / >
{ /if }
2025-05-27 16:19:57 +03:00
< Badge variant = { coin . change24h >= 0 ? 'success' : 'destructive' } class="text-sm" >
2025-05-23 21:45:41 +03:00
{ coin . change24h >= 0 ? '+' : '' }{ Number ( coin . change24h ). toFixed ( 2 )} %
2025-05-23 16:26:02 +03:00
< / Badge >
< / div >
< / div >
2025-05-21 21:34:22 +03:00
< / div >
2025-05-23 16:26:02 +03:00
<!-- Creator Info -->
{ #if coin . creatorName }
2025-05-27 16:19:57 +03:00
< div class = "text-muted-foreground flex flex-wrap items-center gap-2 text-sm" >
2025-05-23 16:26:02 +03:00
< span > Created by< / span >
< HoverCard.Root >
< HoverCard.Trigger
2025-05-23 21:45:41 +03:00
class="flex cursor-pointer items-center gap-1 rounded-sm underline-offset-4 hover:underline focus-visible:outline-2 focus-visible:outline-offset-8"
2025-05-25 18:44:06 +03:00
onclick={() => goto ( `/user/$ { coin . creatorUsername } `) }
2025-05-23 16:26:02 +03:00
>
< Avatar.Root class = "h-4 w-4" >
2025-05-25 18:44:06 +03:00
< Avatar.Image src = { getPublicUrl ( coin . creatorImage )} alt= { coin . creatorName } />
2025-05-23 16:26:02 +03:00
< Avatar.Fallback > { coin . creatorName . charAt ( 0 )} </ Avatar.Fallback >
< / Avatar.Root >
< span class = "font-medium" > { coin . creatorName } (@{ coin . creatorUsername } )</ span >
< / HoverCard.Trigger >
< HoverCard.Content class = "w-80" side = "bottom" sideOffset = { 3 } >
2025-05-25 18:44:06 +03:00
< UserProfilePreview userId = { coin . creatorId } / >
2025-05-23 16:26:02 +03:00
< / HoverCard.Content >
< / HoverCard.Root >
< / div >
{ /if }
2025-05-21 21:34:22 +03:00
< / header >
< div class = "grid gap-6" >
2025-05-23 16:26:02 +03:00
<!-- Price Chart with Trading Actions -->
< div class = "grid grid-cols-1 gap-6 lg:grid-cols-3" >
<!-- Chart (2/3 width) -->
< div class = "lg:col-span-2" >
< Card.Root >
< Card.Header class = "pb-4" >
2025-05-23 19:48:23 +03:00
< div class = "flex items-center justify-between" >
< Card.Title class = "flex items-center gap-2" >
< ChartColumn class = "h-5 w-5" / >
Price Chart ({ selectedTimeframe } )
< / Card.Title >
2025-05-27 16:19:57 +03:00
< div class = "w-24" >
< Select.Root
type="single"
bind:value={ selectedTimeframe }
onValueChange={ handleTimeframeChange }
disabled={ loading }
>
< Select.Trigger class = "w-full" >
{ currentTimeframeLabel }
< / Select.Trigger >
< Select.Content >
< Select.Group >
{ #each timeframeOptions as option }
< Select.Item value = { option . value } label= { option . label } >
{ option . label }
< / Select.Item >
{ /each }
< / Select.Group >
< / Select.Content >
< / Select.Root >
2025-05-23 19:48:23 +03:00
< / div >
< / div >
2025-05-23 16:26:02 +03:00
< / Card.Header >
< Card.Content class = "pt-0" >
2025-05-23 19:48:23 +03:00
{ #if chartData . length === 0 }
< div class = "flex h-[500px] items-center justify-center" >
< p class = "text-muted-foreground" > No trading data available yet< / p >
< / div >
{ : else }
< div class = "h-[500px] w-full" bind:this = { chartContainer } > </div >
{ /if }
2025-05-23 16:26:02 +03:00
< / Card.Content >
< / Card.Root >
< / div >
<!-- Right side - Trading Actions + Liquidity Pool (1/3 width) -->
< div class = "space-y-6 lg:col-span-1" >
<!-- Trading Actions -->
< Card.Root >
< Card.Header class = "pb-4" >
< Card.Title > Trade { coin . symbol } </ Card.Title >
2025-05-23 19:48:23 +03:00
{ #if userHolding > 0 }
< p class = "text-muted-foreground text-sm" >
2025-05-23 21:45:41 +03:00
You own: { formatSupply ( userHolding )}
2025-05-23 19:48:23 +03:00
{ coin . symbol }
< / p >
{ /if }
2025-05-23 16:26:02 +03:00
< / Card.Header >
< Card.Content class = "pt-0" >
2025-05-23 19:48:23 +03:00
{ #if $USER_DATA }
< div class = "space-y-3" >
< Button
class="w-full"
variant="default"
size="lg"
onclick={() => ( buyModalOpen = true )}
disabled={ ! coin . isListed }
>
< TrendingUp class = "h-4 w-4" / >
Buy { coin . symbol }
< / Button >
< Button
class="w-full"
variant="outline"
size="lg"
onclick={() => ( sellModalOpen = true )}
disabled={ ! coin . isListed || userHolding <= 0 }
>
< TrendingDown class = "h-4 w-4" / >
Sell { coin . symbol }
< / Button >
< / div >
{ : else }
< div class = "py-4 text-center" >
< p class = "text-muted-foreground mb-3 text-sm" > Sign in to start trading< / p >
< Button onclick = {() => goto ( '/' )} > Sign In </ Button >
< / div >
{ /if }
2025-05-23 16:26:02 +03:00
< / Card.Content >
< / Card.Root >
<!-- Liquidity Pool -->
< Card.Root >
< Card.Header class = "pb-4" >
2025-05-27 14:54:19 +03:00
< Card.Title class = "flex items-center gap-2" > Liquidity Pool< / Card.Title >
2025-05-23 16:26:02 +03:00
< / Card.Header >
< Card.Content class = "pt-0" >
< div class = "space-y-4" >
< div >
< h4 class = "mb-3 font-medium" > Pool Composition< / h4 >
< div class = "space-y-2" >
< div class = "flex justify-between" >
< span class = "text-muted-foreground text-sm" > { coin . symbol } :</ span >
2025-05-27 14:54:19 +03:00
< span class = "font-mono text-sm" >
{ formatSupply ( coin . poolCoinAmount )}
< / span >
2025-05-23 16:26:02 +03:00
< / div >
< div class = "flex justify-between" >
< span class = "text-muted-foreground text-sm" > Base Currency:< / span >
2025-05-23 21:45:41 +03:00
< span class = "font-mono text-sm" >
${ Number ( coin . poolBaseCurrencyAmount ). toLocaleString ()}
< / span >
2025-05-23 16:26:02 +03:00
< / div >
< / div >
< / div >
< div >
< h4 class = "mb-3 font-medium" > Pool Stats< / h4 >
< div class = "space-y-2" >
< div class = "flex justify-between" >
< span class = "text-muted-foreground text-sm" > Total Liquidity:< / span >
2025-05-23 21:45:41 +03:00
< span class = "font-mono text-sm" >
${( Number ( coin . poolBaseCurrencyAmount ) * 2 ). toLocaleString ()}
< / span >
2025-05-23 16:26:02 +03:00
< / div >
< div class = "flex justify-between" >
2025-05-23 21:45:41 +03:00
< span class = "text-muted-foreground text-sm" > Current Price:< / span >
< span class = "font-mono text-sm" > ${ formatPrice ( coin . currentPrice )} </ span >
2025-05-23 16:26:02 +03:00
< / div >
< / div >
< / div >
< / div >
< / Card.Content >
< / Card.Root >
< / div >
< / div >
<!-- Statistics Grid -->
< div class = "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4" >
<!-- Market Cap -->
2025-05-27 15:06:37 +03:00
< Card.Root class = "gap-1" >
< Card.Header >
2025-05-23 16:26:02 +03:00
< Card.Title class = "flex items-center gap-2 text-sm font-medium" >
< DollarSign class = "h-4 w-4" / >
Market Cap
< / Card.Title >
2025-05-21 21:34:22 +03:00
< / Card.Header >
2025-05-23 16:26:02 +03:00
< Card.Content class = "pt-0" >
2025-05-27 14:54:19 +03:00
< p class = "text-xl font-bold" >
{ formatMarketCap ( coin . marketCap )}
< / p >
2025-05-21 21:34:22 +03:00
< / Card.Content >
< / Card.Root >
2025-05-23 16:26:02 +03:00
<!-- 24h Volume -->
2025-05-27 15:06:37 +03:00
< Card.Root class = "gap-1" >
< Card.Header >
2025-05-23 16:26:02 +03:00
< Card.Title class = "flex items-center gap-2 text-sm font-medium" >
< ChartColumn class = "h-4 w-4" / >
24h Volume
< / Card.Title >
2025-05-21 21:34:22 +03:00
< / Card.Header >
2025-05-23 16:26:02 +03:00
< Card.Content class = "pt-0" >
2025-05-27 14:54:19 +03:00
< p class = "text-xl font-bold" >
{ formatMarketCap ( coin . volume24h )}
< / p >
2025-05-21 21:34:22 +03:00
< / Card.Content >
< / Card.Root >
2025-05-23 16:26:02 +03:00
<!-- Circulating Supply -->
2025-05-27 15:06:37 +03:00
< Card.Root class = "gap-1" >
< Card.Header >
2025-05-23 16:26:02 +03:00
< Card.Title class = "flex items-center gap-2 text-sm font-medium" >
< Coins class = "h-4 w-4" / >
Circulating Supply
< / Card.Title >
2025-05-21 21:34:22 +03:00
< / Card.Header >
2025-05-23 16:26:02 +03:00
< Card.Content class = "pt-0" >
2025-05-27 15:06:37 +03:00
< p class = "text-xl font-bold" >
2025-05-27 16:19:57 +03:00
{ formatSupply ( coin . circulatingSupply )} < span
class="text-muted-foreground ml-1 text-xs"
>
2025-05-27 15:06:37 +03:00
of { formatSupply ( coin . initialSupply )} total
< / span >
2025-05-21 21:34:22 +03:00
< / p >
< / Card.Content >
< / Card.Root >
2025-05-23 16:26:02 +03:00
<!-- 24h Change -->
2025-05-27 15:06:37 +03:00
< Card.Root class = "gap-1" >
< Card.Header >
2025-05-23 16:26:02 +03:00
< Card.Title class = "text-sm font-medium" > 24h Change< / Card.Title >
< / Card.Header >
< Card.Content class = "pt-0" >
< div class = "flex items-center gap-2" >
{ #if coin . change24h >= 0 }
< TrendingUp class = "h-4 w-4 text-green-500" / >
{ : else }
< TrendingDown class = "h-4 w-4 text-red-500" / >
{ /if }
< Badge variant = { coin . change24h >= 0 ? 'success' : 'destructive' } class="text-sm" >
2025-05-23 21:45:41 +03:00
{ coin . change24h >= 0 ? '+' : '' }{ Number ( coin . change24h ). toFixed ( 2 )} %
2025-05-23 16:26:02 +03:00
< / Badge >
< / div >
< / Card.Content >
< / Card.Root >
2025-05-21 21:34:22 +03:00
< / div >
2025-05-24 19:28:38 +03:00
<!-- Comments Section -->
< CommentSection { coinSymbol } />
2025-05-21 21:34:22 +03:00
< / div >
2025-05-27 14:12:29 +03:00
{ /if }
< / div >