From 2bc388b6624cb27ba93d22fea8f28515b16e84da Mon Sep 17 00:00:00 2001 From: zach Date: Sat, 21 Feb 2026 16:26:34 -0500 Subject: [PATCH] 404 page created with random pokemon encounter. Add sample data to latest sales table and set up NM prices inside modal --- package-lock.json | 19 ++ package.json | 1 + src/assets/css/_bootstrap.scss | 4 +- src/assets/css/main.scss | 226 +++++++++---------- src/components/Card.astro | 16 +- src/components/EnergyIcon.astro | 4 +- src/components/SetIcon.astro | 20 ++ src/pages/404.astro | 26 +++ src/pages/partials/card-modal.astro | 114 +++++++--- src/sampleData/chartdata.json | 318 +++++++++++++++++++++++++++ src/sampleData/latestsales.json | 52 +++++ src/svg/rarity/common.svg | 17 +- src/svg/rarity/illustration_rare.svg | 17 +- src/svg/rarity/rare.svg | 17 +- src/svg/rarity/uncommon.svg | 18 +- src/svg/set/ascended_heroes.svg | 1 + 16 files changed, 693 insertions(+), 177 deletions(-) create mode 100644 src/pages/404.astro create mode 100644 src/sampleData/chartdata.json create mode 100644 src/sampleData/latestsales.json create mode 100644 src/svg/set/ascended_heroes.svg diff --git a/package-lock.json b/package-lock.json index 0d10d28..15f672b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "astro": "^5.17.1", "bootstrap": "^5.3.8", "chalk": "^5.6.2", + "chart.js": "^4.5.1", "dotenv": "^17.2.4", "drizzle-orm": "^1.0.0-beta.15-859cf75", "mysql2": "^3.16.3", @@ -1365,6 +1366,12 @@ "node": ">=12" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@oslojs/encoding": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@oslojs/encoding/-/encoding-1.1.0.tgz", @@ -2722,6 +2729,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chart.js": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", diff --git a/package.json b/package.json index ffb50e7..3a2489d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "astro": "^5.17.1", "bootstrap": "^5.3.8", "chalk": "^5.6.2", + "chart.js": "^4.5.1", "dotenv": "^17.2.4", "drizzle-orm": "^1.0.0-beta.15-859cf75", "mysql2": "^3.16.3", diff --git a/src/assets/css/_bootstrap.scss b/src/assets/css/_bootstrap.scss index 0aee24c..48e31b0 100644 --- a/src/assets/css/_bootstrap.scss +++ b/src/assets/css/_bootstrap.scss @@ -19,7 +19,7 @@ @import 'bootstrap/scss/images'; @import 'bootstrap/scss/nav'; // @import 'bootstrap/scss/accordion'; -// @import 'bootstrap/scss/alert'; +@import 'bootstrap/scss/alert'; // @import 'bootstrap/scss/badge'; // @import 'bootstrap/scss/breadcrumb'; // @import 'bootstrap/scss/button-group'; @@ -39,7 +39,7 @@ // @import 'bootstrap/scss/popover'; // @import 'bootstrap/scss/progress'; // @import 'bootstrap/scss/spinners'; -// @import 'bootstrap/scss/tables'; +@import 'bootstrap/scss/tables'; // @import 'bootstrap/scss/toasts'; // @import 'bootstrap/scss/tooltip'; @import 'bootstrap/scss/transitions'; diff --git a/src/assets/css/main.scss b/src/assets/css/main.scss index 1d02bc3..d31c950 100644 --- a/src/assets/css/main.scss +++ b/src/assets/css/main.scss @@ -28,151 +28,133 @@ } // ---------------------- -// Card +// Cards & Modal // ---------------------- -.tcg-card { - cursor: pointer; -} - .modal-xl { - @media (min-width: 768px) { - max-width: 95vw; - } - @media (min-width: 1400px) { - max-width: 90vw; - } + @media (min-width: 768px) { + max-width: 95vw; + } + @media (min-width: 1400px) { + max-width: 90vw; + } } .card-modal { - background-color: rgba(1, 11, 18, .8); - cursor: default; + background-color: rgba(1, 11, 18, 0.8); + cursor: default; } -.nav-link:hover, .nav-link:focus { - color: rgba(255, 255, 255, 0.87); -} - -.nav-tabs .nav-link.active, .nav-tabs .nav-item.show .nav-link { - color: rgba(0, 0, 0, .94); -} - -.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { - border-color: rgba(0, 0, 0, .0); +canvas { + max-width: 100%; + height: 300px; } +// ---------------------- +// Navigation Tabs +// ---------------------- .nav-link { - font-weight: 600; - color: rgba(255,255,255,67); - transition: margin-top 0.2s cubic-bezier(0.5, 0, 0.3, 1), - padding-top 0.2s cubic-bezier(0.5, 0, 0.3, 1), - padding-bottom 0.2s cubic-bezier(0.5, 0, 0.3, 1); -} -.nav-link:hover, .nav-link:focus { + font-weight: 600; + color: rgba(255, 255, 255, 0.67); + transition: + margin-top 0.2s cubic-bezier(0.5, 0, 0.3, 1), + padding-top 0.2s cubic-bezier(0.5, 0, 0.3, 1), + padding-bottom 0.2s cubic-bezier(0.5, 0, 0.3, 1); + + &:hover, + &:focus { color: rgba(0, 0, 0, 0.87); -} -.nav-link.nm, .nav-link.nm:hover, .nav-link.nm:focus { - border-bottom: 3px solid rgba(156, 204, 102, 1); -} -.nav-link.nm:hover, .nav-link.nm:focus { - background-color: rgba(156, 204, 102, .67); -} -.nav-link.nm.active { - background-color: rgba(156, 204, 102, 1); - border-bottom: 3px solid rgba(156, 204, 102, 1); -} -.nav-link.lp, .nav-link.lp:hover, .nav-link.lp:focus { - border-bottom: 3px solid rgba(211, 225, 86, 1); -} -.nav-link.lp:hover, .nav-link.lp:focus { - background-color: rgba(211, 225, 86, .67); -} -.nav-link.lp.active { - background-color: rgba(211, 225, 86, 1); - border-bottom: 3px solid rgba(211, 225, 86, 1); -} -.nav-link.mp, .nav-link.mp:hover, .nav-link.mp:focus { - border-bottom: 3px solid rgba(255, 238, 87, 1); -} -.nav-link.mp:hover, .nav-link.mp:focus { - background-color: rgba(255, 238, 87, .67); -} -.nav-link.mp.active { - background-color: rgba(255, 238, 87, 1); - border-bottom: 3px solid rgba(255, 238, 87, 1); -} -.nav-link.hp, .nav-link.hp:hover, .nav-link.hp:focus { - border-bottom: 3px solid rgba(255, 201, 41, 1); -} -.nav-link.hp:hover, .nav-link.hp:focus { - background-color: rgba(255, 201, 41, .67); -} -.nav-link.hp.active { - background-color: rgba(255, 201, 41, 1); - border-bottom: 3px solid rgba(255, 201, 41, 1); -} -.nav-link.dmg, .nav-link.dmg:hover, .nav-link.dmg:focus { - border-bottom: 3px solid rgba(255, 167, 36, 1); -} -.nav-link.dmg:hover, .nav-link.dmg:focus { - background-color: rgba(255, 167, 36, .67); -} -.nav-link.dmg.active { - background-color: rgba(255, 167, 36, 1); - border-bottom: 3px solid rgba(255, 167, 36, 1); -} -.nav-link.vendor, .nav-link.vendor:hover, .nav-link.vendor:focus { - border-bottom: 3px solid hsl(262, 47%, 55%); -} -.nav-link.vendor:hover, .nav-link.vendor:focus { - background-color: hsla(262, 47%, 55%, .67); -} -.nav-link.vendor.active { - color: rgba(255, 255, 255, 0.87); - background-color: hsl(262, 47%, 55%); - border-bottom: 3px solid hsl(262, 47%, 55%); + } } -.dark-callout { - @media (min-width: 768px) { - background-color: rgba(44, 48, 59, 1); +.nav-tabs { + .nav-link.active, + .nav-item.show .nav-link { + color: rgba(0, 0, 0, 0.94); + } + + .nav-link:hover, + .nav-link:focus { + border-color: transparent; + } +} + +// Tiered Nav-Link Colors +$tiers: ( + nm: rgba(156, 204, 102, 1), + lp: rgba(211, 225, 86, 1), + mp: rgba(255, 238, 87, 1), + hp: rgba(255, 201, 41, 1), + dmg: rgba(255, 167, 36, 1), + vendor: hsl(262, 47%, 55%) +); + +@each $name, $color in $tiers { + .nav-link.#{$name} { + border-bottom: 3px solid $color; + + &:hover, + &:focus { + background-color: rgba($color, 0.67); } + + &.active { + background-color: $color; + border-bottom-color: $color; + @if $name == vendor { + color: rgba(255, 255, 255, 0.87); + } + } + } +} + +// ---------------------- +// Misc Components +// ---------------------- +.dark-callout { + @media (min-width: 768px) { + background-color: rgba(44, 48, 59, 1); + } } .card-image { - aspect-ratio: 23/32; + aspect-ratio: 23 / 32; object-fit: cover; z-index: 998; + cursor: pointer; } +// Icon sizes .small-icon svg { width: 100%; max-height: 16px; margin-top: -0.25rem; } -.energy-icon svg { - width:2.5rem; +.energy-icon svg, +.rarity-icon-large svg, +.set-icon svg { + width: 2.5rem; margin-top: -0.25rem; - margin-right: -0.25rem; } -.rarity-icon-large svg { - width: 2.5rem; +.rarity-icon-large svg, +.set-icon svg { margin-bottom: -0.25rem; +} + +.energy-icon svg { margin-right: -0.25rem; } .set-icon svg { - width: 2.5rem; - margin-bottom: -0.25rem; margin-left: -0.25rem; } .shadow-filter { - //filter: drop-shadow(0 30px 30px #333); - filter: drop-shadow(0 5px 5px rgba(0, 0, 0, 0.3)) - drop-shadow(0 4px 6px rgba(0, 0, 0, 0.2)); + filter: + drop-shadow(0 5px 5px rgba(0, 0, 0, 0.3)) + drop-shadow(0 4px 6px rgba(0, 0, 0, 0.2)); } // ---------------------- @@ -193,7 +175,6 @@ ); } -// Base label style .price-label { font-size: 0.7rem; font-weight: 600; @@ -215,29 +196,24 @@ @media (min-width: 1600px) { font-size: 1rem; } -} -// Your palette tiers -.price-label:nth-of-type(n + 2) { - background-color: hsl(66, 70%, 61%); + &:nth-of-type(n + 2) { + background-color: hsl(66, 70%, 61%); + } + &:nth-of-type(n + 3) { + background-color: hsl(54, 100%, 67%); + } + &:nth-of-type(n + 4) { + background-color: hsl(45, 100%, 58%); + } + &:last-of-type { + background-color: hsl(36, 100%, 57%); + border-radius: 0.33rem; + } } -.price-label:nth-of-type(n + 3) { - background-color: hsl(54, 100%, 67%); -} - -.price-label:nth-of-type(n + 4) { - background-color: hsl(45, 100%, 58%); -} - -.price-label:last-of-type { - background-color: hsl(36, 100%, 57%); - border-radius: 0.33rem; -} - - // ---------------------- -// Search Elements +// Search // ---------------------- @media (max-width: 768px) { .search-box, diff --git a/src/components/Card.astro b/src/components/Card.astro index c866a6d..7694220 100644 --- a/src/components/Card.astro +++ b/src/components/Card.astro @@ -9,7 +9,7 @@ import RarityIcon from './RarityIcon.astro'; const { query } = Astro.props; const searchResults = await client.collections('cards').documents().search({ q: query, - query_by: 'productLineName,productName,setName,number,rarityName,Artist', + query_by: 'productLineName,productName,setName,number,rarityName', per_page: 250, }); const productIds = searchResults.hits?.map((hit: any) => hit.document.productId) ?? []; @@ -38,9 +38,10 @@ const formatPrice = (price:any) => { const order = ["Near Mint", "Lightly Played", "Moderately Played", "Heavily Played", "Damaged"]; --- {pokemon.map((card) => ( -
-
- {card.productName} +
+
+ {card.productName} +
{card.prices .slice() @@ -49,7 +50,7 @@ const order = ["Near Mint", "Lightly Played", "Moderately Played", "Heavily Play arr.findIndex(p => p.condition === price.condition) === index ) .map((price) => ( -
+
{price.condition.split(' ').map((w) => w[0]).join('')}
${formatPrice(price.marketPrice)}
@@ -57,10 +58,9 @@ const order = ["Near Mint", "Lightly Played", "Moderately Played", "Heavily Play
{card.productName}
-
{card.set?.setCode}
-
{card.number}
+
{card.set?.setCode}
+
{card.number}
-
))} \ No newline at end of file diff --git a/src/components/EnergyIcon.astro b/src/components/EnergyIcon.astro index b7551b1..5501ef1 100644 --- a/src/components/EnergyIcon.astro +++ b/src/components/EnergyIcon.astro @@ -1,7 +1,7 @@ --- import grass from "/src/svg/energy/grass.svg?raw"; import fairy from "/src/svg/energy/fairy.svg?raw"; -import dark from "/src/svg/energy/dark.svg?raw"; +import darkness from "/src/svg/energy/dark.svg?raw"; import dragon from "/src/svg/energy/dragon.svg?raw"; import fire from "/src/svg/energy/fire.svg?raw"; import water from "/src/svg/energy/water.svg?raw"; @@ -16,7 +16,7 @@ const { energy } = Astro.props; const energyMap = { "Grass": grass, "Fairy": fairy, - "Dark": dark, + "Darkness": darkness, "Dragon": dragon, "Fire": fire, "Water": water, diff --git a/src/components/SetIcon.astro b/src/components/SetIcon.astro index dd2e1fc..ad47a69 100644 --- a/src/components/SetIcon.astro +++ b/src/components/SetIcon.astro @@ -27,10 +27,21 @@ import diamond_and_pearl from "/src/svg/set/diamond_and_pearl.svg?raw"; import double_crisis from "/src/svg/set/double_crisis.svg?raw"; import dragon_majesty from "/src/svg/set/dragon_majesty.svg?raw"; import neo_genesis from "/src/svg/set/neo_genesis.svg?raw"; +import jungle from "/src/svg/set/jungle.svg?raw"; +import fossil from "/src/svg/set/fossil.svg?raw"; +import ascended_heroes from "/src/svg/set/ascended_heroes.svg?raw"; +import expedition from "/src/svg/set/expedition.svg?raw"; +import dragonvault from "/src/svg/set/dragon_vault.svg?raw"; +import dragonsexalted from "/src/svg/set/dragons_exalted.svg?raw"; +import ecardsample from "/src/svg/set/e-card_sample_set.svg?raw"; +import emergingpowers from "/src/svg/set/emerging_powers.svg?raw"; +import evolutions from "/src/svg/set/evolutions.svg?raw"; +import evolvingskies from "/src/svg/set/evolving_skies.svg?raw"; const { set } = Astro.props; const setMap = { + "ME: Ascended Heroes": ascended_heroes, "Ancient Origins": ancient_origins, "Aquapolis": aquapolis, "Arceus": arceus, @@ -59,6 +70,15 @@ const setMap = { "Double Crisis": double_crisis, "Dragon Majesty": dragon_majesty, "Neo Genesis": neo_genesis, + "Jungle": jungle, + "Fossil": fossil, + "Expedition Base Set": expedition, + "Dragon Vault": dragonvault, + "Dragons Exalted": dragonsexalted, + "E-Card Sample": ecardsample, + "Emerging Powers": emergingpowers, + "Evolutions": evolutions, + "SWSH07: Evolving Skies": evolvingskies, }; const svg = setMap[set as keyof typeof setMap] ?? ""; diff --git a/src/pages/404.astro b/src/pages/404.astro new file mode 100644 index 0000000..2fe5274 --- /dev/null +++ b/src/pages/404.astro @@ -0,0 +1,26 @@ +--- +import Layout from '../layouts/Main.astro'; +import StickyFilter from '../components/StickyFilter.astro'; + +const searchParams = Astro.url.searchParams; +const query = searchParams.get('q') || '*'; + +const randomNumber = Math.floor(Math.random() * 1000) + 1; +--- + + + +
+
+
+

404 - Page Not Found

+

Sorry, the page you are looking for does not exist.

+

Return to the home page or search for another Pokémon.

+
+
+ + +
+
\ No newline at end of file diff --git a/src/pages/partials/card-modal.astro b/src/pages/partials/card-modal.astro index b7ac0fa..c3d5652 100644 --- a/src/pages/partials/card-modal.astro +++ b/src/pages/partials/card-modal.astro @@ -1,6 +1,15 @@ --- import ebay from "/vendors/ebay.svg?raw"; +import SetIcon from '../../components/SetIcon.astro'; +import EnergyIcon from '../../components/EnergyIcon.astro'; +import RarityIcon from '../../components/RarityIcon.astro'; import { db } from '../../db/index.ts'; +import { privateDecrypt } from "node:crypto"; + +import latestSales from '../../sampleData/latestsales.json'; +import chartdata from '../../sampleData/chartdata.json'; + +const priceData = chartdata; export const partial = true; export const prerender = false; @@ -18,12 +27,43 @@ const card = await db.query.cards.findFirst({ } }); +const nearMint = await db.query.skus.findFirst({ + where: { + productId: Number(productId), + } +}); + +const nearMintPrice = nearMint?.marketPrice ?? null; + +const calculatedAt = new Date(nearMint?.calculatedAt); + +function timeAgo(date) { + const seconds = Math.floor((Date.now() - date) / 1000); + + const intervals = { + year: 31536000, + month: 2592000, + day: 86400, + hour: 3600, + minute: 60 + }; + + for (const [unit, value] of Object.entries(intervals)) { + const count = Math.floor(seconds / value); + if (count >= 1) return `${count} ${unit}${count > 1 ? "s" : ""} ago`; + } + + return "just now"; +} + ---