"use client"; import type { CSSProperties } from "react"; import { cn } from "@/lib/utils"; import type { DetailUiScreenOverlaySnapshot } from "@/lib/detail-ui-surface-adapter"; type RenderableOverlayComponent = DetailUiScreenOverlaySnapshot["components"][number]; const OVERLAY_RENDERABLE_TYPES = new Set([ "detail_text_block", "detail_card", "detail_badge", "detail_feature_card", "detail_feature_tag", "detail_spec_table", "detail_table_label", "detail_table_value", "detail_comparison_strip", "detail_compare_before", "detail_compare_after", "detail_proof_bar", "detail_cta_banner", "detail_cta_button", "detail_cta_support", ]); function collectRenderableComponents(snapshot: DetailUiScreenOverlaySnapshot) { return snapshot.components .filter((component) => OVERLAY_RENDERABLE_TYPES.has(component.type)) .sort((left, right) => { const leftZ = Number(left.layout?.zIndex || 0); const rightZ = Number(right.layout?.zIndex || 0); return leftZ - rightZ; }); } function resolveOverlayText( snapshot: DetailUiScreenOverlaySnapshot, component: RenderableOverlayComponent, ) { const binding = component.bindings?.content; if (!binding || binding.kind !== "text") { return String(component.props?.content || ""); } const path = String(binding.path || "").trim(); const screen = snapshot.dataModel.screen; if (!path || !screen) { return String(component.props?.content || ""); } if (path.endsWith("/title")) return String(screen.title || ""); if (path.endsWith("/subtitle")) return String(screen.subtitle || ""); if (path.endsWith("/copy")) return String(screen.copy || ""); if (path.endsWith("/layoutNote")) return String(screen.layoutNote || ""); return String(component.props?.content || ""); } function getOverlayComponentStyle(params: { component: RenderableOverlayComponent; snapshot: DetailUiScreenOverlaySnapshot; content: string; }) { const frame = (params.component.layout?.frame || {}) as { x?: number; y?: number; width?: number; height?: number; }; const style = (params.component.style || {}) as Record; const widthScale = 100 / Math.max(1, params.snapshot.screenSize.width); const heightScale = 100 / Math.max(1, params.snapshot.screenSize.height); const overlayStyle: CSSProperties = { position: "absolute", left: `${Number(frame.x || 0) * widthScale}%`, top: `${Number(frame.y || 0) * heightScale}%`, width: `${Number(frame.width || 0) * widthScale}%`, height: `${Number(frame.height || 0) * heightScale}%`, zIndex: Number(params.component.layout?.zIndex || 1), fontSize: typeof style.fontSize === "number" ? `${style.fontSize}px` : undefined, fontFamily: typeof style.fontFamily === "string" ? style.fontFamily : undefined, fontWeight: typeof style.fontWeight === "string" ? style.fontWeight : undefined, lineHeight: typeof style.lineHeight === "number" ? style.lineHeight : undefined, letterSpacing: typeof style.letterSpacing === "number" ? `${style.letterSpacing}px` : undefined, color: typeof style.color === "string" ? style.color : undefined, textAlign: style.textAlign === "center" || style.textAlign === "right" ? style.textAlign : "left", textTransform: style.textTransform === "uppercase" || style.textTransform === "lowercase" || style.textTransform === "capitalize" ? style.textTransform : undefined, background: typeof style.backgroundColor === "string" ? style.backgroundColor : undefined, borderColor: typeof style.borderColor === "string" ? style.borderColor : undefined, borderWidth: typeof style.borderWidth === "number" ? `${style.borderWidth}px` : undefined, borderStyle: typeof style.borderWidth === "number" && style.borderWidth > 0 ? "solid" : undefined, borderRadius: typeof style.borderRadius === "number" ? `${style.borderRadius}px` : undefined, padding: typeof style.padding === "number" ? `${style.padding}px` : undefined, boxShadow: typeof style.boxShadow === "string" ? style.boxShadow : undefined, backdropFilter: typeof style.backdropBlur === "number" ? `blur(${style.backdropBlur}px)` : undefined, WebkitBackdropFilter: typeof style.backdropBlur === "number" ? `blur(${style.backdropBlur}px)` : undefined, overflow: "hidden", whiteSpace: "pre-wrap", wordBreak: "break-word", display: "flex", alignItems: "center", justifyContent: style.textAlign === "center" ? "center" : style.textAlign === "right" ? "flex-end" : "flex-start", }; if ( params.component.type === "detail_feature_card" || params.component.type === "detail_spec_table" || params.component.type === "detail_compare_before" || params.component.type === "detail_compare_after" || params.component.type === "detail_proof_bar" || params.component.type === "detail_cta_banner" || params.component.type === "detail_cta_support" ) { overlayStyle.alignItems = "flex-start"; } if (params.component.type === "detail_cta_button") { overlayStyle.justifyContent = "center"; overlayStyle.cursor = "pointer"; } if (params.component.type === "detail_table_label") { overlayStyle.justifyContent = "center"; } return overlayStyle; } export function DetailSurfaceOverlayRenderer(params: { overlay: DetailUiScreenOverlaySnapshot | null; imageMetrics?: { offsetX: number; offsetY: number; renderedWidth: number; renderedHeight: number; } | null; className?: string; }) { if (!params.overlay) return null; const components = collectRenderableComponents(params.overlay); if (components.length === 0) return null; const containerStyle = params.imageMetrics && params.imageMetrics.renderedWidth > 0 && params.imageMetrics.renderedHeight > 0 ? { position: "absolute" as const, left: `${params.imageMetrics.offsetX}px`, top: `${params.imageMetrics.offsetY}px`, width: `${params.imageMetrics.renderedWidth}px`, height: `${params.imageMetrics.renderedHeight}px`, } : { position: "absolute" as const, inset: "0px", }; return (
{components.map((component) => { const content = resolveOverlayText(params.overlay!, component); return (
{content}
); })}
); }