spb/satelliteindex
Public
TypeScript 66.5%
Python 30.9%
JavaScript 1.4%
CSS 0.7%
1import Link from 'next/link';2import type { ReactNode } from 'react';3import { cn } from '@/lib/cn';45/** Long-form typography primitives for the meta pages (methodology, about, privacy, terms, developers). Server-safe. */67export function Prose({ children, className }: { children: ReactNode; className?: string }) {8 return <div className={cn('max-w-3xl text-[15px] leading-relaxed text-ink-2 [&_p+p]:mt-4 [&_strong]:font-semibold [&_strong]:text-ink [&_ul]:mt-3 [&_ul]:space-y-1.5 [&_ul]:pl-5 [&_ul]:list-disc [&_ol]:mt-3 [&_ol]:space-y-1.5 [&_ol]:pl-5 [&_ol]:list-decimal [&_code]:mono [&_code]:rounded [&_code]:bg-plane-2 [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-[13px] [&_code]:text-ink', className)}>{children}</div>;9}1011/** Anchored section with a hairline on top; `id` is used by the TOC. */12export function DocSection({ id, eyebrow, title, children, className }: { id: string; eyebrow?: string; title: ReactNode; children: ReactNode; className?: string }) {13 return (14 <section id={id} className={cn('scroll-mt-[calc(var(--header-h)+1rem)] border-t border-rule py-8 md:py-10', className)}>15 {eyebrow && <p className="eyebrow">{eyebrow}</p>}16 <h2 className="mt-1 text-xl font-semibold tracking-tight md:text-2xl">17 <a href={`#${id}`} className="hover:text-accent">18 {title}19 </a>20 </h2>21 <div className="mt-4">{children}</div>22 </section>23 );24}2526export function SubHeading({ children, id }: { children: ReactNode; id?: string }) {27 return (28 <h3 id={id} className="mt-6 scroll-mt-[calc(var(--header-h)+1rem)] text-base font-semibold text-ink">29 {children}30 </h3>31 );32}3334/** Sticky mini table of contents (desktop); a compact inline list on mobile. DOM order = visual order (TOC first on all breakpoints). */35export function Toc({ items, className }: { items: { id: string; label: string }[]; className?: string }) {36 return (37 <nav aria-label="Contents" className={cn('min-w-0 max-w-full lg:sticky lg:top-[calc(var(--header-h)+1.5rem)] lg:self-start', className)}>38 <p className="eyebrow mb-2">Contents</p>39 <ol className="no-scrollbar flex max-w-full gap-1 overflow-x-auto lg:block lg:space-y-0.5 lg:overflow-visible">40 {items.map((it, i) => (41 <li key={it.id} className="shrink-0">42 <a href={`#${it.id}`} className="inline-flex min-h-9 items-center gap-2 rounded-md border border-rule px-2.5 text-xs text-ink-2 hover:bg-plane-2 hover:text-ink lg:min-h-8 lg:border-0 lg:px-0 lg:text-[13px]">43 <span className="mono text-[10px] text-ink-3">{String(i + 1).padStart(2, '0')}</span>44 {it.label}45 </a>46 </li>47 ))}48 </ol>49 </nav>50 );51}5253/** Two-column doc layout: TOC (left, sticky on desktop) + content. */54export function DocLayout({ toc, children }: { toc: { id: string; label: string }[]; children: ReactNode }) {55 return (56 <div className="grid gap-8 lg:grid-cols-[200px_minmax(0,1fr)] lg:gap-14">57 <Toc items={toc} />58 <div className="min-w-0">{children}</div>59 </div>60 );61}6263/** Code block in mono on the secondary plane; horizontal scroll instead of overflow. */64export function Code({ children, className, label }: { children: string; className?: string; label?: string }) {65 return (66 <figure className={cn('mt-3 min-w-0 overflow-hidden rounded-md border border-rule bg-plane-2', className)}>67 {label && <figcaption className="eyebrow border-b border-rule px-3 py-1.5">{label}</figcaption>}68 <pre className="scrollbar-thin overflow-x-auto p-3 text-[12.5px] leading-relaxed text-ink">69 <code className="mono">{children}</code>70 </pre>71 </figure>72 );73}7475/** Definition list rendered as hairline rows (label / value). */76export function KeyValues({ rows, className }: { rows: { k: ReactNode; v: ReactNode }[]; className?: string }) {77 return (78 <dl className={cn('divide-y divide-rule border-y border-rule', className)}>79 {rows.map((r, i) => (80 <div key={i} className="grid gap-1 py-2.5 sm:grid-cols-[200px_minmax(0,1fr)] sm:gap-4">81 <dt className="text-xs uppercase tracking-wider text-ink-3 sm:pt-0.5">{r.k}</dt>82 <dd className="min-w-0 break-words text-sm text-ink">{r.v}</dd>83 </div>84 ))}85 </dl>86 );87}8889export function Callout({ children, tone = 'info', className }: { children: ReactNode; tone?: 'info' | 'warn'; className?: string }) {90 return (91 <div className={cn('mt-4 rounded-md border-l-2 px-4 py-3 text-sm leading-relaxed', tone === 'warn' ? 'border-warn bg-warn-soft text-ink' : 'border-accent bg-accent-soft text-ink', className)}>{children}</div>92 );93}9495export function MethodologyLink({ anchor, children }: { anchor?: string; children?: ReactNode }) {96 return (97 <Link href={anchor ? `/methodology#${anchor}` : '/methodology'} className="link text-sm">98 {children ?? 'Methodology'}99 </Link>100 );101}102