mirror of
https://github.com/hanzoai/frames.git
synced 2026-09-24 00:08:03 +00:00
The workflow skills, the CLI reference, and the guides described a vendor's services: a separate account to sign into, a credential file beside the project, a music library and asset search that are not ours, and preview media on a CDN we do not run. Auth is now one Hanzo IAM token in $HANZO_API_KEY, read from Hanzo KMS — one credential for voice, music, sound effects, images, video, the shared catalog, and cloud rendering. The credential guide, the cloud-render pages, and the CLI auth reference all say the same thing, because there is now only one thing to say. Docs previews resolve from the docs site's own /images/ path, produced by our own generator. They are not committed (docs/images/ is ignored), so a deploy must run `bun run generate:catalog-previews` first — the same gap as before, minus a CDN we cannot publish to. Element grounding calls zen-vl through api.hanzo.ai rather than a third-party vision endpoint, and the LUT library builds every look from params, so no look needs a host at all. Command examples read `npx @hanzo/frame` / `hanzo frame` throughout. Three navigation entries left dangling by the rebrand now resolve. The changelog and the weekly archive keep their entries as written and say plainly that pre-port entries describe the upstream product.
60 lines
1.6 KiB
React
60 lines
1.6 KiB
React
export function TemplateCard({ id, title, description, href, portrait }) {
|
|
const [hovering, setHovering] = React.useState(false);
|
|
|
|
const imgSrc = `/images/templates/${id}.png`;
|
|
const videoSrc = `/images/templates/${id}.mp4`;
|
|
|
|
return (
|
|
<a
|
|
href={href}
|
|
className="not-prose group block rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden transition-shadow hover:shadow-lg no-underline"
|
|
onMouseEnter={() => setHovering(true)}
|
|
onMouseLeave={() => setHovering(false)}
|
|
>
|
|
<div
|
|
className="relative overflow-hidden bg-gray-100 dark:bg-gray-800"
|
|
style={{ aspectRatio: portrait ? "9/16" : "16/9" }}
|
|
>
|
|
<img
|
|
src={imgSrc}
|
|
alt={`${title} example`}
|
|
className="absolute inset-0 w-full h-full object-cover"
|
|
style={{
|
|
opacity: hovering ? 0 : 1,
|
|
transition: "opacity 0.2s ease",
|
|
}}
|
|
/>
|
|
{hovering && (
|
|
<video
|
|
src={videoSrc}
|
|
autoPlay
|
|
muted
|
|
loop
|
|
playsInline
|
|
className="absolute inset-0 w-full h-full object-cover"
|
|
/>
|
|
)}
|
|
</div>
|
|
<div className="p-4">
|
|
<h3 className="text-base font-semibold text-gray-900 dark:text-white m-0">
|
|
{title}
|
|
</h3>
|
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1 mb-0">
|
|
{description}
|
|
</p>
|
|
</div>
|
|
</a>
|
|
);
|
|
}
|
|
|
|
export function TemplateGrid({ children }) {
|
|
return (
|
|
<div
|
|
className="not-prose grid gap-4"
|
|
style={{ gridTemplateColumns: "repeat(2, 1fr)" }}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|