Browse Source

feat: add template thumbnail images with hover transitions

- Add template thumbnails to public/template-thumbnails/
- Add hover images for model-product and style-transfer templates
- Update TemplateCard to support hover image with opacity transition
- Replace street-scene.jpg with new-bg-model-product.png in model-product
- Replace house-lake.jpg with style-transfer-reference.png in style-transfer

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 6 months ago
parent
commit
9e1b5be185
  1. BIN
      public/sample-images/new-bg-model-product.png
  2. BIN
      public/sample-images/style-transfer-reference.png
  3. BIN
      public/template-thumbnails/background-swap.png
  4. BIN
      public/template-thumbnails/color-variations.png
  5. BIN
      public/template-thumbnails/model-product-hover.png
  6. BIN
      public/template-thumbnails/model-product.png
  7. BIN
      public/template-thumbnails/product-shot.png
  8. BIN
      public/template-thumbnails/scene-composite.png
  9. BIN
      public/template-thumbnails/style-transfer-hover.png
  10. BIN
      public/template-thumbnails/style-transfer.png
  11. 26
      src/components/quickstart/TemplateCard.tsx
  12. 32
      src/components/quickstart/TemplateExplorerView.tsx
  13. 7
      src/lib/quickstart/templates.ts

BIN
public/sample-images/new-bg-model-product.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 KiB

BIN
public/sample-images/style-transfer-reference.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
public/template-thumbnails/background-swap.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 KiB

BIN
public/template-thumbnails/color-variations.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 389 KiB

BIN
public/template-thumbnails/model-product-hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 KiB

BIN
public/template-thumbnails/model-product.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

BIN
public/template-thumbnails/product-shot.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 KiB

BIN
public/template-thumbnails/scene-composite.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
public/template-thumbnails/style-transfer-hover.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
public/template-thumbnails/style-transfer.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 KiB

26
src/components/quickstart/TemplateCard.tsx

@ -13,6 +13,7 @@ interface TemplateCardProps {
};
nodeCount: number;
previewImage?: string;
hoverImage?: string;
isLoading?: boolean;
onUseWorkflow: () => void;
disabled?: boolean;
@ -36,6 +37,7 @@ export function TemplateCard({
template,
nodeCount,
previewImage,
hoverImage,
isLoading = false,
onUseWorkflow,
disabled = false,
@ -64,12 +66,24 @@ export function TemplateCard({
`}
>
{previewImage ? (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={previewImage}
alt={`${template.name} preview`}
className="w-full h-full object-cover"
/>
<>
{/* Primary image */}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={previewImage}
alt={`${template.name} preview`}
className={`absolute inset-0 w-full h-full object-cover transition-opacity duration-300 ${hoverImage ? "group-hover:opacity-0" : ""}`}
/>
{/* Hover image (if provided) */}
{hoverImage && (
/* eslint-disable-next-line @next/next/no-img-element */
<img
src={hoverImage}
alt={`${template.name} hover preview`}
className="absolute inset-0 w-full h-full object-cover opacity-0 group-hover:opacity-100 transition-opacity duration-300"
/>
)}
</>
) : (
<div className="absolute inset-0 flex items-center justify-center">
<svg

32
src/components/quickstart/TemplateExplorerView.tsx

@ -2,7 +2,7 @@
import { useState, useEffect, useCallback, useMemo, useRef } from "react";
import { WorkflowFile } from "@/store/workflowStore";
import { getAllPresets, PRESET_TEMPLATES, getTemplateContent } from "@/lib/quickstart/templates";
import { getAllPresets, PRESET_TEMPLATES } from "@/lib/quickstart/templates";
import { QuickstartBackButton } from "./QuickstartBackButton";
import { TemplateCard } from "./TemplateCard";
import { CommunityWorkflowMeta, TemplateCategory, TemplateMetadata } from "@/types/quickstart";
@ -69,18 +69,21 @@ export function TemplateExplorerView({
return metadata;
}, []);
// Get preview image for each template (first image from "full" content level)
const previewImages = useMemo(() => {
const images: Record<string, string | undefined> = {};
PRESET_TEMPLATES.forEach((template) => {
const content = getTemplateContent(template.id, "full");
if (content?.images) {
const imageUrls = Object.values(content.images).map((img) => img.url);
images[template.id] = imageUrls[0];
}
});
return images;
}, []);
// Template thumbnail images (static paths)
const templateThumbnails: Record<string, { primary: string; hover?: string }> = {
"product-shot": { primary: "/template-thumbnails/product-shot.png" },
"model-product": {
primary: "/template-thumbnails/model-product.png",
hover: "/template-thumbnails/model-product-hover.png"
},
"color-variations": { primary: "/template-thumbnails/color-variations.png" },
"background-swap": { primary: "/template-thumbnails/background-swap.png" },
"style-transfer": {
primary: "/template-thumbnails/style-transfer.png",
hover: "/template-thumbnails/style-transfer-hover.png"
},
"scene-composite": { primary: "/template-thumbnails/scene-composite.png" },
};
// Filter presets based on search, category, and tags
const filteredPresets = useMemo(() => {
@ -405,7 +408,8 @@ export function TemplateExplorerView({
key={preset.id}
template={preset}
nodeCount={presetMetadata[preset.id]?.nodeCount ?? 0}
previewImage={previewImages[preset.id]}
previewImage={templateThumbnails[preset.id]?.primary}
hoverImage={templateThumbnails[preset.id]?.hover}
isLoading={loadingWorkflowId === preset.id}
onUseWorkflow={() => handlePresetSelect(preset.id)}
disabled={isLoading && loadingWorkflowId !== preset.id}

7
src/lib/quickstart/templates.ts

@ -48,6 +48,9 @@ export const SAMPLE_IMAGES = {
// Animals
donkey: "/sample-images/donkey.jpg",
owl: "/sample-images/owl.jpg",
// Reference images for templates
newBgModelProduct: "/sample-images/new-bg-model-product.png",
styleTransferReference: "/sample-images/style-transfer-reference.png",
};
// Default node dimensions for consistent layouts
@ -153,7 +156,7 @@ const TEMPLATE_CONTENT: Record<string, Record<ContentLevel, TemplateContent>> =
images: {
"imageInput-1": { url: SAMPLE_IMAGES.model, filename: "model.jpg" },
"imageInput-2": { url: SAMPLE_IMAGES.rayban, filename: "rayban.jpg" },
"imageInput-3": { url: SAMPLE_IMAGES.streetScene, filename: "street-scene.jpg" },
"imageInput-3": { url: SAMPLE_IMAGES.newBgModelProduct, filename: "new-bg-model-product.png" },
},
},
},
@ -216,7 +219,7 @@ const TEMPLATE_CONTENT: Record<string, Record<ContentLevel, TemplateContent>> =
"prompt-1": "Apply the warm, rustic color palette and painterly texture from the house by the lake image to the owl portrait. Maintain the owl's detailed features and pose, but transform it into an artistic interpretation that feels like it belongs in the same visual world as the landscape. The result should have the warm earth tones, soft lighting, and dreamy quality of the reference.",
},
images: {
"imageInput-1": { url: SAMPLE_IMAGES.houseLake, filename: "house-lake.jpg" },
"imageInput-1": { url: SAMPLE_IMAGES.styleTransferReference, filename: "style-transfer-reference.png" },
"imageInput-2": { url: SAMPLE_IMAGES.owl, filename: "owl.jpg" },
},
},

Loading…
Cancel
Save