Skip to content

Commit d8b230d

Browse files
committed
feat(site): cross-category score cards (CPU/GPU/SoC) with tier badge + provenance
The featured cards and playground were smartphone-only. Generalize deviceCard(d, category) so CPUs, GPUs and SoCs render their hybrid scores too — the overall ring plus per-category bars (CPU single/multi, GPU graphics, SoC cpu/system), a within- generation tier badge (S–F), the era, and a "via <benchmark>" provenance line. The featured section now showcases a phone + CPU + GPU + SoC mix, and the hero terminal surfaces overall/tier/source. Pairs with engine scoring v2.0.0. Refs #1
1 parent 45e4d32 commit d8b230d

2 files changed

Lines changed: 82 additions & 24 deletions

File tree

site/src/scripts/techapi.js

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,10 @@ async function loadList(resource) {
8888
let v = obj[k];
8989
if (v && typeof v === "object" && v.name) v = v.name; // {name,slug} → name
9090
if (k === "display" && v) v = { size_inch: v.size_inch, refresh_hz: v.refresh_hz };
91-
if (k === "score" && v) v = { overall: v.overall, performance: v.performance, camera: v.camera, cpu: v.cpu, gpu: v.gpu };
91+
if (k === "score" && v) {
92+
const ax = v.perf || v.multi || v.graphics || v.cpu || {};
93+
v = { overall: v.overall, tier: ax.tier, index: ax.index, source: ax.source };
94+
}
9295
out[k] = v;
9396
}
9497
return out;
@@ -563,63 +566,110 @@ if (resSel) populateSlugs("smartphones").then((items) => {
563566
/* ============================================================
564567
FEATURED DEVICES
565568
============================================================ */
566-
const PREFERRED = ["galaxy-s26-ultra", "iphone-17-pro-max", "pixel-10-pro",
567-
"oneplus-14", "xiaomi-15-ultra", "galaxy-z-fold-7"];
568569

569570
function bar(label, v) {
570571
const w = v == null ? 0 : Math.round(v);
571572
return `<div class="sb"><span class="sb-l">${label}</span>
572573
<span class="sb-track"><span class="sb-fill" data-w="${w}"></span></span>
573574
<span class="sb-v">${v == null ? "—" : v}</span></div>`;
574575
}
575-
function deviceCard(d) {
576+
577+
// Per-category card shape: spec chips, score bars (label + value), subtitle, and the
578+
// primary axis whose hybrid tier/era/source is surfaced as a badge + provenance line.
579+
const withUnit = (v, suffix = "") => (v == null ? null : `${v}${suffix}`);
580+
const CARD = {
581+
smartphones: {
582+
sub: (d) => d.soc?.name || "",
583+
specs: (d) => [withUnit(d.ram_gb, "GB"), withUnit(d.battery_mah, "mAh"),
584+
d.display?.size_inch ? `${d.display.size_inch}"` : null, withUnit(d.display?.refresh_hz, "Hz")],
585+
bars: (sc) => [["Perf", sc.performance], ["Cam", sc.camera], ["Batt", sc.battery], ["Disp", sc.display]],
586+
axis: (sc) => sc.perf,
587+
},
588+
cpus: {
589+
sub: (d) => d.architecture || d.segment || "",
590+
specs: (d) => [d.cores ? `${d.cores}C/${d.threads}T` : null, withUnit(d.boost_clock_ghz, "GHz"),
591+
withUnit(d.tdp_w, "W"), d.process_node],
592+
bars: (sc) => [["Single", sc.single?.index], ["Multi", sc.multi?.index]],
593+
axis: (sc) => sc.multi,
594+
},
595+
gpus: {
596+
sub: (d) => d.architecture || "",
597+
specs: (d) => [withUnit(d.memory_gb, "GB"), d.memory_type, withUnit(d.tdp_w, "W"), withUnit(d.boost_clock_mhz, "MHz")],
598+
bars: (sc) => [["Graphics", sc.graphics?.index]],
599+
axis: (sc) => sc.graphics,
600+
},
601+
socs: {
602+
sub: (d) => d.gpu_name || "",
603+
specs: (d) => [withUnit(d.process_nm, "nm"), d.gpu_name, withUnit(d.gpu_cores, " GPU"), withUnit(d.npu_tops, " TOPS")],
604+
bars: (sc) => [["CPU", sc.cpu?.index], ["System", sc.system?.index]],
605+
axis: (sc) => sc.cpu,
606+
},
607+
};
608+
const prettyBench = (s) => s ? s.replace(/_/g, " ").replace(/\b(cpu|gpu|g3d|fp32|r23|r15|r10|r11 5|2024)\b/gi,
609+
(m) => m.toUpperCase()).replace(/\bcinebench\b/i, "Cinebench").replace(/\bgeekbench\b/i, "Geekbench")
610+
.replace(/\bpassmark\b/i, "PassMark").replace(/\bantutu score\b/i, "AnTuTu").replace(/\btimespy\b/i, "Time Spy") : "";
611+
612+
function deviceCard(d, category = "smartphones") {
613+
const cfg = CARD[category] || CARD.smartphones;
576614
const sc = d.score || {};
577615
const overall = sc.overall == null ? "—" : Math.round(sc.overall);
578-
const initial = (d.brand?.name || d.name || "?").charAt(0).toUpperCase();
579-
const specs = [
580-
d.ram_gb ? `${d.ram_gb}GB` : null,
581-
d.battery_mah ? `${d.battery_mah}mAh` : null,
582-
d.display?.size_inch ? `${d.display.size_inch}"` : null,
583-
d.display?.refresh_hz ? `${d.display.refresh_hz}Hz` : null,
584-
].filter(Boolean);
616+
const brandName = d.brand?.name || d.manufacturer?.name || "";
617+
const initial = (brandName || d.name || "?").charAt(0).toUpperCase();
618+
const specs = cfg.specs(d).filter(Boolean);
619+
const axis = cfg.axis(sc) || {};
620+
const tier = axis.tier ? `<span class="tier tier-${esc(axis.tier)}">${esc(axis.tier)}</span>` : "";
621+
const era = axis.era ? `<span class="chip chip-era">${esc(axis.era)}</span>` : "";
622+
const src = axis.source ? `<div class="card-src">via ${esc(prettyBench(axis.source))}</div>` : "";
585623
const el = document.createElement("article");
586624
el.className = "card"; el.dataset.slug = d.slug;
587625
el.innerHTML = `
588626
<div class="card-top">
589627
<div class="thumb"><span class="thumb-fallback">${esc(initial)}</span></div>
590628
<div class="card-id">
591-
<div class="card-brand">${esc(d.brand?.name || "")}</div>
629+
<div class="card-brand">${esc(brandName)}</div>
592630
<div class="card-name">${esc(d.name)}</div>
593-
<div class="card-soc">${esc(d.soc?.name || "")}</div>
631+
<div class="card-soc">${esc(cfg.sub(d))}</div>
594632
</div>
595633
<div class="ring" style="--p:${sc.overall || 0}"><b>${overall}</b><i>score</i></div>
596634
</div>
597-
<div class="chips">${specs.map((s) => `<span class="chip">${esc(s)}</span>`).join("")}</div>
598-
<div class="bars">${bar("Perf", sc.performance)}${bar("Cam", sc.camera)}${bar("Batt", sc.battery)}${bar("Disp", sc.display)}</div>`;
635+
<div class="chips">${tier}${era}${specs.map((s) => `<span class="chip">${esc(s)}</span>`).join("")}</div>
636+
<div class="bars">${cfg.bars(sc).map(([l, v]) => bar(l, v)).join("")}</div>${src}`;
599637
if (d.image_url) {
600638
const img = new Image();
601639
img.src = d.image_url; img.alt = d.name; img.loading = "lazy"; img.className = "thumb-img";
602640
img.onload = () => el.querySelector(".thumb").appendChild(img);
603641
}
604642
el.addEventListener("click", () => {
605-
resSel.value = "smartphones"; slugIn.value = d.slug; run("smartphones", d.slug);
643+
resSel.value = category; slugIn.value = d.slug; run(category, d.slug);
606644
document.getElementById("playground").scrollIntoView({ behavior: "smooth" });
607645
});
608646
return el;
609647
}
610648

649+
// A cross-category showcase so the scoring is visible across phones + CPU + GPU + SoC.
650+
const FEATURED = [
651+
{ cat: "smartphones", slug: "galaxy-s26-ultra" },
652+
{ cat: "cpus", slug: "core-i9-14900k" },
653+
{ cat: "gpus", slug: "geforce-rtx-5090" },
654+
{ cat: "smartphones", slug: "iphone-17-pro-max" },
655+
{ cat: "socs", slug: "snapdragon-8-elite" },
656+
{ cat: "cpus", slug: "ryzen-9-7950x" },
657+
];
611658
(async function featured() {
612659
const cards = document.getElementById("cards");
613660
if (!cards) return;
614661
try {
615-
const items = await loadList("smartphones");
616-
const have = new Set(items.map((i) => i.slug));
617-
let slugs = PREFERRED.filter((s) => have.has(s));
618-
for (const it of items) { if (slugs.length >= 6) break; if (!slugs.includes(it.slug)) slugs.push(it.slug); }
619-
const details = await Promise.all(slugs.slice(0, 6).map((s) =>
620-
getJSON(`v1/smartphones/${s}/index.json`).catch(() => null)));
662+
let picks = await Promise.all(FEATURED.map((f) =>
663+
getJSON(`v1/${f.cat}/${f.slug}/index.json`).then((d) => ({ d, cat: f.cat })).catch(() => null)));
664+
picks = picks.filter(Boolean);
665+
if (!picks.length) { // fallback: first few phones if the curated slugs are absent
666+
const items = await loadList("smartphones");
667+
const details = await Promise.all(items.slice(0, 6).map((it) =>
668+
getJSON(`v1/smartphones/${it.slug}/index.json`).then((d) => ({ d, cat: "smartphones" })).catch(() => null)));
669+
picks = details.filter(Boolean);
670+
}
621671
cards.innerHTML = "";
622-
details.filter(Boolean).forEach((d) => cards.appendChild(deviceCard(d)));
672+
picks.forEach(({ d, cat }) => cards.appendChild(deviceCard(d, cat)));
623673
if (!cards.children.length) cards.innerHTML = '<p class="muted">Build the dataset to see featured devices.</p>';
624674
const obs = new IntersectionObserver((es) => es.forEach((e) => {
625675
if (!e.isIntersecting) return;

site/src/styles/techapi.css

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,9 +458,17 @@ section.block { padding: 76px 0; border-top: 1px solid var(--border); }
458458
.ring::before { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--surface); }
459459
.ring b { position: relative; font-family: var(--mono); font-weight: 700; font-size: 16px; line-height: 1; }
460460
.ring i { position: relative; font-family: var(--mono); font-style: normal; font-size: 6.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .14em; margin-top: 4px; }
461-
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 16px 0 14px; }
461+
.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 16px 0 14px; align-items: center; }
462462
.chip { font-family: var(--mono); font-size: 11.5px; padding: 4px 9px; border-radius: 3px; background: var(--surface-2); border: 1px solid var(--border); color: var(--fg-2); }
463+
.chip-era { color: var(--muted); }
464+
.tier { font-family: var(--mono); font-size: 11.5px; font-weight: 700; padding: 4px 8px; border-radius: 3px; border: 1px solid var(--border); letter-spacing: .02em; }
465+
.tier-S { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
466+
.tier-A { color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); }
467+
.tier-B { color: var(--fg); }
468+
.tier-C, .tier-D { color: var(--fg-2); }
469+
.tier-E, .tier-F { color: var(--muted); }
463470
.bars { display: grid; gap: 8px; }
471+
.card-src { margin-top: 12px; font-family: var(--mono); font-size: 10.5px; color: var(--faint); letter-spacing: .02em; }
464472
.sb { display: flex; align-items: center; gap: 10px; font-family: var(--mono); font-size: 11px; }
465473
.sb-l { width: 36px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
466474
.sb-v { width: 26px; text-align: right; color: var(--fg); font-variant-numeric: tabular-nums; }

0 commit comments

Comments
 (0)