RAG Source Panel
A retrieval-augmented answer with inline citation chips that spotlight matching source cards in a filterable, score-sortable side panel — hover or click a citation to scroll and highlight its evidence, complete with snippet, title, relevance score and URL.
MCP
Code
:root {
--bg: #0c0d10;
--surface: #15161b;
--surface-2: #1d1f27;
--ink: #e9eaf0;
--muted: #9a9cab;
--accent: #8b5cf6;
--accent-2: #22d3ee;
--ok: #34d399;
--warn: #fbbf24;
--danger: #f87171;
--line: rgba(255, 255, 255, 0.10);
--line-2: rgba(255, 255, 255, 0.18);
--r-sm: 8px;
--r-md: 12px;
--r-lg: 16px;
}
* {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
min-height: 100vh;
background:
radial-gradient(1100px 500px at 85% -10%, rgba(139, 92, 246, 0.18), transparent 60%),
radial-gradient(900px 500px at -5% 110%, rgba(34, 211, 238, 0.12), transparent 60%),
var(--bg);
color: var(--ink);
font-family: "Inter", system-ui, sans-serif;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
.app {
max-width: 1120px;
margin: 0 auto;
padding: clamp(18px, 4vw, 40px);
}
/* ---------- header ---------- */
.app__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 22px;
}
.brand {
display: flex;
align-items: center;
gap: 14px;
}
.brand__dot {
width: 34px;
height: 34px;
border-radius: 10px;
flex: none;
background: linear-gradient(135deg, var(--accent), var(--accent-2));
box-shadow: 0 0 0 1px var(--line), 0 8px 24px rgba(139, 92, 246, 0.4);
}
.brand__title {
margin: 0;
font-size: clamp(18px, 3vw, 22px);
font-weight: 700;
letter-spacing: -0.02em;
}
.brand__sub {
margin: 2px 0 0;
font-size: 13px;
color: var(--muted);
max-width: 48ch;
}
.badge {
flex: none;
font-size: 12px;
font-weight: 600;
padding: 7px 12px;
border-radius: 999px;
color: var(--accent-2);
background: rgba(34, 211, 238, 0.10);
border: 1px solid rgba(34, 211, 238, 0.28);
white-space: nowrap;
}
/* ---------- layout ---------- */
.grid {
display: grid;
grid-template-columns: 1.15fr 1fr;
gap: 18px;
align-items: start;
}
/* ---------- answer ---------- */
.answer {
background: linear-gradient(180deg, var(--surface), rgba(21, 22, 27, 0.7));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: clamp(16px, 2.4vw, 24px);
}
.answer__meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 14px;
}
.pill {
font-size: 11.5px;
font-weight: 500;
color: var(--muted);
padding: 5px 10px;
border-radius: var(--r-sm);
background: var(--surface-2);
border: 1px solid var(--line);
}
.pill--model {
color: var(--ink);
font-family: "JetBrains Mono", monospace;
font-weight: 500;
border-color: var(--line-2);
}
.answer__body {
font-size: 15.5px;
color: #dfe0ea;
}
.answer__body p {
margin: 0 0 14px;
}
.answer__body p:last-child {
margin-bottom: 0;
}
.answer__body strong {
color: #fff;
}
.answer__body code {
font-family: "JetBrains Mono", monospace;
font-size: 0.86em;
background: var(--surface-2);
border: 1px solid var(--line);
padding: 1px 6px;
border-radius: 6px;
color: var(--accent-2);
}
.answer__note {
margin: 18px 0 0;
padding-top: 14px;
border-top: 1px dashed var(--line);
font-size: 12.5px;
color: var(--muted);
}
/* citation chip */
.cite {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 20px;
padding: 0 5px;
margin: 0 1px 0 3px;
vertical-align: top;
transform: translateY(-2px);
font-size: 11px;
font-weight: 700;
line-height: 1;
color: var(--accent);
background: rgba(139, 92, 246, 0.14);
border: 1px solid rgba(139, 92, 246, 0.4);
border-radius: 6px;
cursor: pointer;
user-select: none;
transition: background 0.16s, color 0.16s, transform 0.16s, box-shadow 0.16s;
}
.cite::before {
content: "";
}
.cite:hover,
.cite:focus-visible,
.cite.is-active {
color: #fff;
background: var(--accent);
transform: translateY(-3px);
box-shadow: 0 4px 14px rgba(139, 92, 246, 0.5);
outline: none;
}
.cite:focus-visible {
outline: 2px solid var(--accent-2);
outline-offset: 2px;
}
/* ---------- panel ---------- */
.panel {
background: linear-gradient(180deg, var(--surface), rgba(21, 22, 27, 0.7));
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: 16px;
position: sticky;
top: 18px;
}
.panel__head {
padding-bottom: 14px;
margin-bottom: 8px;
border-bottom: 1px solid var(--line);
}
.panel__titlerow {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 12px;
}
.panel__title {
margin: 0;
font-size: 15px;
font-weight: 600;
letter-spacing: -0.01em;
}
.panel__count {
font-size: 12px;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 14px 18px;
align-items: center;
}
.control__label {
display: block;
font-size: 11.5px;
color: var(--muted);
margin-bottom: 6px;
}
.control__label b {
color: var(--ink);
font-family: "JetBrains Mono", monospace;
font-weight: 500;
font-variant-numeric: tabular-nums;
}
.control--range {
flex: 1 1 150px;
}
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 5px;
border-radius: 999px;
background: linear-gradient(90deg, var(--accent), var(--accent-2));
outline: none;
cursor: pointer;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
border: 3px solid var(--accent);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
border-radius: 50%;
background: #fff;
border: 3px solid var(--accent);
}
input[type="range"]:focus-visible {
box-shadow: 0 0 0 3px rgba(34, 211, 238, 0.35);
}
.segmented {
display: inline-flex;
padding: 3px;
gap: 3px;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: var(--r-sm);
}
.seg {
font: inherit;
font-size: 12px;
font-weight: 500;
color: var(--muted);
background: none;
border: none;
padding: 6px 11px;
border-radius: 6px;
cursor: pointer;
transition: background 0.16s, color 0.16s;
}
.seg:hover {
color: var(--ink);
}
.seg.is-active {
color: #fff;
background: var(--accent);
}
.seg:focus-visible {
outline: 2px solid var(--accent-2);
outline-offset: 2px;
}
/* ---------- source list ---------- */
.panel__list {
list-style: none;
margin: 8px 0 0;
padding: 4px;
max-height: 460px;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--line-2) transparent;
}
.panel__list::-webkit-scrollbar {
width: 8px;
}
.panel__list::-webkit-scrollbar-thumb {
background: var(--line-2);
border-radius: 999px;
}
.source {
position: relative;
display: block;
padding: 13px 14px 14px;
margin-bottom: 10px;
border-radius: var(--r-md);
background: var(--surface-2);
border: 1px solid var(--line);
cursor: pointer;
opacity: 0;
transform: translateY(8px);
transition: opacity 0.4s ease, transform 0.4s ease, border-color 0.18s,
box-shadow 0.18s, background 0.18s;
}
.source.is-visible {
opacity: 1;
transform: none;
}
.source:hover {
border-color: var(--line-2);
background: #22242e;
}
.source.is-active {
border-color: var(--accent);
background: rgba(139, 92, 246, 0.10);
box-shadow: 0 0 0 1px var(--accent), 0 10px 30px rgba(139, 92, 246, 0.28);
}
@keyframes pulseRing {
0% { box-shadow: 0 0 0 0 rgba(139, 92, 246, 0.55); }
100% { box-shadow: 0 0 0 12px rgba(139, 92, 246, 0); }
}
.source.is-pulsing::after {
content: "";
position: absolute;
inset: 0;
border-radius: var(--r-md);
animation: pulseRing 0.7s ease-out;
pointer-events: none;
}
.source__top {
display: flex;
align-items: flex-start;
gap: 10px;
margin-bottom: 8px;
}
.source__idx {
flex: none;
width: 22px;
height: 22px;
display: grid;
place-items: center;
font-size: 11px;
font-weight: 700;
color: var(--accent);
background: rgba(139, 92, 246, 0.16);
border: 1px solid rgba(139, 92, 246, 0.4);
border-radius: 6px;
}
.source.is-active .source__idx {
color: #fff;
background: var(--accent);
}
.source__title {
font-size: 13.5px;
font-weight: 600;
color: var(--ink);
line-height: 1.35;
}
.source__snippet {
font-size: 12.5px;
color: var(--muted);
margin: 0 0 12px;
}
.source__snippet mark {
background: rgba(34, 211, 238, 0.22);
color: #cffafe;
border-radius: 3px;
padding: 0 2px;
}
.source__foot {
display: flex;
align-items: center;
gap: 10px;
}
.score {
flex: 1 1 auto;
display: flex;
align-items: center;
gap: 8px;
}
.score__bar {
flex: 1 1 auto;
height: 6px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
overflow: hidden;
}
.score__fill {
height: 100%;
border-radius: 999px;
width: 0;
transition: width 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.score__val {
font-family: "JetBrains Mono", monospace;
font-size: 11.5px;
font-weight: 500;
color: var(--ink);
font-variant-numeric: tabular-nums;
min-width: 30px;
text-align: right;
}
.source__url {
display: inline-flex;
align-items: center;
gap: 5px;
max-width: 42%;
font-size: 11.5px;
color: var(--accent-2);
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.source__url:hover {
text-decoration: underline;
}
.source__url svg {
flex: none;
}
.panel__empty {
text-align: center;
color: var(--muted);
font-size: 13px;
padding: 26px 10px;
}
/* ---------- responsive ---------- */
@media (max-width: 860px) {
.grid {
grid-template-columns: 1fr;
}
.panel {
position: static;
}
.panel__list {
max-height: none;
}
}
@media (max-width: 520px) {
.app__head {
flex-direction: column;
align-items: flex-start;
}
.controls {
flex-direction: column;
align-items: stretch;
}
.source__foot {
flex-wrap: wrap;
}
.source__url {
max-width: 100%;
}
}
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
}
.source {
opacity: 1;
transform: none;
}
}"use strict";
/* ------------------------------------------------------------------ */
/* Retrieved sources — real-feeling RAG evidence for the answer above. */
/* ------------------------------------------------------------------ */
const SOURCES = [
{
id: 1,
title: "Malkov & Yashunin — Efficient and robust ANN using HNSW graphs",
snippet:
"The multi-layer structure places a small fraction of elements in upper layers, forming long-range links that behave as express highways during greedy search.",
highlight: "express highways",
score: 0.94,
url: "arxiv.org/abs/1603.09320",
},
{
id: 2,
title: "pgvector docs — Tuning HNSW recall with efSearch",
snippet:
"Increasing efSearch widens the dynamic candidate list, raising recall at the cost of a few additional graph hops per query.",
highlight: "efSearch widens the dynamic candidate list",
score: 0.88,
url: "github.com/pgvector/pgvector#hnsw",
},
{
id: 3,
title: "Faiss wiki — Graph-based indexes and logarithmic search",
snippet:
"Because navigation descends through sparse upper layers first, expected query complexity scales logarithmically with the number of stored vectors.",
highlight: "logarithmically with the number of stored vectors",
score: 0.81,
url: "github.com/facebookresearch/faiss/wiki",
},
{
id: 4,
title: "Weaviate blog — Why we default to HNSW",
snippet:
"Tunable accuracy lets operators pick a recall target per workload, from fast filtering passes to high-precision reranking stages.",
highlight: "Tunable accuracy",
score: 0.63,
url: "weaviate.io/blog/why-is-vector-search-so-fast",
},
{
id: 5,
title: "ANN-Benchmarks — Latency stability at million scale",
snippet:
"Across corpora from 100k to 10M vectors, HNSW query latency stayed nearly flat while brute-force scan time grew linearly.",
highlight: "query latency stayed nearly flat",
score: 0.57,
url: "ann-benchmarks.com",
},
];
const state = { sort: "score", thresh: 0 };
let activeId = null;
let pulseTimer = null;
const listEl = document.getElementById("sources");
const countEl = document.getElementById("count");
const emptyEl = document.getElementById("empty");
const threshEl = document.getElementById("thresh");
const threshValEl = document.getElementById("threshVal");
const answerEl = document.getElementById("answer");
const supportsSmooth = "scrollBehavior" in document.documentElement.style;
/* ---------- helpers ---------- */
function scoreColor(s) {
if (s >= 0.8) return "var(--ok)";
if (s >= 0.6) return "var(--warn)";
return "var(--danger)";
}
function markSnippet(text, phrase) {
if (!phrase) return escapeHtml(text);
const i = text.indexOf(phrase);
if (i < 0) return escapeHtml(text);
return (
escapeHtml(text.slice(0, i)) +
"<mark>" +
escapeHtml(phrase) +
"</mark>" +
escapeHtml(text.slice(i + phrase.length))
);
}
function escapeHtml(s) {
const d = document.createElement("div");
d.textContent = s;
return d.innerHTML;
}
const linkIcon =
'<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M7 17 17 7M9 7h8v8"/></svg>';
/* ---------- render ---------- */
const io =
"IntersectionObserver" in window
? new IntersectionObserver(
(entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add("is-visible");
io.unobserve(e.target);
}
});
},
{ root: listEl, threshold: 0.15 }
)
: null;
function render() {
const visible = SOURCES.filter((s) => s.score >= state.thresh).sort((a, b) =>
state.sort === "score" ? b.score - a.score : a.id - b.id
);
listEl.innerHTML = "";
emptyEl.hidden = visible.length !== 0;
visible.forEach((s) => {
const li = document.createElement("li");
li.className = "source";
li.dataset.id = String(s.id);
li.tabIndex = 0;
li.setAttribute("role", "button");
li.setAttribute(
"aria-label",
"Source " + s.id + ": " + s.title + ", relevance " + s.score.toFixed(2)
);
li.innerHTML =
'<div class="source__top">' +
'<span class="source__idx">' + s.id + "</span>" +
'<span class="source__title">' + escapeHtml(s.title) + "</span>" +
"</div>" +
'<p class="source__snippet">' + markSnippet(s.snippet, s.highlight) + "</p>" +
'<div class="source__foot">' +
'<span class="score">' +
'<span class="score__bar"><span class="score__fill"></span></span>' +
'<span class="score__val">' + s.score.toFixed(2) + "</span>" +
"</span>" +
'<a class="source__url" href="https://' + escapeHtml(s.url) +
'" target="_blank" rel="noopener noreferrer" title="' + escapeHtml(s.url) + '">' +
linkIcon + "<span>" + escapeHtml(s.url) + "</span></a>" +
"</div>";
const fill = li.querySelector(".score__fill");
fill.style.background = scoreColor(s.score);
requestAnimationFrame(() => {
fill.style.width = Math.round(s.score * 100) + "%";
});
if (io) io.observe(li);
else li.classList.add("is-visible");
listEl.appendChild(li);
});
countEl.textContent =
visible.length + (visible.length === 1 ? " shown" : " shown");
if (activeId != null && !visible.some((s) => s.id === activeId)) {
setActive(null);
} else if (activeId != null) {
applyActiveClasses();
}
}
/* ---------- cross-highlighting ---------- */
function citeChips(id) {
return answerEl.querySelectorAll('.cite[data-src="' + id + '"]');
}
function applyActiveClasses() {
answerEl.querySelectorAll(".cite").forEach((c) => {
c.classList.toggle("is-active", Number(c.dataset.src) === activeId);
});
listEl.querySelectorAll(".source").forEach((el) => {
el.classList.toggle("is-active", Number(el.dataset.id) === activeId);
});
}
function setActive(id) {
activeId = id;
applyActiveClasses();
}
function spotlightSource(id) {
setActive(id);
const card = listEl.querySelector('.source[data-id="' + id + '"]');
if (!card) return;
if (card.scrollIntoView) {
card.scrollIntoView(
supportsSmooth
? { behavior: "smooth", block: "nearest" }
: { block: "nearest" }
);
}
card.classList.remove("is-pulsing");
// reflow so the animation restarts even on repeat clicks
void card.offsetWidth;
card.classList.add("is-pulsing");
clearTimeout(pulseTimer);
pulseTimer = setTimeout(() => card.classList.remove("is-pulsing"), 750);
}
/* highlight cite chips when hovering a source card */
function highlightFromSource(id) {
citeChips(id).forEach((c) => c.classList.add("is-active"));
const card = listEl.querySelector('.source[data-id="' + id + '"]');
if (card) card.classList.add("is-active");
}
function clearHoverHighlight() {
if (activeId != null) {
applyActiveClasses();
return;
}
answerEl.querySelectorAll(".cite.is-active").forEach((c) => c.classList.remove("is-active"));
listEl.querySelectorAll(".source.is-active").forEach((c) => c.classList.remove("is-active"));
}
/* ---------- events ---------- */
// citation chips -> spotlight source
answerEl.addEventListener("click", (e) => {
const chip = e.target.closest(".cite");
if (chip) spotlightSource(Number(chip.dataset.src));
});
answerEl.addEventListener("keydown", (e) => {
const chip = e.target.closest(".cite");
if (chip && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
spotlightSource(Number(chip.dataset.src));
}
});
answerEl.addEventListener("mouseover", (e) => {
const chip = e.target.closest(".cite");
if (chip && activeId == null) highlightFromSource(Number(chip.dataset.src));
});
answerEl.addEventListener("mouseout", (e) => {
if (e.target.closest(".cite")) clearHoverHighlight();
});
// source cards -> spotlight + reverse highlight
listEl.addEventListener("click", (e) => {
if (e.target.closest("a")) return; // let links through
const card = e.target.closest(".source");
if (card) spotlightSource(Number(card.dataset.id));
});
listEl.addEventListener("keydown", (e) => {
const card = e.target.closest(".source");
if (card && (e.key === "Enter" || e.key === " ")) {
e.preventDefault();
spotlightSource(Number(card.dataset.id));
}
});
listEl.addEventListener("mouseover", (e) => {
const card = e.target.closest(".source");
if (card && activeId == null) highlightFromSource(Number(card.dataset.id));
});
listEl.addEventListener("mouseout", (e) => {
if (e.target.closest(".source")) clearHoverHighlight();
});
// controls
threshEl.addEventListener("input", () => {
state.thresh = parseFloat(threshEl.value);
threshValEl.textContent = state.thresh.toFixed(2);
render();
});
document.querySelectorAll(".seg").forEach((btn) => {
btn.addEventListener("click", () => {
if (btn.dataset.sort === state.sort) return;
state.sort = btn.dataset.sort;
document.querySelectorAll(".seg").forEach((b) => {
const on = b === btn;
b.classList.toggle("is-active", on);
b.setAttribute("aria-pressed", String(on));
});
render();
});
});
// deselect on outside click
document.addEventListener("click", (e) => {
if (!e.target.closest(".cite") && !e.target.closest(".source")) {
if (activeId != null) setActive(null);
}
});
/* ---------- init ---------- */
render();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>RAG Source Panel</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="app">
<header class="app__head">
<div class="brand">
<span class="brand__dot" aria-hidden="true"></span>
<div>
<h1 class="brand__title">RAG Source Panel</h1>
<p class="brand__sub">Answer grounded in retrieved sources — hover or click a citation to reveal its evidence.</p>
</div>
</div>
<span class="badge" title="Retrieval-augmented generation">RAG · grounded</span>
</header>
<div class="grid">
<!-- ANSWER -->
<section class="answer" aria-label="Generated answer">
<div class="answer__meta">
<span class="pill pill--model">gpt-grounded-4o</span>
<span class="pill">query: “How does HNSW indexing speed up vector search?”</span>
</div>
<div class="answer__body" id="answer">
<p>
Hierarchical Navigable Small World (<strong>HNSW</strong>) graphs accelerate
approximate nearest-neighbor search by organizing vectors into a layered
proximity graph, where upper layers act as long-range express lanes and lower
layers refine the result<span class="cite" data-src="1" tabindex="0" role="button" aria-label="Source 1">1</span><span class="cite" data-src="2" tabindex="0" role="button" aria-label="Source 2">2</span>.
</p>
<p>
A greedy walk starts at the sparse top layer and descends, so query time grows
<em>logarithmically</em> rather than linearly with corpus size<span class="cite" data-src="3" tabindex="0" role="button" aria-label="Source 3">3</span>.
Recall is tunable through the <code>efSearch</code> parameter, trading a few
extra hops for higher accuracy<span class="cite" data-src="2" tabindex="0" role="button" aria-label="Source 2">2</span><span class="cite" data-src="4" tabindex="0" role="button" aria-label="Source 4">4</span>.
</p>
<p>
In practice HNSW dominates production vector stores because its build cost is
paid once and query latency stays flat as data grows into the millions<span class="cite" data-src="5" tabindex="0" role="button" aria-label="Source 5">5</span>.
</p>
</div>
<p class="answer__note">Citations are generated inline. Focus a chip and press Enter, or hover it, to spotlight the source.</p>
</section>
<!-- SOURCES -->
<aside class="panel" aria-label="Source panel">
<div class="panel__head">
<div class="panel__titlerow">
<h2 class="panel__title">Sources</h2>
<span class="panel__count" id="count" aria-live="polite">5 shown</span>
</div>
<div class="controls">
<label class="control control--range">
<span class="control__label">Min score <b id="threshVal">0.00</b></span>
<input id="thresh" type="range" min="0" max="1" step="0.01" value="0" aria-label="Minimum relevance score" />
</label>
<div class="control control--sort" role="group" aria-label="Sort sources">
<span class="control__label">Sort</span>
<div class="segmented">
<button class="seg is-active" data-sort="score" aria-pressed="true">Relevance</button>
<button class="seg" data-sort="order" aria-pressed="false">Order</button>
</div>
</div>
</div>
</div>
<ol class="panel__list" id="sources"></ol>
<p class="panel__empty" id="empty" hidden>No sources meet this score threshold.</p>
</aside>
</div>
</main>
<script src="script.js"></script>
</body>
</html>The RAG Source Panel pairs a generated answer with the evidence behind it. The answer text carries inline citation chips like [1] and [2]; hovering or clicking one spotlights the matching source card in the panel on the right, scrolling it into view and pulsing a highlight ring. Hovering a source card works the other way too, lighting up every citation that points at it, so the link between claim and evidence is always visible in both directions.
Each source card shows a snippet, document title, a color-coded relevance score bar, and a clickable URL. The panel header exposes a live filter (minimum score threshold) and a sort toggle (by relevance or by document order), letting you triage which sources actually mattered. A running count updates as you filter, and keyboard focus moves cleanly through chips and cards with visible focus rings.
Interaction-wise the component leans on plain DOM APIs: Element.scrollIntoView({ behavior: "smooth", block: "nearest" }) for the scroll-to-source behavior, IntersectionObserver to fade in cards as they enter the panel viewport, and delegated pointer/focus events to keep the citation ↔ source cross-highlighting in sync. Everything is vanilla JS with a graceful fallback when smooth scrolling or IntersectionObserver is unavailable.