Add initial project skeleton

This commit is contained in:
Dexter Barnes
2025-08-17 20:55:38 +01:00
commit edf68e8ddc
9 changed files with 1317 additions and 0 deletions

71
public/app.js Normal file
View File

@@ -0,0 +1,71 @@
const $ = (sel) => document.querySelector(sel);
const $$ = (sel) => Array.from(document.querySelectorAll(sel));
const playerInput = $('#player');
const resultsEl = $('#results');
const searchBtn = $('#searchBtn');
async function fetchJSON(url) {
const res = await fetch(url);
if (!res.ok) throw new Error(`Request failed ${res.status}`);
return res.json();
}
async function search() {
resultsEl.innerHTML = '';
const player = playerInput.value.trim();
if (!player) {
resultsEl.innerHTML = `<div class="empty">Enter a player name to search.</div>`;
return;
}
const params = new URLSearchParams();
params.set('player', player);
try {
const data = await fetchJSON(`/api/ebay/search?${params.toString()}`);
renderResults(data.results || []);
} catch (e) {
console.error(e);
resultsEl.innerHTML = `<div class="empty">Something went wrong. Try again.</div>`;
}
}
function renderResults(items) {
if (!items.length) {
resultsEl.innerHTML = `<div class="empty">No cards found. Try adjusting filters or the name.</div>`;
return;
}
const tpl = document.getElementById('card-template');
items.forEach(c => {
const node = tpl.content.firstElementChild.cloneNode(true);
const img = node.querySelector('.card-img');
img.src = c.image || '';
img.alt = c.title || 'Listing image';
const title = node.querySelector('.player');
title.textContent = c.title || 'Sold listing';
const price = (c.price ? `${c.currency || ''} ${Number(c.price).toFixed(2)}` : '');
const ended = (c.ended ? new Date(c.ended).toLocaleString() : '');
node.querySelector('.secondary').textContent = [price, ended].filter(Boolean).join(' • ');
// Wrap card with link to eBay
node.addEventListener('click', () => {
if (c.url) window.open(c.url, '_blank');
});
resultsEl.appendChild(node);
});
}
searchBtn.addEventListener('click', () => {
search();
});
// Press Enter to search
playerInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
search();
}
});

39
public/index.html Normal file
View File

@@ -0,0 +1,39 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Baseball Cards Search</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<header>
<h1>Baseball Cards Search</h1>
<p class="subtitle">Search eBay sold listings for a player's cards.</p>
</header>
<section class="controls">
<div class="control grow">
<input id="player" type="text" placeholder="Enter a player name (e.g., Derek Jeter)" autocomplete="off" />
</div>
<div class="control">
<label>&nbsp;</label>
<button id="searchBtn">Search Sold</button>
</div>
</section>
<section id="results" class="results"></section>
<template id="card-template">
<div class="card">
<img class="card-img" alt="Card image" />
<div class="meta">
<div class="line player"></div>
<div class="line secondary"></div>
</div>
</div>
</template>
<script src="/app.js" type="module"></script>
</body>
</html>

37
public/styles.css Normal file
View File

@@ -0,0 +1,37 @@
* { box-sizing: border-box; }
:root {
--bg: #0e0f13;
--panel: #171923;
--muted: #9aa4b2;
--text: #e6e9ef;
--accent: #4f8cff;
--accent-2: #7c4dff;
--border: #262a34;
}
body { margin: 0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; background: var(--bg); color: var(--text); }
header { padding: 24px 24px 8px; }
header h1 { margin: 0 0 6px; font-size: 24px; }
header .subtitle { margin: 0; color: var(--muted); font-size: 14px; }
.controls { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; padding: 40px 24px; max-width: 800px; margin: 0 auto; min-height: 50vh; }
.control { display: flex; flex-direction: column; gap: 6px; width: 100%; max-width: 560px; }
.control.grow { width: 100%; max-width: 560px; }
label { font-size: 12px; color: var(--muted); }
select, input[type="text"] { height: 48px; color: var(--text); background: var(--panel); border: 1px solid var(--border); border-radius: 10px; padding: 0 14px; outline: none; }
select:focus, input[type="text"]:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(79,140,255,0.15); }
button { height: 48px; border: none; border-radius: 10px; background: linear-gradient(135deg, var(--accent), var(--accent-2)); color: white; font-weight: 600; cursor: pointer; padding: 0 16px; width: 200px; align-self: center; }
button:hover { filter: brightness(1.05); }
.results { padding: 20px 24px 36px; display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px; }
.card { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; }
.card-img { width: 100%; height: 300px; object-fit: cover; background: #111; }
.meta { padding: 10px 12px 12px; }
.meta .line { line-height: 1.2; }
.meta .player { font-weight: 700; }
.meta .secondary { color: var(--muted); margin-top: 2px; font-size: 14px; }
.empty { color: var(--muted); text-align: center; grid-column: 1/-1; padding: 40px 0; }
@media (max-width: 820px) {
.controls { min-height: 40vh; padding: 28px 16px; }
}