first publish

This commit is contained in:
knso committed 2026-09-06 17:21:52 +02:00
commit b8e41b4d55
147 files changed
+19367

No files matched your search

+102
View File
@@ -0,0 +1,102 @@
import { cn } from "@/lib/utils";
import { flags } from "@/lib/site";
import { isPlaceholder, splitPlaceholders } from "@/lib/legal";
import type { FactItem } from "@/content/home";
export type { FactItem };
export interface FactsProps {
items: readonly FactItem[];
/**
* `stack` = mono lines with gold ticks (hero, top right).
* `table` = two-column label / value table on bone with a hairline frame (Rahmen).
*/
variant?: "stack" | "table";
/** Table heading (DM Mono caps), e.g. „Rahmen“. */
title?: string;
className?: string;
}
/**
* Renders „[…]“ segments in the accent colour (`--gold-deep` on light, `--gold` on dark).
*
* Deliberately flag-agnostic: cutting a placeholder out of a running sentence leaves
* fragments („Auswertungsgespräch .“), so the launch switch is a build guard, not a
* strip – `assertNoPlaceholders` in every content module aborts the build while a
* placeholder is still open (src/lib/legal.ts, list in CONTENT_TODO).
*/
export function PlaceholderText({ text }: { text: string }) {
return (
<>
{splitPlaceholders(text).map((seg, i) =>
seg.placeholder ? (
<span key={i} className="text-accent">
{seg.text}
</span>
) : (
<span key={i}>{seg.text}</span>
),
)}
</>
);
}
/** Drops whole placeholder ROWS when `flags.showPlaceholders` is off (inline placeholders
inside a value are the build guard's job, not this filter's – see PlaceholderText). */
export function visibleFacts(items: readonly FactItem[]): FactItem[] {
return items.filter((f) => flags.showPlaceholders || !(f.placeholder || isPlaceholder(f.value)));
}
/**
* Facts stack (Format · Sprache · Ort · Dauer): DM Mono 12 px, one gold tick
* per line, label muted, value in the foreground colour. Both variants are
* ledgers – tick / label / value are columns with one left edge each, not an
* inline run that starts the value at a different x on every row.
*/
export function Facts({ items, variant = "stack", title, className }: FactsProps) {
const rows = visibleFacts(items);
if (!rows.length) return null;
if (variant === "table") {
return (
<div className={cn("border border-line bg-surface p-6 sm:p-10", className)}>
{title && <p className="mb-6 font-mono text-xs uppercase tracking-[0.12em] text-accent">{title}</p>}
<dl className="font-mono text-xs tracking-[0.06em] tabular-nums">
{rows.map((f) => (
/* Below sm the value column would be ~150 px inside the padded box and long
values („Inhouse-Workshop 1–2 Tage / Trainingsreihe / Online“) break over four
lines – so the label sits above the value there, identically on every page. */
<div key={f.label} className="grid grid-cols-1 gap-x-6 gap-y-1 border-b border-line py-3 last:border-b-0 sm:grid-cols-[1fr_3fr]">
<dt className="text-fg-muted">{f.label}</dt>
<dd className="text-fg">
<PlaceholderText text={f.value} />
</dd>
</div>
))}
</dl>
</div>
);
}
return (
/* Three rails: tick | label (max-content, so every label ends on one edge) | value. */
<dl
className={cn(
"grid grid-cols-[1.5rem_max-content_1fr] gap-x-3 gap-y-3 font-mono text-xs tracking-[0.06em] tabular-nums",
className,
)}
>
{rows.map((f) => (
<div key={f.label} className="contents">
<span aria-hidden="true" className="mt-[0.55em] h-px w-6 self-start bg-accent" />
<dt className="text-fg-muted">{f.label}</dt>
<dd className="text-fg">
<PlaceholderText text={f.value} />
</dd>
</div>
))}
</dl>
);
}
export default Facts;