feat(onboarding): real demo stills in place of gradient placeholders
Swap the fake gallery tiles' gradient stand-ins for 13 rights-clean generated WebP stills (512px, ~25 KB each). Search demo now returns thematically matched results — beach/dunes for the filename query, sunset and water for /s, landscapes for /t — so the show-don't-tell steps look like a real library. tileGradient retained for the abstract Explore and Timeline mini-previews.
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 30 KiB |
@@ -1,24 +1,24 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { FakeTile } from "./fakes";
|
||||
import { SEARCH_RESULTS } from "./fakes";
|
||||
|
||||
const DEMOS = [
|
||||
{
|
||||
query: "beach-day_042.jpg",
|
||||
mode: "Filename",
|
||||
description: "Plain text matches file names — the default, instant search.",
|
||||
tiles: [2, 5, 0],
|
||||
results: SEARCH_RESULTS.filename,
|
||||
},
|
||||
{
|
||||
query: "/s golden sunset over water",
|
||||
mode: "Semantic",
|
||||
description: "Describe what's in the picture. Visual embeddings find matches even when filenames say nothing.",
|
||||
tiles: [1, 7, 5],
|
||||
results: SEARCH_RESULTS.semantic,
|
||||
},
|
||||
{
|
||||
query: "/t landscape",
|
||||
mode: "Tags",
|
||||
description: "Search by AI or manual tags. Autocomplete suggests tags as you type.",
|
||||
tiles: [4, 2, 6],
|
||||
results: SEARCH_RESULTS.tags,
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -69,9 +69,11 @@ export function StepSearchDemo() {
|
||||
</div>
|
||||
|
||||
{/* Fake results */}
|
||||
<div className={`mt-3 grid grid-cols-6 gap-1.5 transition-opacity duration-300 ${fullyTyped ? "opacity-100" : "opacity-30"}`}>
|
||||
{demo.tiles.map((tileIndex, i) => (
|
||||
<FakeTile key={`${demoIndex}-${i}`} index={tileIndex} />
|
||||
<div className={`mt-3 grid grid-cols-3 gap-1.5 transition-opacity duration-300 ${fullyTyped ? "opacity-100" : "opacity-30"}`}>
|
||||
{demo.results.map((src, i) => (
|
||||
<div key={`${demoIndex}-${i}`} className="aspect-square overflow-hidden rounded-xl bg-white/[0.04]">
|
||||
<img src={src} alt="" className="h-full w-full object-cover" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,51 @@
|
||||
// Shared placeholder primitives for the onboarding tour. Everything here is
|
||||
// deliberately fake — gradient stand-ins for media, looping demo progress —
|
||||
// so new users see the real UI's shapes before their own library exists.
|
||||
// deliberately fake — rights-clean demo stills (generated, owned), looping
|
||||
// demo progress — so new users see the real UI's shapes before their own
|
||||
// library exists.
|
||||
import sunset from "../../assets/onboarding/sunset.webp";
|
||||
import beach from "../../assets/onboarding/beach.webp";
|
||||
import landscape1 from "../../assets/onboarding/landscape1.webp";
|
||||
import landscape2 from "../../assets/onboarding/landscape2.webp";
|
||||
import forest from "../../assets/onboarding/forest.webp";
|
||||
import citynight from "../../assets/onboarding/citynight.webp";
|
||||
import flower from "../../assets/onboarding/flower.webp";
|
||||
import alpinelake from "../../assets/onboarding/alpinelake.webp";
|
||||
import dunes from "../../assets/onboarding/dunes.webp";
|
||||
import cozy from "../../assets/onboarding/cozy.webp";
|
||||
import cat from "../../assets/onboarding/cat.webp";
|
||||
import balloon from "../../assets/onboarding/balloon.webp";
|
||||
import architecture from "../../assets/onboarding/architecture.webp";
|
||||
|
||||
// Ordered for grid variety: adjacent indices are visually distinct.
|
||||
const FAKE_IMAGES = [
|
||||
sunset,
|
||||
cozy,
|
||||
landscape1,
|
||||
citynight,
|
||||
flower,
|
||||
dunes,
|
||||
alpinelake,
|
||||
cat,
|
||||
forest,
|
||||
balloon,
|
||||
landscape2,
|
||||
beach,
|
||||
architecture,
|
||||
];
|
||||
|
||||
export function fakeImage(index: number): string {
|
||||
return FAKE_IMAGES[index % FAKE_IMAGES.length];
|
||||
}
|
||||
|
||||
// Curated result sets so each search demo returns thematically right images.
|
||||
export const SEARCH_RESULTS = {
|
||||
filename: [beach, dunes, sunset], // "beach-day_042.jpg"
|
||||
semantic: [sunset, alpinelake, beach], // "golden sunset over water"
|
||||
tags: [landscape1, landscape2, forest], // "landscape"
|
||||
} as const;
|
||||
|
||||
// Abstract gradient blobs/ticks for the Explore & Timeline mini-previews,
|
||||
// where small non-photographic shapes read more clearly than tiny stills.
|
||||
const TILE_GRADIENTS = [
|
||||
"from-sky-900/70 via-slate-800 to-slate-900",
|
||||
"from-amber-900/60 via-stone-800 to-stone-900",
|
||||
@@ -35,7 +79,7 @@ export function FakeTile({
|
||||
return (
|
||||
<div className={`group relative aspect-square overflow-hidden rounded-xl bg-white/[0.04] ${className}`}>
|
||||
{loaded ? (
|
||||
<div className={`absolute inset-0 bg-gradient-to-br ${tileGradient(index)} transition-opacity duration-500`} />
|
||||
<img src={fakeImage(index)} alt="" loading="lazy" className="absolute inset-0 h-full w-full object-cover" />
|
||||
) : (
|
||||
<div className="absolute inset-0 animate-pulse bg-white/[0.04]" />
|
||||
)}
|
||||
|
||||