"use client";
import type { CSSProperties } from "react";
import { ArrowRight, CheckCircle2, ShieldCheck, Sparkles } from "lucide-react";
import { cn } from "@/lib/utils";
import type {
DetailUiDslNode,
DetailUiDslRegion,
DetailUiDslV2Document,
} from "@/lib/detail-ui-dsl-v2";
function normalizedRegionToPixels(params: {
region: DetailUiDslRegion;
width: number;
height: number;
}) {
return {
x: params.region.x * params.width,
y: params.region.y * params.height,
width: params.region.width * params.width,
height: params.region.height * params.height,
};
}
function iconFor(name?: string, size = 16) {
const style = { width: size, height: size };
if (name === "shield") return ;
if (name === "arrow") return ;
if (name === "check") return ;
return ;
}
function panelClass(variant?: string, tone?: string) {
if (variant === "glass") {
if (tone === "dark") {
return "bg-slate-900/72 border border-white/10 text-white shadow-[0_18px_50px_rgba(15,23,42,0.26)] backdrop-blur-xl";
}
if (tone === "accent") {
return "bg-blue-600/88 border border-blue-300/30 text-white shadow-[0_20px_56px_rgba(37,99,235,0.28)] backdrop-blur-xl";
}
return "bg-white/84 border border-white/50 text-slate-950 shadow-[0_20px_56px_rgba(15,23,42,0.18)] backdrop-blur-xl";
}
if (variant === "ghost") {
return "bg-transparent text-slate-900";
}
return tone === "dark"
? "bg-slate-900 text-white"
: tone === "accent"
? "bg-blue-600 text-white"
: "bg-white text-slate-950";
}
function renderNode(node: DetailUiDslNode): JSX.Element {
if (node.kind === "panel") {
return (
{node.children.map(renderNode)}
);
}
if (node.kind === "stack") {
return (
{node.children.map(renderNode)}
);
}
if (node.kind === "row") {
const columns = Math.max(1, node.columns || node.children.length || 1);
return (
{node.children.map(renderNode)}
);
}
if (node.kind === "badge") {
return (
{iconFor(node.icon, 14)}
{node.text}
);
}
if (node.kind === "heading") {
return (
{node.text}
);
}
if (node.kind === "body") {
return (
{node.text}
);
}
if (node.kind === "feature-card") {
return (
{node.tag ? (
{iconFor(node.icon, 12)}
{node.tag}
) : null}
{node.title}
{node.body ? (
{node.body}
) : null}
);
}
if (node.kind === "spec-table") {
return (
{node.rows.map((row, index) => (
0 && "border-t border-slate-100/90",
)}
>
{row.label}
{row.value}
))}
);
}
if (node.kind === "compare") {
return (
{node.before.title}
{node.before.body}
{node.after.title}
{node.after.body}
);
}
if (node.kind === "proof") {
return (
{iconFor(node.icon, 15)}
{node.text}
);
}
if (node.kind === "cta") {
return (
{node.headline ? (
{node.headline}
) : null}
{node.supportText ? (
{node.supportText}
) : null}
);
}
return ;
}
export function DetailUiDomRenderer(params: {
document: DetailUiDslV2Document | null;
imageMetrics?: {
offsetX: number;
offsetY: number;
renderedWidth: number;
renderedHeight: number;
} | null;
className?: string;
}) {
if (!params.document) return null;
const renderWidth =
params.imageMetrics?.renderedWidth && params.imageMetrics.renderedWidth > 0
? params.imageMetrics.renderedWidth
: params.document.screenSize.width;
const renderHeight =
params.imageMetrics?.renderedHeight && params.imageMetrics.renderedHeight > 0
? params.imageMetrics.renderedHeight
: params.document.screenSize.height;
const scale = Math.max(
0.55,
Math.min(
2.4,
Math.min(
renderWidth / Math.max(1, params.document.screenSize.width),
renderHeight / Math.max(1, params.document.screenSize.height),
),
),
);
const containerStyle: CSSProperties =
params.imageMetrics && params.imageMetrics.renderedWidth > 0 && params.imageMetrics.renderedHeight > 0
? {
position: "absolute",
left: `${params.imageMetrics.offsetX}px`,
top: `${params.imageMetrics.offsetY}px`,
width: `${params.imageMetrics.renderedWidth}px`,
height: `${params.imageMetrics.renderedHeight}px`,
}
: {
position: "absolute",
inset: "0px",
};
const region = normalizedRegionToPixels({
region: params.document.region,
width: renderWidth,
height: renderHeight,
});
return (
{params.document.nodes.map((node) => renderNode(node))}
);
}