diff --git a/viz/app/search_page.js b/viz/app/search_page.js
index ffd3c17..d535b9d 100644
--- a/viz/app/search_page.js
+++ b/viz/app/search_page.js
@@ -1,4 +1,3 @@
-/* viz/app/search_page.js */
import { esc, renderThumbHtml, prettyTs } from "./dom.js";
import { tokenizeQuery, matchesAllTokens, displaySku, keySkuForRow } from "./sku.js";
import { loadIndex, loadRecent, loadSavedQuery, saveQuery } from "./state.js";
@@ -32,7 +31,7 @@ export function renderSearch($app) {
let allAgg = [];
let indexReady = false;
- // sku(canonical) -> storeLabel -> url
+ // canonicalSku -> storeLabel -> url
let URL_BY_SKU_STORE = new Map();
function buildUrlMap(listings, canonicalSkuFn) {
@@ -60,11 +59,7 @@ export function renderSearch($app) {
function urlForAgg(it, storeLabel) {
const sku = String(it?.sku || "");
const s = String(storeLabel || "");
- return (
- URL_BY_SKU_STORE.get(sku)?.get(s) ||
- String(it?.sampleUrl || "").trim() ||
- ""
- );
+ return URL_BY_SKU_STORE.get(sku)?.get(s) || "";
}
function renderAggregates(items) {
@@ -81,8 +76,8 @@ export function renderSearch($app) {
const price = it.cheapestPriceStr ? it.cheapestPriceStr : "(no price)";
const store = it.cheapestStoreLabel || ([...it.stores][0] || "Store");
- // link must match displayed store label
- const href = urlForAgg(it, store);
+ // link must match the displayed store label
+ const href = urlForAgg(it, store) || String(it.sampleUrl || "").trim();
const storeBadge = href
? `
-
${renderThumbHtml(it.img)}
-
+
+ ${renderThumbHtml(it.img)}
+
-
+
${esc(it.name || "(no name)")}
-
-
-
-
${esc(price)}
- ${storeBadge}
${esc(displaySku(it.sku))}
+
+ ${esc(price)}
+ ${storeBadge}
+
@@ -160,10 +155,8 @@ export function renderSearch($app) {
: `${esc(r.oldPrice || "")} → ${esc(r.newPrice || "")}`;
const when = r.ts ? prettyTs(r.ts) : r.date || "";
-
const rawSku = String(r.sku || "");
const sku = canon(rawSku);
-
const img = aggBySku.get(sku)?.img || "";
const href = String(r.url || "").trim();
@@ -175,25 +168,26 @@ export function renderSearch($app) {
)}`
: `${esc(r.storeLabel || "")}`;
+ // date as a badge so it sits nicely in the single meta row
+ const dateBadge = when ? `${esc(when)}` : "";
+
return `
-
${renderThumbHtml(img)}
-
+
+ ${renderThumbHtml(img)}
+
-
+
${esc(r.name || "(no name)")}
-
- ${esc(kind)}
-
-
-
-
-
${esc(priceLine)}
- ${storeBadge}
-
${esc(when)}
${esc(displaySku(sku))}
+
+ ${esc(kind)}
+ ${esc(priceLine)}
+ ${storeBadge}
+ ${dateBadge}
+
diff --git a/viz/style.css b/viz/style.css
index 7d34706..00d76e5 100644
--- a/viz/style.css
+++ b/viz/style.css
@@ -1,4 +1,3 @@
-/* viz/style.css */
:root {
--bg: #0b0d10;
--panel: #12161b;
@@ -19,7 +18,7 @@ body {
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
-/* badge links should look like badges, but clearly clickable */
+/* badge links: keep badge look, but clearly clickable */
a.badge { color: var(--muted); }
a.badge:hover { text-decoration: underline; cursor: pointer; }
@@ -82,7 +81,7 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
.itemRow {
display: flex;
gap: 12px;
- align-items: stretch; /* key: let thumb fill height */
+ align-items: stretch; /* let thumb fill card height */
}
.thumbBox {
@@ -95,7 +94,6 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
display: flex;
align-items: stretch;
justify-content: center;
- height: auto;
min-height: 64px;
}
@@ -115,26 +113,6 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
.itemBody {
flex: 1;
min-width: 0;
- display: flex;
- gap: 14px;
- align-items: stretch;
-}
-
-.itemMain {
- flex: 1;
- min-width: 0;
- display: flex;
- flex-direction: column;
- justify-content: center;
- gap: 8px;
-}
-
-.itemFacts {
- display: flex;
- flex-direction: column;
- align-items: flex-end;
- justify-content: center;
- gap: 8px;
}
.itemTop {
@@ -147,6 +125,7 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
.itemName {
font-weight: 700;
font-size: 14px;
+ min-width: 0;
}
.badge {
@@ -161,22 +140,23 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
gap: 6px;
}
-.meta {
+.metaRow {
+ margin-top: 8px;
display: flex;
gap: 10px;
flex-wrap: wrap;
+ align-items: center;
color: var(--muted);
- font-size: 12px;
+ font-size: 13px;
}
-.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
-
-.priceBig {
- font-size: 14px;
+.price {
font-weight: 700;
color: var(--text);
}
+.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
+
.topbar {
display: flex;
align-items: center;
@@ -261,16 +241,6 @@ a.badge:hover { text-decoration: underline; cursor: pointer; }
.thumbBox { width: 56px; flex: 0 0 56px; min-height: 56px; }
.detailThumbBox { width: 84px; height: 84px; flex: 0 0 84px; }
- /* on mobile, keep the "facts" inline so cards don't get super tall */
- .itemBody { flex-direction: column; gap: 10px; }
- .itemFacts {
- flex-direction: row;
- flex-wrap: wrap;
- justify-content: flex-start;
- align-items: center;
- gap: 10px;
- }
-
.chartBox {
height: 58vh;
min-height: 260px;