diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro index c71950e610f..0eb89ad8164 100644 --- a/site/src/pages/index.astro +++ b/site/src/pages/index.astro @@ -4,6 +4,7 @@ import "../styles/techapi.css"; const raw = import.meta.env.BASE_URL; const base = raw.endsWith("/") ? raw : raw + "/"; const ghUrl = "https://github.com/GetTechAPI/TechAPI"; +const engineUrl = "https://github.com/GetTechAPI/TechEngine"; const endpoints = [ { label: "/v1/smartphones", desc: "List all phones", href: `${base}v1/smartphones/index.json` }, @@ -318,6 +319,7 @@ const endpoints = [

⚠ Curated, growing dataset — not yet an exhaustive catalog of every device.

+

Validated, scored & served by TTechEngine — the open data + scoring engine.

diff --git a/site/src/scripts/techapi.js b/site/src/scripts/techapi.js index bc7ea846360..e701491da60 100644 --- a/site/src/scripts/techapi.js +++ b/site/src/scripts/techapi.js @@ -88,7 +88,10 @@ async function loadList(resource) { let v = obj[k]; if (v && typeof v === "object" && v.name) v = v.name; // {name,slug} → name if (k === "display" && v) v = { size_inch: v.size_inch, refresh_hz: v.refresh_hz }; - if (k === "score" && v) v = { overall: v.overall, performance: v.performance, camera: v.camera, cpu: v.cpu, gpu: v.gpu }; + if (k === "score" && v) { + const ax = v.perf || v.multi || v.graphics || v.cpu || {}; + v = { overall: v.overall, tier: ax.tier, index: ax.index, source: ax.source }; + } out[k] = v; } return out; @@ -563,8 +566,6 @@ if (resSel) populateSlugs("smartphones").then((items) => { /* ============================================================ FEATURED DEVICES ============================================================ */ -const PREFERRED = ["galaxy-s26-ultra", "iphone-17-pro-max", "pixel-10-pro", - "oneplus-14", "xiaomi-15-ultra", "galaxy-z-fold-7"]; function bar(label, v) { const w = v == null ? 0 : Math.round(v); @@ -572,54 +573,103 @@ function bar(label, v) { ${v == null ? "—" : v}`; } -function deviceCard(d) { + +// Per-category card shape: spec chips, score bars (label + value), subtitle, and the +// primary axis whose hybrid tier/era/source is surfaced as a badge + provenance line. +const withUnit = (v, suffix = "") => (v == null ? null : `${v}${suffix}`); +const CARD = { + smartphones: { + sub: (d) => d.soc?.name || "", + specs: (d) => [withUnit(d.ram_gb, "GB"), withUnit(d.battery_mah, "mAh"), + d.display?.size_inch ? `${d.display.size_inch}"` : null, withUnit(d.display?.refresh_hz, "Hz")], + bars: (sc) => [["Perf", sc.performance], ["Cam", sc.camera], ["Batt", sc.battery], ["Disp", sc.display]], + axis: (sc) => sc.perf, + }, + cpus: { + sub: (d) => d.architecture || d.segment || "", + specs: (d) => [d.cores ? `${d.cores}C/${d.threads}T` : null, withUnit(d.boost_clock_ghz, "GHz"), + withUnit(d.tdp_w, "W"), d.process_node], + bars: (sc) => [["Single", sc.single?.index], ["Multi", sc.multi?.index]], + axis: (sc) => sc.multi, + }, + gpus: { + sub: (d) => d.architecture || "", + specs: (d) => [withUnit(d.memory_gb, "GB"), d.memory_type, withUnit(d.tdp_w, "W"), withUnit(d.boost_clock_mhz, "MHz")], + bars: (sc) => [["Graphics", sc.graphics?.index]], + axis: (sc) => sc.graphics, + }, + socs: { + sub: (d) => d.gpu_name || "", + specs: (d) => [withUnit(d.process_nm, "nm"), d.gpu_name, withUnit(d.gpu_cores, " GPU"), withUnit(d.npu_tops, " TOPS")], + bars: (sc) => [["CPU", sc.cpu?.index], ["System", sc.system?.index]], + axis: (sc) => sc.cpu, + }, +}; +const prettyBench = (s) => s ? s.replace(/_/g, " ").replace(/\b(cpu|gpu|g3d|fp32|r23|r15|r10|r11 5|2024)\b/gi, + (m) => m.toUpperCase()).replace(/\bcinebench\b/i, "Cinebench").replace(/\bgeekbench\b/i, "Geekbench") + .replace(/\bpassmark\b/i, "PassMark").replace(/\bantutu score\b/i, "AnTuTu").replace(/\btimespy\b/i, "Time Spy") : ""; + +function deviceCard(d, category = "smartphones") { + const cfg = CARD[category] || CARD.smartphones; const sc = d.score || {}; const overall = sc.overall == null ? "—" : Math.round(sc.overall); - const initial = (d.brand?.name || d.name || "?").charAt(0).toUpperCase(); - const specs = [ - d.ram_gb ? `${d.ram_gb}GB` : null, - d.battery_mah ? `${d.battery_mah}mAh` : null, - d.display?.size_inch ? `${d.display.size_inch}"` : null, - d.display?.refresh_hz ? `${d.display.refresh_hz}Hz` : null, - ].filter(Boolean); + const brandName = d.brand?.name || d.manufacturer?.name || ""; + const initial = (brandName || d.name || "?").charAt(0).toUpperCase(); + const specs = cfg.specs(d).filter(Boolean); + const axis = cfg.axis(sc) || {}; + const tier = axis.tier ? `${esc(axis.tier)}` : ""; + const era = axis.era ? `${esc(axis.era)}` : ""; + const src = axis.source ? `
via ${esc(prettyBench(axis.source))}
` : ""; const el = document.createElement("article"); el.className = "card"; el.dataset.slug = d.slug; el.innerHTML = `
${esc(initial)}
-
${esc(d.brand?.name || "")}
+
${esc(brandName)}
${esc(d.name)}
-
${esc(d.soc?.name || "")}
+
${esc(cfg.sub(d))}
${overall}score
-
${specs.map((s) => `${esc(s)}`).join("")}
-
${bar("Perf", sc.performance)}${bar("Cam", sc.camera)}${bar("Batt", sc.battery)}${bar("Disp", sc.display)}
`; +
${tier}${era}${specs.map((s) => `${esc(s)}`).join("")}
+
${cfg.bars(sc).map(([l, v]) => bar(l, v)).join("")}
${src}`; if (d.image_url) { const img = new Image(); img.src = d.image_url; img.alt = d.name; img.loading = "lazy"; img.className = "thumb-img"; img.onload = () => el.querySelector(".thumb").appendChild(img); } el.addEventListener("click", () => { - resSel.value = "smartphones"; slugIn.value = d.slug; run("smartphones", d.slug); + resSel.value = category; slugIn.value = d.slug; run(category, d.slug); document.getElementById("playground").scrollIntoView({ behavior: "smooth" }); }); return el; } +// A cross-category showcase so the scoring is visible across phones + CPU + GPU + SoC. +const FEATURED = [ + { cat: "smartphones", slug: "galaxy-s26-ultra" }, + { cat: "cpus", slug: "core-i9-14900k" }, + { cat: "gpus", slug: "geforce-rtx-5090" }, + { cat: "smartphones", slug: "iphone-17-pro-max" }, + { cat: "socs", slug: "snapdragon-8-elite" }, + { cat: "cpus", slug: "ryzen-9-7950x" }, +]; (async function featured() { const cards = document.getElementById("cards"); if (!cards) return; try { - const items = await loadList("smartphones"); - const have = new Set(items.map((i) => i.slug)); - let slugs = PREFERRED.filter((s) => have.has(s)); - for (const it of items) { if (slugs.length >= 6) break; if (!slugs.includes(it.slug)) slugs.push(it.slug); } - const details = await Promise.all(slugs.slice(0, 6).map((s) => - getJSON(`v1/smartphones/${s}/index.json`).catch(() => null))); + let picks = await Promise.all(FEATURED.map((f) => + getJSON(`v1/${f.cat}/${f.slug}/index.json`).then((d) => ({ d, cat: f.cat })).catch(() => null))); + picks = picks.filter(Boolean); + if (!picks.length) { // fallback: first few phones if the curated slugs are absent + const items = await loadList("smartphones"); + const details = await Promise.all(items.slice(0, 6).map((it) => + getJSON(`v1/smartphones/${it.slug}/index.json`).then((d) => ({ d, cat: "smartphones" })).catch(() => null))); + picks = details.filter(Boolean); + } cards.innerHTML = ""; - details.filter(Boolean).forEach((d) => cards.appendChild(deviceCard(d))); + picks.forEach(({ d, cat }) => cards.appendChild(deviceCard(d, cat))); if (!cards.children.length) cards.innerHTML = '

Build the dataset to see featured devices.

'; const obs = new IntersectionObserver((es) => es.forEach((e) => { if (!e.isIntersecting) return; diff --git a/site/src/styles/techapi.css b/site/src/styles/techapi.css index 20a1920a9ad..fb3b6cdfdbf 100644 --- a/site/src/styles/techapi.css +++ b/site/src/styles/techapi.css @@ -458,9 +458,17 @@ section.block { padding: 76px 0; border-top: 1px solid var(--border); } .ring::before { content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--surface); } .ring b { position: relative; font-family: var(--mono); font-weight: 700; font-size: 16px; line-height: 1; } .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; } -.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 16px 0 14px; } +.chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 16px 0 14px; align-items: center; } .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); } +.chip-era { color: var(--muted); } +.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; } +.tier-S { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); } +.tier-A { color: var(--accent-text); border-color: color-mix(in srgb, var(--accent) 55%, var(--border)); } +.tier-B { color: var(--fg); } +.tier-C, .tier-D { color: var(--fg-2); } +.tier-E, .tier-F { color: var(--muted); } .bars { display: grid; gap: 8px; } +.card-src { margin-top: 12px; font-family: var(--mono); font-size: 10.5px; color: var(--faint); letter-spacing: .02em; } .sb { display: flex; align-items: center; gap: 10px; font-family: var(--mono); font-size: 11px; } .sb-l { width: 36px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; } .sb-v { width: 26px; text-align: right; color: var(--fg); font-variant-numeric: tabular-nums; } @@ -544,6 +552,10 @@ footer { padding: 48px 0 60px; border-top: 1px solid var(--border); } .foot-links { display: flex; gap: 18px; font-family: var(--mono); font-size: 13px; } .foot-links a { color: var(--muted); } .foot-links a:hover { color: var(--fg); } .foot-warn { font-family: var(--mono); font-size: 12.5px; color: var(--faint); margin-top: 16px; } +.foot-engine { font-family: var(--mono); font-size: 12.5px; color: var(--muted); margin-top: 10px; } +.foot-engine a { color: var(--fg-2); font-weight: 600; display: inline-flex; align-items: center; gap: 4px; } +.foot-engine a:hover { color: var(--accent-text); } +.foot-engine a .mark { width: 16px; height: 16px; font-size: 11px; display: inline-grid; place-items: center; background: var(--accent); color: var(--accent-ink); border-radius: 4px; font-weight: 700; } /* skeleton */ .skel { background: linear-gradient(100deg, var(--surface), var(--surface-2), var(--surface)); background-size: 200% 100%; animation: sh 1.3s infinite; border-radius: 6px; }