equal height columns for card images

This commit is contained in:
Zach Harding
2026-05-26 11:31:19 -04:00
parent c582a40894
commit 48b0098c6f
2 changed files with 63 additions and 2 deletions

View File

@@ -327,14 +327,14 @@ const facets = searchResults.results.slice(1).map((result: any) => {
)}
{pokemon.map((card:any) => (
<div class="col">
<div class="col equal-height-col">
{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>
)}
<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-glare"></div>
</div>

61
src/volatility.ts Normal file
View 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 (01 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;
}