Wiki — Search Results (faceted)
A faceted knowledge-base search results page for the fictional Aurora KB. A sticky search bar echoes the active query and live result count, a left facet rail filters by result type (Article, Doc, API, Tutorial), section, and last-updated window with per-option counts, and a sorted list of result cards shows highlighted title and snippet matches, a breadcrumb path, a colour-coded type badge, and a relative last-updated stamp. Checkboxes filter the list live, an active-filters chip row supports one-click removal and clear-all, sort toggles Relevance or Newest, and an empty state appears when nothing matches.
MCP
الكود
:root {
--bg: #ffffff;
--bg-2: #f7f8fa;
--panel: #ffffff;
--ink: #1a1a1f;
--ink-2: #3a3a42;
--muted: #6b7280;
--line: rgba(20, 20, 30, 0.10);
--line-2: rgba(20, 20, 30, 0.18);
--link: #2563eb;
--link-hover: #1d4ed8;
--accent: #2563eb;
--note: #2563eb;
--tip: #16a34a;
--warn: #d97706;
--danger: #dc2626;
--code-bg: #f4f4f6;
--kbd-bg: #eceef2;
--r-sm: 6px;
--r-md: 10px;
--r-lg: 14px;
--shadow-sm: 0 1px 2px rgba(20, 20, 30, 0.05);
--shadow-md: 0 6px 24px rgba(20, 20, 30, 0.08);
--ui: "Inter", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
--serif: "Source Serif 4", Georgia, serif;
--mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--topbar-h: 60px;
--rail-w: 268px;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--ui);
font-size: 15px;
line-height: 1.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a { color: var(--link); text-decoration: none; }
a:hover { color: var(--link-hover); }
:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--r-sm);
}
mark {
background: #fde68a;
color: inherit;
border-radius: 3px;
padding: 0 1px;
font-weight: 600;
}
kbd {
font-family: var(--mono);
font-size: 12px;
background: var(--kbd-bg);
border: 1px solid var(--line-2);
border-bottom-width: 2px;
border-radius: var(--r-sm);
padding: 1px 6px;
color: var(--ink-2);
line-height: 1.4;
}
.skip-link {
position: absolute;
left: -9999px;
top: 0;
background: var(--ink);
color: #fff;
padding: 10px 16px;
z-index: 200;
border-radius: 0 0 var(--r-md) 0;
}
.skip-link:focus { left: 0; color: #fff; }
/* ---------- Topbar ---------- */
.topbar {
position: sticky;
top: 0;
z-index: 60;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: saturate(180%) blur(8px);
border-bottom: 1px solid var(--line);
}
.topbar__inner {
height: var(--topbar-h);
display: flex;
align-items: center;
gap: 14px;
padding: 0 18px;
max-width: 1320px;
margin: 0 auto;
}
.iconbtn {
display: inline-grid;
place-items: center;
width: 38px;
height: 38px;
border: 1px solid var(--line);
background: var(--panel);
color: var(--ink-2);
border-radius: var(--r-md);
cursor: pointer;
transition: background .15s, border-color .15s;
}
.iconbtn:hover { background: var(--bg-2); border-color: var(--line-2); }
.drawer-toggle { display: none; }
.brand {
display: inline-flex;
align-items: center;
gap: 9px;
font-weight: 700;
color: var(--ink);
white-space: nowrap;
}
.brand:hover { color: var(--ink); }
.brand__mark {
display: grid;
place-items: center;
width: 30px;
height: 30px;
border-radius: 8px;
background: linear-gradient(135deg, var(--accent), #7c3aed);
color: #fff;
font-weight: 800;
font-size: 16px;
}
.brand__name { font-size: 16px; letter-spacing: -.01em; }
.brand__kb {
font-weight: 600;
color: var(--muted);
font-size: 12px;
border: 1px solid var(--line-2);
border-radius: 5px;
padding: 1px 5px;
margin-left: 2px;
}
.searchbar {
position: relative;
flex: 1;
max-width: 560px;
display: flex;
align-items: center;
}
.searchbar__icon {
position: absolute;
left: 12px;
color: var(--muted);
pointer-events: none;
}
.searchbar__input {
width: 100%;
font: inherit;
font-size: 15px;
color: var(--ink);
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: 999px;
padding: 9px 42px 9px 38px;
transition: border-color .15s, background .15s, box-shadow .15s;
}
.searchbar__input::placeholder { color: var(--muted); }
.searchbar__input:focus {
outline: none;
background: var(--panel);
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14);
}
.searchbar__kbd {
position: absolute;
right: 10px;
pointer-events: none;
}
.topbar__right { margin-left: auto; }
.ghostbtn {
display: inline-flex;
align-items: center;
font-weight: 600;
font-size: 14px;
color: var(--ink-2);
padding: 8px 12px;
border-radius: var(--r-md);
}
.ghostbtn:hover { background: var(--bg-2); color: var(--ink); }
/* ---------- Layout ---------- */
.layout {
display: grid;
grid-template-columns: var(--rail-w) minmax(0, 1fr);
gap: 0;
max-width: 1320px;
margin: 0 auto;
align-items: start;
}
/* ---------- Facet rail ---------- */
.facets {
position: sticky;
top: var(--topbar-h);
align-self: start;
max-height: calc(100vh - var(--topbar-h));
overflow-y: auto;
padding: 22px 20px 40px;
border-right: 1px solid var(--line);
}
.facets__head {
display: flex;
align-items: baseline;
justify-content: space-between;
margin-bottom: 18px;
}
.facets__title {
margin: 0;
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .07em;
color: var(--muted);
}
.linkbtn {
border: 0;
background: none;
font: inherit;
font-size: 13px;
font-weight: 600;
color: var(--link);
cursor: pointer;
padding: 2px;
}
.linkbtn:hover { color: var(--link-hover); text-decoration: underline; }
.facet { padding: 16px 0; border-top: 1px solid var(--line); }
.facet:first-of-type { border-top: 0; padding-top: 0; }
.facet__title {
margin: 0 0 10px;
font-size: 14px;
font-weight: 700;
color: var(--ink);
}
.facet__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; }
.check {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 8px;
border-radius: var(--r-sm);
cursor: pointer;
user-select: none;
transition: background .12s;
}
.check:hover { background: var(--bg-2); }
.check input {
position: absolute;
opacity: 0;
width: 1px;
height: 1px;
}
.check__box {
flex: none;
width: 17px;
height: 17px;
border: 1.5px solid var(--line-2);
border-radius: 5px;
background: var(--panel);
display: grid;
place-items: center;
transition: background .12s, border-color .12s;
}
.check__box::after {
content: "";
width: 9px;
height: 9px;
background: #fff;
clip-path: polygon(14% 47%, 0 61%, 41% 100%, 100% 17%, 86% 4%, 39% 73%);
transform: scale(0);
transition: transform .12s;
}
.check input:checked + .check__box {
background: var(--accent);
border-color: var(--accent);
}
.check input:checked + .check__box::after { transform: scale(1); }
.check__radio {
flex: none;
width: 17px;
height: 17px;
border: 1.5px solid var(--line-2);
border-radius: 50%;
background: var(--panel);
display: grid;
place-items: center;
transition: border-color .12s;
}
.check__radio::after {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--accent);
transform: scale(0);
transition: transform .12s;
}
.check input:checked + .check__radio {
border-color: var(--accent);
}
.check input:checked + .check__radio::after { transform: scale(1); }
.check input:focus-visible + .check__box,
.check input:focus-visible + .check__radio {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.check__label { flex: 1; color: var(--ink-2); font-size: 14px; }
.check input:checked ~ .check__label { color: var(--ink); font-weight: 600; }
.check__count {
font-family: var(--mono);
font-size: 12px;
color: var(--muted);
background: var(--bg-2);
border-radius: 999px;
padding: 1px 8px;
min-width: 26px;
text-align: center;
}
.check.is-empty { opacity: .45; }
.check.is-empty:hover { background: none; cursor: default; }
/* ---------- Content / results ---------- */
.content {
min-width: 0;
padding: 24px 28px 64px;
max-width: 880px;
}
.content:focus { outline: none; }
.results__head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 14px;
}
.results__h1 {
margin: 0 0 4px;
font-size: 21px;
font-weight: 800;
letter-spacing: -.02em;
}
.results__count { margin: 0; color: var(--muted); font-size: 14px; }
.results__count strong { color: var(--ink); font-variant-numeric: tabular-nums; }
.results__q { color: var(--ink-2); font-weight: 600; }
.results__sort { display: flex; align-items: center; gap: 8px; }
.results__sortlabel { font-size: 13px; font-weight: 600; color: var(--muted); }
.select { position: relative; display: inline-flex; align-items: center; }
.select select {
appearance: none;
font: inherit;
font-size: 14px;
font-weight: 600;
color: var(--ink);
background: var(--panel);
border: 1px solid var(--line-2);
border-radius: var(--r-md);
padding: 8px 34px 8px 12px;
cursor: pointer;
transition: border-color .15s, box-shadow .15s;
}
.select select:hover { border-color: var(--muted); }
.select select:focus-visible {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.14);
}
.select__chev {
position: absolute;
right: 10px;
color: var(--muted);
pointer-events: none;
}
/* ---------- Chips ---------- */
.chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin: 4px 0 18px;
min-height: 0;
}
.chips:empty { display: none; }
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
background: var(--bg-2);
border: 1px solid var(--line);
border-radius: 999px;
padding: 5px 6px 5px 12px;
animation: chipin .16s ease;
}
.chip__key { color: var(--muted); font-weight: 500; }
.chip__x {
display: grid;
place-items: center;
width: 18px;
height: 18px;
border: 0;
border-radius: 50%;
background: transparent;
color: var(--muted);
cursor: pointer;
font-size: 14px;
line-height: 1;
}
.chip__x:hover { background: var(--line-2); color: var(--ink); }
.chip--clear {
background: transparent;
border-color: var(--line-2);
color: var(--link);
padding: 5px 12px;
cursor: pointer;
font: inherit;
font-size: 13px;
font-weight: 600;
}
.chip--clear:hover { background: rgba(37, 99, 235, 0.08); }
@keyframes chipin { from { opacity: 0; transform: translateY(-3px); } }
/* ---------- Result list ---------- */
.resultlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
.card {
display: block;
padding: 18px 18px 17px;
border: 1px solid transparent;
border-radius: var(--r-lg);
transition: background .14s, border-color .14s, box-shadow .14s;
}
.card + .card { border-top: 1px solid var(--line); border-radius: 0; }
.card:hover {
background: var(--panel);
border-color: var(--line);
border-radius: var(--r-lg);
box-shadow: var(--shadow-sm);
}
.card:hover + .card { border-top-color: transparent; }
.card__crumb {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 5px;
font-size: 12.5px;
color: var(--muted);
margin-bottom: 6px;
}
.card__crumb a { color: var(--muted); font-weight: 500; }
.card__crumb a:hover { color: var(--link); }
.card__crumb .sep { opacity: .55; }
.card__titlerow { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.card__title {
margin: 0;
font-size: 17px;
font-weight: 700;
letter-spacing: -.01em;
}
.card__title a { color: var(--ink); }
.card__title a:hover { color: var(--link); text-decoration: underline; text-underline-offset: 2px; }
.badge {
flex: none;
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .045em;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid transparent;
line-height: 1.5;
}
.badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.badge--Article { color: #1d4ed8; background: #eff5ff; border-color: #dbe6ff; }
.badge--Doc { color: #047857; background: #ecfdf5; border-color: #d1fae5; }
.badge--API { color: #7c3aed; background: #f5f0ff; border-color: #e9defc; }
.badge--Tutorial { color: #b45309; background: #fff7ed; border-color: #fde6c8; }
.card__snippet {
margin: 7px 0 9px;
font-family: var(--serif);
font-size: 15px;
line-height: 1.65;
color: var(--ink-2);
}
.card__snippet b { color: var(--ink); font-weight: 600; }
.card__foot {
display: flex;
align-items: center;
gap: 14px;
font-size: 12.5px;
color: var(--muted);
flex-wrap: wrap;
}
.card__foot .dot { width: 3px; height: 3px; border-radius: 50%; background: var(--line-2); }
.card__path code {
font-family: var(--mono);
font-size: 12px;
background: var(--code-bg);
border: 1px solid var(--line);
border-radius: 5px;
padding: 1px 6px;
color: var(--ink-2);
}
/* ---------- Empty state ---------- */
.empty {
text-align: center;
padding: 56px 24px;
color: var(--muted);
}
.empty svg { color: var(--line-2); margin-bottom: 8px; }
.empty__title { margin: 0 0 6px; font-size: 18px; font-weight: 700; color: var(--ink); }
.empty__body { margin: 0 auto 18px; max-width: 380px; font-size: 14px; line-height: 1.6; }
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
font: inherit;
font-weight: 600;
font-size: 14px;
color: #fff;
background: var(--accent);
border: 0;
border-radius: var(--r-md);
padding: 9px 16px;
cursor: pointer;
transition: background .15s, transform .05s;
}
.btn:hover { background: var(--link-hover); }
.btn:active { transform: translateY(1px); }
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 16px);
background: var(--ink);
color: #fff;
font-size: 13.5px;
font-weight: 500;
padding: 10px 16px;
border-radius: var(--r-md);
box-shadow: var(--shadow-md);
opacity: 0;
pointer-events: none;
transition: opacity .2s, transform .2s;
z-index: 120;
}
.toast.is-on { opacity: 1; transform: translate(-50%, 0); }
/* ---------- Drawer scrim ---------- */
.drawer-scrim {
position: fixed;
inset: 0;
background: rgba(20, 20, 30, 0.4);
z-index: 70;
opacity: 0;
transition: opacity .2s;
}
.drawer-scrim.is-on { opacity: 1; }
/* ---------- Responsive ---------- */
@media (max-width: 820px) {
.drawer-toggle { display: inline-grid; }
.layout { grid-template-columns: minmax(0, 1fr); }
.facets {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: min(312px, 88vw);
max-height: none;
background: var(--panel);
z-index: 80;
border-right: 1px solid var(--line);
box-shadow: var(--shadow-md);
transform: translateX(-104%);
transition: transform .24s ease;
padding-top: 20px;
}
.facets.is-open { transform: translateX(0); }
.content { padding: 20px 18px 56px; }
.ghostbtn { display: none; }
}
@media (max-width: 520px) {
body { font-size: 14.5px; }
.topbar__inner { gap: 10px; padding: 0 12px; }
.brand__name { display: none; }
.searchbar__kbd { display: none; }
.results__head { align-items: flex-start; }
.results__sort { width: 100%; justify-content: space-between; }
.card { padding: 16px 4px; }
.card:hover { padding: 16px 12px; }
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}(function () {
"use strict";
// ---- Fictional knowledge-base corpus ----
const NOW = new Date("2026-06-08T00:00:00Z");
const DAY = 86400000;
const RESULTS = [
{
type: "Doc", section: "Aurora DB",
title: "Diagnosing and reducing replication lag",
crumb: ["Aurora DB", "Operations", "Replication"],
path: "/aurora-db/ops/replication-lag",
snippet: "Replication lag is the delay between a write committing on the primary and the same change becoming visible on a replica. Aurora DB exposes lag through the replica_lag_ms gauge; sustained lag above 250 ms usually points at long-running transactions, hot partitions, or a saturated apply thread.",
updated: 6,
},
{
type: "Article", section: "Aurora DB",
title: "How the Aurora DB replication pipeline works",
crumb: ["Aurora DB", "Internals"],
path: "/aurora-db/internals/replication-pipeline",
snippet: "Every commit on the primary is appended to a durable log, fanned out to replicas, and applied by a dedicated worker. Understanding where lag accumulates — the shipping queue, the network hop, or the apply stage — is the first step in tuning a replica fleet for read-after-write consistency.",
updated: 41,
},
{
type: "API", section: "Aurora DB",
title: "GET /v2/replicas/{id}/lag",
crumb: ["Aurora DB", "API reference", "Replicas"],
path: "/aurora-db/api/replicas-lag",
snippet: "Returns the current replication lag for a single replica in milliseconds, along with the last applied log sequence number. Poll this endpoint to drive autoscaling and to gate traffic away from replicas whose lag exceeds your freshness budget.",
updated: 19,
},
{
type: "Tutorial", section: "Aurora DB",
title: "Build a lag-aware read router in 20 minutes",
crumb: ["Aurora DB", "Tutorials"],
path: "/aurora-db/tutorials/lag-aware-router",
snippet: "In this tutorial you wire a small proxy that reads each replica's lag, routes fresh-read requests to the primary, and sends everything else to the least-lagged replica. By the end you will have a working router that keeps tail replication lag inside a 100 ms service-level objective.",
updated: 73,
},
{
type: "Doc", section: "Project Nimbus",
title: "Configuring write-ahead buffering for low lag",
crumb: ["Project Nimbus", "Storage", "Tuning"],
path: "/nimbus/storage/wal-buffering",
snippet: "Project Nimbus batches writes into a configurable buffer before flushing to the shared log. A larger buffer raises throughput but can increase replication lag during bursts. This page covers the buffer.flush_interval and buffer.max_bytes knobs and how to balance them.",
updated: 12,
},
{
type: "Article", section: "Project Nimbus",
title: "Capacity planning for the Nimbus shared log",
crumb: ["Project Nimbus", "Operations"],
path: "/nimbus/ops/capacity-planning",
snippet: "The shared log is the backbone of Project Nimbus. When ingest approaches the log's sustained bandwidth, apply threads fall behind and lag grows non-linearly. We walk through estimating headroom, reading the saturation dashboards, and scaling shards before they tip over.",
updated: 128,
},
{
type: "API", section: "Project Nimbus",
title: "POST /v1/streams/{name}/checkpoint",
crumb: ["Project Nimbus", "API reference", "Streams"],
path: "/nimbus/api/stream-checkpoint",
snippet: "Commits a consumer checkpoint so that a restarted reader resumes without replaying the full stream. Frequent checkpoints reduce recovery time but add write pressure that can contribute to replication lag on busy partitions.",
updated: 205,
},
{
type: "Tutorial", section: "Verdant Mesh",
title: "Federating two regions across the Verdant Mesh",
crumb: ["Verdant Mesh", "Tutorials", "Multi-region"],
path: "/verdant-mesh/tutorials/federate-regions",
snippet: "The Verdant Mesh links regional clusters over an encrypted overlay. Cross-region replication inevitably carries lag bounded by the speed of light, so this tutorial shows how to mark a region as eventually consistent and surface staleness to the application layer.",
updated: 34,
},
{
type: "Article", section: "Verdant Mesh",
title: "Consistency models in the Verdant Empire",
crumb: ["Verdant Mesh", "Concepts"],
path: "/verdant-mesh/concepts/consistency-models",
snippet: "From strict serializability to bounded staleness, the Verdant Empire's services choose a consistency model per workload. This reference explains how each model trades latency for freshness and where replication lag becomes observable to end users.",
updated: 96,
},
{
type: "Doc", section: "Verdant Mesh",
title: "Health checks and lag-based failover",
crumb: ["Verdant Mesh", "Operations", "Reliability"],
path: "/verdant-mesh/ops/lag-failover",
snippet: "Verdant Mesh promotes a standby when the active node's replication lag crosses a threshold for a sustained window. Tune the failover.lag_ceiling and the dwell timer so transient spikes don't trigger needless promotions.",
updated: 3,
},
{
type: "Doc", section: "Platform Ops",
title: "Alerting on replication lag with the Ops console",
crumb: ["Platform Ops", "Observability", "Alerts"],
path: "/platform-ops/observability/lag-alerts",
snippet: "Define a multi-window alert that fires when replication lag stays above your objective. The Ops console ships a starter rule that pages on a fast burn and opens a ticket on a slow burn, so you catch both incidents and creeping regressions.",
updated: 22,
},
{
type: "Article", section: "Platform Ops",
title: "A field guide to lag-related incidents",
crumb: ["Platform Ops", "Runbooks"],
path: "/platform-ops/runbooks/lag-incidents",
snippet: "Collected postmortems from the platform team: a runaway migration that starved the apply thread, a noisy neighbor that saturated the network, and a clock skew that made replication lag look far worse than it was. Each entry ends with the durable fix.",
updated: 58,
},
{
type: "API", section: "Platform Ops",
title: "GET /metrics/replication_lag_ms",
crumb: ["Platform Ops", "API reference", "Metrics"],
path: "/platform-ops/api/metrics-lag",
snippet: "Scrapes the replication_lag_ms gauge for every node in a fleet. Pair this with a histogram recording rule to track p99 replication lag over time rather than chasing instantaneous spikes.",
updated: 9,
},
{
type: "Tutorial", section: "Platform Ops",
title: "Load-testing a cluster until lag appears",
crumb: ["Platform Ops", "Tutorials"],
path: "/platform-ops/tutorials/lag-load-test",
snippet: "Deliberately push a staging cluster past its apply capacity to learn its lag curve before production does. You will script a ramping write load, watch replication lag inflect, and record the throughput where freshness guarantees start to break.",
updated: 150,
},
];
const QUERY = "replication lag";
// ---- State ----
const state = {
facets: { type: new Set(), section: new Set() },
date: "any",
sort: "relevance",
query: QUERY,
};
// ---- DOM refs ----
const $ = (s, r) => (r || document).querySelector(s);
const $$ = (s, r) => Array.from((r || document).querySelectorAll(s));
const listEl = $("#resultList");
const emptyEl = $("#emptyState");
const chipsEl = $("#chips");
const countEl = $("#resultCount");
const queryEcho = $("#queryEcho");
const toastEl = $("#toast");
// ---- Helpers ----
const FACET_LABELS = {
type: "Type",
section: "Section",
date: "Updated",
};
const DATE_LABELS = { "30": "Past 30 days", "180": "Past 6 months", "365": "Past year" };
function escapeHtml(s) {
return s.replace(/[&<>"']/g, (c) =>
({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])
);
}
function highlight(text) {
const terms = state.query.trim().split(/\s+/).filter(Boolean);
let html = escapeHtml(text);
if (!terms.length) return html;
const pattern = new RegExp(
"(" + terms.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|") + ")",
"gi"
);
return html.replace(pattern, "<mark>$1</mark>");
}
function relativeDate(daysAgo) {
if (daysAgo === 0) return "today";
if (daysAgo === 1) return "yesterday";
if (daysAgo < 30) return daysAgo + " days ago";
if (daysAgo < 60) return "a month ago";
if (daysAgo < 365) return Math.round(daysAgo / 30) + " months ago";
return Math.round(daysAgo / 365) + " year" + (daysAgo >= 730 ? "s" : "") + " ago";
}
function relevanceScore(item) {
const hay = (item.title + " " + item.snippet).toLowerCase();
const terms = state.query.toLowerCase().split(/\s+/).filter(Boolean);
let score = 0;
terms.forEach((t) => {
if (item.title.toLowerCase().includes(t)) score += 5;
const m = hay.match(new RegExp(t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"));
score += m ? m.length : 0;
});
return score;
}
// ---- Filtering ----
function passesType(item) {
return state.facets.type.size === 0 || state.facets.type.has(item.type);
}
function passesSection(item) {
return state.facets.section.size === 0 || state.facets.section.has(item.section);
}
function passesDate(item) {
if (state.date === "any") return true;
return item.updated <= Number(state.date);
}
function filtered() {
return RESULTS.filter((r) => passesType(r) && passesSection(r) && passesDate(r));
}
// Counts respect *other* facet groups so they stay meaningful.
function facetCount(group, value) {
return RESULTS.filter((item) => {
if (group === "type") return item.type === value && passesSection(item) && passesDate(item);
if (group === "section") return item.section === value && passesType(item) && passesDate(item);
if (group === "date")
return item.updated <= Number(value) && passesType(item) && passesSection(item);
return false;
}).length;
}
// ---- Render ----
function renderCounts() {
$$("[data-count]").forEach((el) => {
const [group, value] = el.getAttribute("data-count").split(/:(.+)/);
const n = facetCount(group, value);
el.textContent = n;
const label = el.closest(".check");
if (label && group !== "date") label.classList.toggle("is-empty", n === 0 && !isChecked(group, value));
});
}
function isChecked(group, value) {
if (group === "date") return state.date === value;
return state.facets[group] && state.facets[group].has(value);
}
function renderChips() {
const chips = [];
["type", "section"].forEach((group) => {
state.facets[group].forEach((value) => {
chips.push({ group, value, label: value });
});
});
if (state.date !== "any") {
chips.push({ group: "date", value: state.date, label: DATE_LABELS[state.date] });
}
if (!chips.length) {
chipsEl.innerHTML = "";
return;
}
chipsEl.innerHTML =
chips
.map(
(c) =>
`<span class="chip"><span class="chip__key">${FACET_LABELS[c.group]}:</span>${escapeHtml(
c.label
)}<button class="chip__x" type="button" aria-label="Remove ${escapeHtml(
c.label
)} filter" data-group="${c.group}" data-value="${escapeHtml(c.value)}">×</button></span>`
)
.join("") +
`<button class="chip--clear" type="button" id="clearAll">Clear all</button>`;
}
function renderList() {
let items = filtered();
if (state.sort === "newest") {
items.sort((a, b) => a.updated - b.updated);
} else {
items.sort((a, b) => relevanceScore(b) - relevanceScore(a) || a.updated - b.updated);
}
countEl.textContent = items.length;
queryEcho.textContent = "“" + state.query + "”";
if (!items.length) {
listEl.innerHTML = "";
emptyEl.hidden = false;
return;
}
emptyEl.hidden = true;
listEl.innerHTML = items
.map((item) => {
const crumb = item.crumb
.map((c) => `<a href="#">${escapeHtml(c)}</a>`)
.join('<span class="sep" aria-hidden="true">›</span>');
return `<li class="card">
<nav class="card__crumb" aria-label="Breadcrumb">${crumb}</nav>
<div class="card__titlerow">
<h2 class="card__title"><a href="#">${highlight(item.title)}</a></h2>
<span class="badge badge--${item.type}">${
item.type === "API" ? "API" : item.type
}</span>
</div>
<p class="card__snippet">${highlight(item.snippet)}</p>
<div class="card__foot">
<span class="card__path"><code>${escapeHtml(item.path)}</code></span>
<span class="dot" aria-hidden="true"></span>
<span>Updated ${relativeDate(item.updated)}</span>
</div>
</li>`;
})
.join("");
}
function renderAll() {
renderCounts();
renderChips();
renderList();
}
// ---- Toast ----
let toastTimer;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("is-on");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => toastEl.classList.remove("is-on"), 2200);
}
// ---- Events ----
$$('[data-facet="type"], [data-facet="section"]').forEach((input) => {
input.addEventListener("change", () => {
const group = input.getAttribute("data-facet");
if (input.checked) state.facets[group].add(input.value);
else state.facets[group].delete(input.value);
renderAll();
});
});
$$('[data-facet="date"]').forEach((input) => {
input.addEventListener("change", () => {
state.date = input.value;
renderAll();
});
});
$("#sortSelect").addEventListener("change", (e) => {
state.sort = e.target.value;
renderList();
toast("Sorted by " + (state.sort === "newest" ? "newest" : "relevance"));
});
// Chip removal + clear-all (delegated)
chipsEl.addEventListener("click", (e) => {
const x = e.target.closest(".chip__x");
if (x) {
const group = x.getAttribute("data-group");
const value = x.getAttribute("data-value");
removeFilter(group, value);
return;
}
if (e.target.id === "clearAll") {
clearAllFilters();
toast("All filters cleared");
}
});
function removeFilter(group, value) {
if (group === "date") {
state.date = "any";
const anyRadio = document.querySelector('[data-facet="date"][value="any"]');
if (anyRadio) anyRadio.checked = true;
} else {
state.facets[group].delete(value);
const input = document.querySelector(
`[data-facet="${group}"][value="${cssEscape(value)}"]`
);
if (input) input.checked = false;
}
renderAll();
}
function clearAllFilters() {
state.facets.type.clear();
state.facets.section.clear();
state.date = "any";
$$('[data-facet="type"], [data-facet="section"]').forEach((i) => (i.checked = false));
const anyRadio = document.querySelector('[data-facet="date"][value="any"]');
if (anyRadio) anyRadio.checked = true;
renderAll();
}
function cssEscape(v) {
return (window.CSS && CSS.escape) ? CSS.escape(v) : v.replace(/["\\]/g, "\\$&");
}
$("#resetFacets").addEventListener("click", () => {
clearAllFilters();
toast("Filters reset");
});
$("#emptyClear").addEventListener("click", () => {
clearAllFilters();
toast("All filters cleared");
});
// Search form: update query + re-highlight
$("#searchForm").addEventListener("submit", (e) => {
e.preventDefault();
const v = $("#searchInput").value.trim();
state.query = v || "";
renderAll();
$("#searchInput").blur();
toast(v ? "Searching “" + v + "”" : "Showing all pages");
});
// Keyboard: "/" focuses search
document.addEventListener("keydown", (e) => {
if (e.key === "/" && document.activeElement !== $("#searchInput")) {
const tag = (document.activeElement.tagName || "").toLowerCase();
if (tag !== "input" && tag !== "textarea" && tag !== "select") {
e.preventDefault();
$("#searchInput").focus();
$("#searchInput").select();
}
}
});
// ---- Mobile drawer ----
const facetsEl = $("#facets");
const scrim = $("#drawerScrim");
const toggle = $("#drawerToggle");
function openDrawer() {
facetsEl.classList.add("is-open");
scrim.hidden = false;
requestAnimationFrame(() => scrim.classList.add("is-on"));
toggle.setAttribute("aria-expanded", "true");
}
function closeDrawer() {
facetsEl.classList.remove("is-open");
scrim.classList.remove("is-on");
setTimeout(() => (scrim.hidden = true), 220);
toggle.setAttribute("aria-expanded", "false");
}
toggle.addEventListener("click", () =>
facetsEl.classList.contains("is-open") ? closeDrawer() : openDrawer()
);
scrim.addEventListener("click", closeDrawer);
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && facetsEl.classList.contains("is-open")) closeDrawer();
});
// ---- Init ----
renderAll();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Search results · Aurora Knowledge Base</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;800&family=Source+Serif+4:opsz,[email protected],400;8..60,500;8..60,600&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<a class="skip-link" href="#results">Skip to results</a>
<header class="topbar">
<div class="topbar__inner">
<button class="iconbtn drawer-toggle" id="drawerToggle" aria-label="Toggle filters" aria-expanded="false" aria-controls="facets">
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path d="M4 6h16M4 12h16M4 18h16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<a class="brand" href="#" aria-label="Aurora Knowledge Base home">
<span class="brand__mark" aria-hidden="true">A</span>
<span class="brand__name">Aurora <span class="brand__kb">KB</span></span>
</a>
<form class="searchbar" id="searchForm" role="search" autocomplete="off">
<svg class="searchbar__icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="2"/><path d="m20 20-3.2-3.2" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
<input class="searchbar__input" id="searchInput" type="search" name="q" value="replication lag" aria-label="Search the knowledge base" placeholder="Search articles, docs, API…" />
<kbd class="searchbar__kbd">/</kbd>
</form>
<div class="topbar__right">
<a class="ghostbtn" href="#" aria-label="Documentation home">Docs</a>
</div>
</div>
</header>
<div class="layout">
<!-- Facet rail -->
<aside class="facets" id="facets" aria-label="Search filters">
<div class="facets__head">
<h2 class="facets__title">Filters</h2>
<button class="linkbtn" id="resetFacets" type="button">Reset</button>
</div>
<section class="facet" data-group="type" aria-labelledby="facet-type">
<h3 class="facet__title" id="facet-type">Result type</h3>
<ul class="facet__list">
<li><label class="check"><input type="checkbox" data-facet="type" value="Article"><span class="check__box" aria-hidden="true"></span><span class="check__label">Article</span><span class="check__count" data-count="type:Article">0</span></label></li>
<li><label class="check"><input type="checkbox" data-facet="type" value="Doc"><span class="check__box" aria-hidden="true"></span><span class="check__label">Doc</span><span class="check__count" data-count="type:Doc">0</span></label></li>
<li><label class="check"><input type="checkbox" data-facet="type" value="API"><span class="check__box" aria-hidden="true"></span><span class="check__label">API reference</span><span class="check__count" data-count="type:API">0</span></label></li>
<li><label class="check"><input type="checkbox" data-facet="type" value="Tutorial"><span class="check__box" aria-hidden="true"></span><span class="check__label">Tutorial</span><span class="check__count" data-count="type:Tutorial">0</span></label></li>
</ul>
</section>
<section class="facet" data-group="section" aria-labelledby="facet-section">
<h3 class="facet__title" id="facet-section">Section</h3>
<ul class="facet__list">
<li><label class="check"><input type="checkbox" data-facet="section" value="Aurora DB"><span class="check__box" aria-hidden="true"></span><span class="check__label">Aurora DB</span><span class="check__count" data-count="section:Aurora DB">0</span></label></li>
<li><label class="check"><input type="checkbox" data-facet="section" value="Project Nimbus"><span class="check__box" aria-hidden="true"></span><span class="check__label">Project Nimbus</span><span class="check__count" data-count="section:Project Nimbus">0</span></label></li>
<li><label class="check"><input type="checkbox" data-facet="section" value="Verdant Mesh"><span class="check__box" aria-hidden="true"></span><span class="check__label">Verdant Mesh</span><span class="check__count" data-count="section:Verdant Mesh">0</span></label></li>
<li><label class="check"><input type="checkbox" data-facet="section" value="Platform Ops"><span class="check__box" aria-hidden="true"></span><span class="check__label">Platform Ops</span><span class="check__count" data-count="section:Platform Ops">0</span></label></li>
</ul>
</section>
<section class="facet" data-group="date" aria-labelledby="facet-date">
<h3 class="facet__title" id="facet-date">Last updated</h3>
<ul class="facet__list facet__list--radio" role="radiogroup" aria-label="Last updated">
<li><label class="check check--radio"><input type="radio" name="date" data-facet="date" value="any" checked><span class="check__radio" aria-hidden="true"></span><span class="check__label">Any time</span></label></li>
<li><label class="check check--radio"><input type="radio" name="date" data-facet="date" value="30"><span class="check__radio" aria-hidden="true"></span><span class="check__label">Past 30 days</span><span class="check__count" data-count="date:30">0</span></label></li>
<li><label class="check check--radio"><input type="radio" name="date" data-facet="date" value="180"><span class="check__radio" aria-hidden="true"></span><span class="check__label">Past 6 months</span><span class="check__count" data-count="date:180">0</span></label></li>
<li><label class="check check--radio"><input type="radio" name="date" data-facet="date" value="365"><span class="check__radio" aria-hidden="true"></span><span class="check__label">Past year</span><span class="check__count" data-count="date:365">0</span></label></li>
</ul>
</section>
</aside>
<div class="drawer-scrim" id="drawerScrim" hidden></div>
<!-- Results -->
<main class="content" id="results" tabindex="-1">
<div class="results__head">
<div class="results__meta">
<h1 class="results__h1">Search results</h1>
<p class="results__count" id="resultSummary">
<strong id="resultCount">0</strong> results for <span class="results__q" id="queryEcho">“replication lag”</span>
</p>
</div>
<div class="results__sort">
<label for="sortSelect" class="results__sortlabel">Sort</label>
<div class="select">
<select id="sortSelect" aria-label="Sort results">
<option value="relevance">Relevance</option>
<option value="newest">Newest</option>
</select>
<svg class="select__chev" viewBox="0 0 24 24" width="16" height="16" aria-hidden="true"><path d="m6 9 6 6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
</div>
</div>
<div class="chips" id="chips" aria-live="polite"></div>
<ol class="resultlist" id="resultList"></ol>
<div class="empty" id="emptyState" hidden>
<svg viewBox="0 0 24 24" width="40" height="40" aria-hidden="true"><circle cx="11" cy="11" r="7" stroke="currentColor" stroke-width="1.6"/><path d="m20 20-3.2-3.2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M8.5 11h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
<h2 class="empty__title">No results match your filters</h2>
<p class="empty__body">Try removing a filter or broadening your search. There may still be matching pages under a different section or type.</p>
<button class="btn" id="emptyClear" type="button">Clear all filters</button>
</div>
</main>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Search Results (faceted)
A clean, dense search results page for the fictional Aurora Knowledge Base, built in the classic docs tradition: a sticky top search bar carrying the live query and result count, a persistent left facet rail, and a centered column of result cards. The query replication lag is matched across fourteen encyclopedic entries spanning Aurora DB, Project Nimbus, the Verdant Mesh, and Platform Ops — each tagged as an Article, Doc, API reference, or Tutorial. Every card pairs a breadcrumb path, a colour-coded type badge, a serif snippet with the search terms bolded, and a relative last-updated stamp.
The facet rail filters the list live. Toggling the type or section checkboxes narrows the results and updates every other group’s counts so the numbers always reflect what’s still reachable, while the Last updated radio group scopes results to a recency window. Each active filter is mirrored as a removable chip above the list, with a Clear all action; a Sort dropdown reorders by relevance score or newest, and removing every match swaps in a dedicated empty state with a one-tap reset. Press / to jump to the search box, and submit the form to re-run highlighting for a new query.
The layout is fully responsive: on narrow viewports the facet rail collapses into a slide-in drawer behind a toggle button with a scrim, the brand label and keyboard hint fold away, and cards reflow to a single dense column down to 360px. All controls are keyboard-operable with visible focus rings, the result count is announced via an aria-live region, and a small toast confirms sorting and filter changes.
Illustrative UI only — fictional articles, products, and data.