/** * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ import type { ReactNode } from "react"; import { authors as REGISTRY } from "./devnotes/authors-data"; /** * Site basepath. Mirrors `instances[0].custom-domain` path in fern/docs.yml. * Custom MDX components bypass Fern's link rewriter, so the card's `href` * needs the prefix manually to avoid 404s under basepath-aware routing. * * Image paths are NOT prefixed — they should be passed in as ES-module imports * from MDX (e.g. `import hero from "@/assets/foo/hero.png"`), which the * bundler resolves to the correct URL in both dev and production. */ const BASEPATH = "/nemo/datadesigner"; /** Prepend BASEPATH to a root-relative path if not already present. */ function withBasepath(path: string): string { if (!path.startsWith("/")) return path; if (path.startsWith(BASEPATH + "/") || path === BASEPATH) return path; return BASEPATH + path; } /** * BlogCard — index card for a dev note / blog post. * * Renders a clickable tile with: optional hero image, ALL-CAPS date eyebrow, * title, description, and an author byline (avatar stack + first author + "+N"). * * Designed for the dev-notes landing index — Fern's built-in only does * icon + title + description, which made every card visually identical. * * Usage in MDX (inside ): * * import { BlogCard, BlogGrid } from "@/components/BlogCard"; * * * * */ export interface BlogCardProps { href: string; title: string; description: string; date: string; authors?: string[]; /** * Optional hero image element. Pass an `` JSX node from MDX so Fern's * MDX rewriter resolves the src to the correct dev/prod path (raw string * paths bypass the rewriter and 404 in dev). Falls back to a deterministic * hash-based gradient + monogram when omitted. * * } … /> */ image?: ReactNode; } /** Deterministic hash → number ∈ [0, 360). Same input → same color. */ function hashHue(input: string): number { let h = 5381; for (let i = 0; i < input.length; i++) { h = ((h << 5) + h + input.charCodeAt(i)) | 0; } return Math.abs(h) % 360; } /** Build a 2-stop diagonal gradient that reads well in both light/dark. * Hue is constrained to a band that pairs with NVIDIA green (avoid muddy * yellows by skipping 40-90°). */ function placeholderGradient(seed: string): string { let hue = hashHue(seed); if (hue >= 40 && hue < 90) hue = (hue + 60) % 360; const a = `hsl(${hue} 55% 38%)`; const b = `hsl(${(hue + 35) % 360} 60% 22%)`; return `linear-gradient(135deg, ${a} 0%, ${b} 100%)`; } /** First grapheme of the title (works for "🎨 Title" too). */ function monogramOf(title: string): string { // Strip leading non-letter punctuation/whitespace then take 1 char. const trimmed = title.replace(/^[^\p{L}\p{N}]+/u, ""); return Array.from(trimmed)[0]?.toUpperCase() ?? "·"; } export function BlogCard({ href, title, description, date, authors = [], image, }: BlogCardProps) { const validAuthors = authors.map((id) => REGISTRY[id]).filter(Boolean); const primary = validAuthors[0]; const extra = validAuthors.length - 1; return (
{image ? ( image ) : ( )}
{date}

{title}

{description}

{primary && (
{validAuthors.slice(0, 3).map((a, i) => ( ))}
{primary.name} {extra > 0 ? ` +${extra}` : ""}
)}
); } export function BlogGrid({ children }: { children: ReactNode }) { return
{children}
; }