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.
This commit is contained in:
2026-06-13 08:36:50 +01:00
parent b72f140737
commit 9c135179a3
15 changed files with 56 additions and 10 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+9 -7
View File
@@ -1,24 +1,24 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { FakeTile } from "./fakes"; import { SEARCH_RESULTS } from "./fakes";
const DEMOS = [ const DEMOS = [
{ {
query: "beach-day_042.jpg", query: "beach-day_042.jpg",
mode: "Filename", mode: "Filename",
description: "Plain text matches file names — the default, instant search.", description: "Plain text matches file names — the default, instant search.",
tiles: [2, 5, 0], results: SEARCH_RESULTS.filename,
}, },
{ {
query: "/s golden sunset over water", query: "/s golden sunset over water",
mode: "Semantic", mode: "Semantic",
description: "Describe what's in the picture. Visual embeddings find matches even when filenames say nothing.", 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", query: "/t landscape",
mode: "Tags", mode: "Tags",
description: "Search by AI or manual tags. Autocomplete suggests tags as you type.", description: "Search by AI or manual tags. Autocomplete suggests tags as you type.",
tiles: [4, 2, 6], results: SEARCH_RESULTS.tags,
}, },
] as const; ] as const;
@@ -69,9 +69,11 @@ export function StepSearchDemo() {
</div> </div>
{/* Fake results */} {/* Fake results */}
<div className={`mt-3 grid grid-cols-6 gap-1.5 transition-opacity duration-300 ${fullyTyped ? "opacity-100" : "opacity-30"}`}> <div className={`mt-3 grid grid-cols-3 gap-1.5 transition-opacity duration-300 ${fullyTyped ? "opacity-100" : "opacity-30"}`}>
{demo.tiles.map((tileIndex, i) => ( {demo.results.map((src, i) => (
<FakeTile key={`${demoIndex}-${i}`} index={tileIndex} /> <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> </div>
+47 -3
View File
@@ -1,7 +1,51 @@
// Shared placeholder primitives for the onboarding tour. Everything here is // Shared placeholder primitives for the onboarding tour. Everything here is
// deliberately fake — gradient stand-ins for media, looping demo progress — // deliberately fake — rights-clean demo stills (generated, owned), looping
// so new users see the real UI's shapes before their own library exists. // 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 = [ const TILE_GRADIENTS = [
"from-sky-900/70 via-slate-800 to-slate-900", "from-sky-900/70 via-slate-800 to-slate-900",
"from-amber-900/60 via-stone-800 to-stone-900", "from-amber-900/60 via-stone-800 to-stone-900",
@@ -35,7 +79,7 @@ export function FakeTile({
return ( return (
<div className={`group relative aspect-square overflow-hidden rounded-xl bg-white/[0.04] ${className}`}> <div className={`group relative aspect-square overflow-hidden rounded-xl bg-white/[0.04] ${className}`}>
{loaded ? ( {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]" /> <div className="absolute inset-0 animate-pulse bg-white/[0.04]" />
)} )}