equal height columns for card images
This commit is contained in:
@@ -327,14 +327,14 @@ const facets = searchResults.results.slice(1).map((result: any) => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{pokemon.map((card:any) => (
|
{pokemon.map((card:any) => (
|
||||||
<div class="col">
|
<div class="col equal-height-col">
|
||||||
{canAddInventory && (
|
{canAddInventory && (
|
||||||
<button type="button" class="btn btn-sm inventory-button position-relative float-end shadow-filter text-center p-2 fw-bold" data-card-id={card.cardId} hx-get={`/partials/card-modal?cardId=${card.cardId}`} hx-target="#cardModal" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#cardModal" onclick="event.stopPropagation(); sessionStorage.setItem('openModalTab', 'nav-vendor');">
|
<button type="button" class="btn btn-sm inventory-button position-relative float-end shadow-filter text-center p-2 fw-bold" data-card-id={card.cardId} hx-get={`/partials/card-modal?cardId=${card.cardId}`} hx-target="#cardModal" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#cardModal" onclick="event.stopPropagation(); sessionStorage.setItem('openModalTab', 'nav-vendor');">
|
||||||
+/–
|
+/–
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<div class="card-trigger position-relative" data-card-id={card.cardId} hx-get={`/partials/card-modal?cardId=${card.cardId}`} hx-target="#cardModal" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#cardModal" onclick="const cardTitle = this.querySelector('#cardImage').getAttribute('alt'); dataLayer.push({'event': 'virtualPageview', 'pageUrl': this.getAttribute('hx-get'), 'pageTitle': cardTitle, 'previousUrl': '/pokemon'});">
|
<div class="card-trigger position-relative" data-card-id={card.cardId} hx-get={`/partials/card-modal?cardId=${card.cardId}`} hx-target="#cardModal" hx-trigger="click" data-bs-toggle="modal" data-bs-target="#cardModal" onclick="const cardTitle = this.querySelector('#cardImage').getAttribute('alt'); dataLayer.push({'event': 'virtualPageview', 'pageUrl': this.getAttribute('hx-get'), 'pageTitle': cardTitle, 'previousUrl': '/pokemon'});">
|
||||||
<div class="image-grow rounded-4 card-image" data-energy={card.energyType} data-rarity={card.rarityName} data-variant={card.variant} data-name={card.productName}><img src={`/static/cards/${card.productId}.jpg`} alt={card.productName} id="cardImage" loading="lazy" decoding="async" class="img-fluid rounded-4 mb-2 w-100" onerror="this.onerror=null; this.src='/static/cards/default.jpg'; this.closest('.image-grow')?.setAttribute('data-default','true')"/><span class="position-absolute top-50 start-0 d-inline medium-icon"><FirstEditionIcon edition={card?.variant} /></span>
|
<div class="image-grow rounded-4 card-image" data-energy={card.energyType} data-rarity={card.rarityName} data-variant={card.variant} data-name={card.productName}><img src={`/static/cards/${card.productId}.jpg`} alt={card.productName} id="cardImage" loading="lazy" decoding="async" class="img-fluid rounded-4 mb-2 w-100 h-100" onerror="this.onerror=null; this.src='/static/cards/default.jpg'; this.closest('.image-grow')?.setAttribute('data-default','true')"/><span class="position-absolute top-50 start-0 d-inline medium-icon"><FirstEditionIcon edition={card?.variant} /></span>
|
||||||
<div class="holo-shine"></div>
|
<div class="holo-shine"></div>
|
||||||
<div class="holo-glare"></div>
|
<div class="holo-glare"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
61
src/volatility.ts
Normal file
61
src/volatility.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
export interface VolatilityResult {
|
||||||
|
label: 'High' | 'Medium' | 'Low' | '—';
|
||||||
|
monthlyVol: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes 30-day rolling volatility from an array of prices using
|
||||||
|
* log-return standard deviation scaled to a monthly expectation.
|
||||||
|
*
|
||||||
|
* @param prices - Ordered array of market prices (oldest → newest)
|
||||||
|
* @returns label ('High' | 'Medium' | 'Low' | '—') and monthlyVol (0–1 range)
|
||||||
|
*/
|
||||||
|
export function computeVolatility(prices: number[]): VolatilityResult {
|
||||||
|
if (prices.length < 2) return { label: '—', monthlyVol: 0 };
|
||||||
|
|
||||||
|
const returns: number[] = [];
|
||||||
|
for (let i = 1; i < prices.length; i++) {
|
||||||
|
returns.push(Math.log(prices[i] / prices[i - 1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const mean = returns.reduce((a, b) => a + b, 0) / returns.length;
|
||||||
|
const variance = returns.reduce((sum, r) => sum + (r - mean) ** 2, 0) / (returns.length - 1);
|
||||||
|
const monthlyVol = Math.sqrt(variance) * Math.sqrt(30);
|
||||||
|
|
||||||
|
const label: VolatilityResult['label'] =
|
||||||
|
monthlyVol >= 0.30 ? 'High'
|
||||||
|
: monthlyVol >= 0.15 ? 'Medium'
|
||||||
|
: 'Low';
|
||||||
|
|
||||||
|
return { label, monthlyVol: Math.round(monthlyVol * 100) / 100 };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Groups a list of price-history rows by condition and computes volatility
|
||||||
|
* for each, filtered to a rolling window.
|
||||||
|
*
|
||||||
|
* @param rows - Array of { condition, calculatedAt, marketPrice }
|
||||||
|
* @param windowMs - Rolling window in milliseconds (default: 30 days)
|
||||||
|
*/
|
||||||
|
export function volatilityByCondition(
|
||||||
|
rows: Array<{ condition: string; calculatedAt: string | Date | null; marketPrice: number | string | null }>,
|
||||||
|
windowMs = 30 * 86_400_000,
|
||||||
|
): Record<string, VolatilityResult> {
|
||||||
|
const cutoff = new Date(Date.now() - windowMs);
|
||||||
|
const grouped: Record<string, number[]> = {};
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
if (row.marketPrice == null || !row.calculatedAt) continue;
|
||||||
|
if (new Date(row.calculatedAt) < cutoff) continue;
|
||||||
|
const price = Number(row.marketPrice);
|
||||||
|
if (price <= 0) continue;
|
||||||
|
if (!grouped[row.condition]) grouped[row.condition] = [];
|
||||||
|
grouped[row.condition].push(price);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Record<string, VolatilityResult> = {};
|
||||||
|
for (const [condition, prices] of Object.entries(grouped)) {
|
||||||
|
result[condition] = computeVolatility(prices);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user