since3-portal/modules/templates/catalog/shared/shell.tsx
2026-10-10 02:38:50 +02:00

76 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { MotionConfig } from "motion/react"
import { Children, isValidElement, useEffect, type CSSProperties, type ReactNode } from "react"
import { cn } from "@/lib/utils"
import { themeStyle } from "../../lib/theme"
import type { FontPair, TemplateContent, TemplateDefinition, TemplateStyle } from "../../lib/types"
import { libraryFont } from "../font-library"
import { useDemo } from "./demo"
const imageStyles: Record<TemplateStyle["images"], string> = {
original: "",
mono: "[&_img]:grayscale",
warm: "[&_img]:sepia-[.3] [&_img]:saturate-[1.2]",
soft: "[&_img]:saturate-[.75] [&_img]:brightness-[1.05] [&_img]:contrast-[.95]",
contrast: "[&_img]:contrast-[1.2] [&_img]:saturate-[1.15]",
}
// Sektionen mit data-order="<schlüssel>" tauschen ihre Plätze nach content.order; alle anderen bleiben, wo sie sind
function arrange(children: ReactNode, order: string[]) {
const items = Children.toArray(children)
const keyOf = (node: ReactNode) => (isValidElement(node) ? (node.props as Record<string, unknown>)["data-order"] : undefined)
const slots = items.flatMap((node, i) => (typeof keyOf(node) === "string" ? [i] : []))
const rank = (node: ReactNode) => {
const index = order.indexOf(keyOf(node) as string)
return index < 0 ? order.length : index
}
const movable = slots.map((i) => items[i]).sort((a, b) => rank(a) - rank(b))
const arranged = [...items]
slots.forEach((slot, n) => {
arranged[slot] = movable[n]
})
return arranged
}
// Rahmen jeder Vorlage: Palette und Schriften als CSS-Variablen (bg-t-*, font-t-*), Bildstil, Bewegung,
// Sektions-Reihenfolge, sanftes Scrollen zu Ankern und – nur im Portal – Entwurfs-Hinweis und KI-Chat (shared/demo.tsx).
// Fixe Leisten am unteren Rand (z. B. mobile CTA) schieben Badge und Chat über --t-safe-bottom nach oben.
export function TemplateShell({ def, content, fonts, className, children }: {
def: TemplateDefinition
content: TemplateContent
fonts: FontPair
className?: string
children: ReactNode
}) {
const demo = useDemo()
useEffect(() => {
const root = document.documentElement
const previous = root.style.scrollBehavior
root.style.scrollBehavior = "smooth"
return () => {
root.style.scrollBehavior = previous
}
}, [])
const pair = (content.fonts.includes(":") && libraryFont(content.fonts)) || fonts
const style = { ...themeStyle(def, content), "--t-display": pair.display, "--t-body": pair.body } as CSSProperties
return (
<MotionConfig reducedMotion={content.style.motion === "subtle" ? "always" : "user"}>
<div
style={style}
className={cn(
pair.className,
"relative min-h-dvh overflow-x-clip bg-t-bg font-t-body text-t-fg antialiased selection:bg-t-accent selection:text-t-accent-fg",
imageStyles[content.style.images],
className
)}
>
{arrange(children, content.order)}
{demo?.overlay}
</div>
</MotionConfig>
)
}