Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,8 @@
"@modelcontextprotocol/sdk": "^1.26.0",
"@react-three/fiber": "^9.6.1",
"@vercel/analytics": "^2.0.1",
"cmdk": "^1.1.1",
"fuse.js": "^7.4.2",
"ioredis": "^5.11.1",
"js-yaml": "^4.1.1",
"lucide-react": "^1.11.0",
Expand Down
454 changes: 454 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

84 changes: 83 additions & 1 deletion src/app/globals.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -219,7 +219,13 @@ table {

/* Editorial section labels - sans-serif with wide tracking + uppercase
* for a Bloomberg-style tech-editorial feel, instead of the developer-tool
* vibe that monospace gives off when used on body / UI labels. */
* vibe that monospace gives off when used on body / UI labels.
*
* `.label-mono` remains for backward compat. The three sized variants below
* harmonise the three near-identical Tailwind class strings repeated ~133
* times across `src/**/*.tsx` (text-[10px]/[11px]/xs + 0.16em/0.18em + the
* matching ink ramp colour). Centralising them here means a copy-edit to
* the tracking or color ramp lands in one place instead of every call site. */
.label-mono {
font-family: var(--font-sans);
font-size: 10px;
Expand All@@ -228,6 +234,28 @@ table {
letter-spacing: 0.18em;
}

@layer components {
.label-mono-xs {
@apply text-[10px] uppercase tracking-[0.16em] text-ink-faint;
}
.label-mono-sm {
@apply text-[11px] uppercase tracking-[0.18em] text-ink-muted font-medium font-sans;
}
.label-mono-lg {
@apply text-xs uppercase tracking-[0.18em] text-ink-muted font-medium;
}

/* Page-level H1 hero treatment. Matches the dominant inline class
* string shared by index / answers / compare / alternatives /
* chains landing pages (display + 3xl-4xl ramp + ink + tight
* leading). Centralised so a copy-edit to the page-title scale
* lands in one place instead of every list landing route. */
.h1-hero {
font-family: var(--font-display);
@apply text-3xl sm:text-4xl text-ink leading-[1.05];
}
}

/* Chart pop animations */
@keyframes chart-pop-rise {
0% { opacity: 0; transform: translate(0, calc(-50% + 4px)) scale(0.92); }
Expand DownExpand Up@@ -302,6 +330,60 @@ textarea:focus-visible {
border-radius: 4px;
}

/* Search dialog open/close animations. tailwindcss-animate isn't
installed; these handcrafted keyframes give us the fade + slight
slide-down on open and the inverse on close. Linear-style: snappy,
no scale bounce. */
@keyframes ocb-search-overlay-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes ocb-search-overlay-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes ocb-search-card-in {
from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes ocb-search-card-out {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-4px); }
}
.ocb-search-overlay-in {
animation: ocb-search-overlay-in 150ms ease-out;
}
.ocb-search-overlay-out {
animation: ocb-search-overlay-out 100ms ease-in forwards;
}
.ocb-search-card-in {
animation: ocb-search-card-in 150ms cubic-bezier(0.16, 0.84, 0.32, 1);
}
.ocb-search-card-out {
animation: ocb-search-card-out 100ms ease-in forwards;
}
@media (prefers-reduced-motion: reduce) {
.ocb-search-overlay-in,
.ocb-search-overlay-out,
.ocb-search-card-in,
.ocb-search-card-out {
animation: none !important;
}
}

/* cmdk command palette input — opts out of the universal focus ring.
The dialog already provides visual focus context (modal backdrop +
centered card); a hard accent outline on the input itself reads as
a validation error rather than a focus state, which is the look the
designer flagged as "pas pro". The input still receives focus and
keyboard nav, just without the harsh outline. */
[cmdk-input]:focus,
[cmdk-input]:focus-visible {
outline: none !important;
outline-offset: 0;
box-shadow: none !important;
}

/* Skip-link target. Hidden until keyboard focus brings it onto the
page, then snaps to the top-left so a screen-reader / keyboard
user can bypass the nav. */
Expand Down
19 changes: 13 additions & 6 deletions src/app/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,8 @@ import { Analytics } from "@vercel/analytics/next";
import "./globals.css";
import { SiteHeader } from "@/components/site-header";
import { SiteFooter } from "@/components/site-footer";
import { SearchProvider } from "@/components/search/search-provider";
import { buildSearchIndex } from "@/lib/search/buildIndex";
import { SITE } from "@/data/site";
import { safeJsonLd } from "@/lib/jsonld";

Expand DownExpand Up@@ -54,7 +56,7 @@ const IS_STAGING =
!!process.env.VERCEL_ENV && process.env.VERCEL_ENV !== "production";

export const metadata: Metadata = {
metadataBase: new URL("https://openchainbench.com"),
metadataBase: new URL(SITE.url),
title: {
default: "OpenChainBench. Open benchmarks for crypto infrastructure",
template: "%s · OpenChainBench",
Expand All@@ -69,7 +71,7 @@ export const metadata: Metadata = {
description:
"Live benchmarks for crypto infrastructure: RPC latency, bridge fees, L2 finality and price feed accuracy.",
type: "website",
url: "https://openchainbench.com",
url: SITE.url,
siteName: "OpenChainBench",
},
twitter: {
Expand DownExpand Up@@ -140,9 +142,12 @@ const ORG_JSONLD = {
],
};

export default function RootLayout({
export default async function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
// Index is built once per server runtime (memoised via React `cache`),
// shipped as JSON to the client provider. ~400 docs, ~25-40 KB raw.
const searchItems = await buildSearchIndex();
return (
<html
lang="en"
Expand DownExpand Up@@ -187,9 +192,11 @@ export default function RootLayout({
<a href="#main-content" className="skip-link">
Skip to main content
</a>
<SiteHeader />
<main id="main-content" className="flex-1 w-full max-w-full overflow-x-clip min-w-0">{children}</main>
<SiteFooter />
<SearchProvider items={searchItems}>
<SiteHeader />
<main id="main-content" className="flex-1 w-full max-w-full overflow-x-clip min-w-0">{children}</main>
<SiteFooter />
</SearchProvider>
{process.env.VERCEL_ENV === "production" && <Analytics />}
</body>
</html>
Expand Down
Loading
Loading