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
33 changes: 30 additions & 3 deletions web/components/audit-page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import Image from "next/image";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { CopyCommandButton } from "@/components/copy-command";
import { GitHubMark } from "@/components/icons";
Expand All @@ -7,6 +8,7 @@ import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { AUDITS, PROMPTS, auditCommand, auditTitle } from "@/lib/content";
import { auditDetail } from "@/lib/audit-details";
import { heroSrc } from "@/lib/heroes";
import { glossify } from "@/lib/glossary";
import { SITE_URL } from "@/lib/site";
import { type Lang, t } from "@/lib/i18n";
Expand All @@ -19,6 +21,7 @@ export function AuditDetailPage({ name, lang }: { name: string; lang: Lang }) {
if (!audit || !detail) return null;

const Icon = audit.icon;
const hero = heroSrc(name);
const home = lang === "de" ? "/de" : "/";
const langHref = lang === "de" ? `/audits/${name}` : `/de/audits/${name}`;

Expand Down Expand Up @@ -72,9 +75,33 @@ export function AuditDetailPage({ name, lang }: { name: string; lang: Lang }) {
</a>
<SiteHeader lang={lang} nav={[]} langHref={langHref} />
<main id="main">
<section className="relative overflow-hidden border-b border-border/60">
<div className="pointer-events-none absolute inset-0 bg-grid [mask-image:radial-gradient(ellipse_at_top,black,transparent_70%)]" />
<div className="relative mx-auto max-w-3xl px-5 py-16 md:py-20">
<section
className={cn(
"relative overflow-hidden border-b border-border/60",
hero && "md:min-h-[480px] md:flex md:items-center",
)}
>
{hero ? (
<>
<Image
src={hero}
alt=""
aria-hidden
fill
priority
sizes="100vw"
className="object-cover object-right"
/>
{/* Left-weighted scrim so the headline/CTA stay legible over the image. */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 bg-gradient-to-r from-background via-background/85 to-background/30 md:to-transparent"
/>
</>
) : (
<div className="pointer-events-none absolute inset-0 bg-grid [mask-image:radial-gradient(ellipse_at_top,black,transparent_70%)]" />
)}
<div className="relative mx-auto w-full max-w-3xl px-5 py-16 md:py-20">
<a
href={`${home}#audits`}
className="inline-flex items-center gap-1 rounded-md text-sm text-muted-foreground transition-colors hover:text-foreground"
Expand Down
30 changes: 30 additions & 0 deletions web/lib/heroes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { existsSync, readdirSync } from "node:fs";
import { join } from "node:path";

const PUBLIC_DIR = join(process.cwd(), "public");
const IMG = /\.(webp|avif|png|jpe?g)$/i;

/**
* Map of audit key -> public hero-image path, by matching `<key>.<img>` in
* `public/`. Read once at module load (build / server render). Imported only by
* the server-rendered audit page, so the `fs` read never reaches a client
* bundle. No match returns null and the audit page keeps its typographic grid
* hero, so dropping a new `public/<key>.webp` lights up a hero with no code change.
*/
const heroByKey: ReadonlyMap<string, string> = (() => {
const map = new Map<string, string>();
if (existsSync(PUBLIC_DIR)) {
for (const file of readdirSync(PUBLIC_DIR)) {
const ext = IMG.exec(file);
if (!ext) continue;
const key = file.slice(0, -ext[0].length);
if (!map.has(key)) map.set(key, `/${file}`);
}
}
return map;
})();

/** The public path to an audit's hero image, or null if none has been added. */
export function heroSrc(name: string): string | null {
return heroByKey.get(name) ?? null;
}
Binary file added web/public/accessibility.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/ai-llm.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/api.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/compliance-privacy.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/content.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/data.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/documentation.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/frontend.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/infrastructure.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/lean.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/performance.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/repo.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added web/public/security.webp
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading