Clean up first-hatch tour: real loading state, dev naming, reveal centering
- Wire real companionsFetching prop into BlobbiDashboard and pass it to useFirstHatchTourActivation instead of hardcoded isLoading: false - Rename skipPostRequirement to skipToEggGlow in both BlobbiPage and BlobbiDevEditor (no post requirement exists in the simplified flow) - Update dev button labels: 'Skip to Glow', 'Restart Tour', 'Reset to Egg + Tour' with accurate titles/descriptions - Handle async resetTour consistently with void in all onClick handlers - Fix reveal overlay centering: move rays/glow/particles inside the Blobbi visual container (relative positioning) instead of a separate viewport-centered absolute layer, so the composition is anchored to the Blobbi regardless of header/naming section height
This commit is contained in:
@@ -29,9 +29,9 @@ import { ADULT_FORMS } from '@/blobbi/adult-blobbi/types/adult.types';
|
||||
|
||||
/** Tour dev actions for the first-hatch tour */
|
||||
interface FirstHatchTourDevActions {
|
||||
/** Skip the current step (for dev testing) */
|
||||
skipPostRequirement: () => void;
|
||||
/** Reset the tour in-memory state AND the profile tags so it can be tested again */
|
||||
/** Jump from idle directly to egg_glowing_waiting_click (skip the auto-advance) */
|
||||
skipToEggGlow: () => void;
|
||||
/** Reset in-memory tour state AND Kind 11125 profile tags so the tour can re-activate */
|
||||
resetTour: () => Promise<void>;
|
||||
/** Current tour step id, or null if not active */
|
||||
currentStepId: string | null;
|
||||
@@ -561,25 +561,25 @@ export function BlobbiDevEditor({
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Test the first-hatch tour flow without needing to create a real post.
|
||||
Test the first-hatch tour. Profile tag <code>blobbi_first_hatch_tour_done</code> is the persisted signal.
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* A. Skip Post Requirement */}
|
||||
{/* A. Skip idle → egg glow */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
tourDevActions.skipPostRequirement();
|
||||
tourDevActions.skipToEggGlow();
|
||||
}}
|
||||
disabled={tourDevActions.currentStepId !== 'idle'}
|
||||
className="gap-2 text-xs"
|
||||
title="Skip to egg_glowing_waiting_click (skips idle)"
|
||||
title="Jump from idle to egg_glowing_waiting_click"
|
||||
>
|
||||
<SkipForward className="size-3.5" />
|
||||
Skip Post
|
||||
Skip to Glow
|
||||
</Button>
|
||||
|
||||
{/* B. Restart First-Hatch Tour (resets profile tags + in-memory state) */}
|
||||
{/* B. Restart tour (resets profile tags + in-memory state) */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@@ -587,24 +587,24 @@ export function BlobbiDevEditor({
|
||||
void tourDevActions.resetTour();
|
||||
}}
|
||||
className="gap-2 text-xs"
|
||||
title="Reset first-hatch tour: clears profile tags and in-memory state"
|
||||
title="Reset profile tags + in-memory state so the tour can re-activate"
|
||||
>
|
||||
<RefreshCw className="size-3.5" />
|
||||
Restart Tour
|
||||
</Button>
|
||||
|
||||
{/* C. Reset Blobbi to Egg */}
|
||||
{/* C. Reset Blobbi to egg + restart tour */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setStage('egg');
|
||||
setState('active');
|
||||
tourDevActions.resetTour();
|
||||
void tourDevActions.resetTour();
|
||||
}}
|
||||
disabled={companion.stage === 'egg'}
|
||||
className="gap-2 text-xs"
|
||||
title="Set stage to egg AND reset the tour — apply changes to test from scratch"
|
||||
title="Set stage to egg + reset tour tags — click Apply to publish, then tour auto-starts"
|
||||
>
|
||||
<Egg className="size-3.5" />
|
||||
Reset to Egg + Tour
|
||||
|
||||
@@ -123,61 +123,6 @@ export function BlobbiRevealOverlay({
|
||||
{/* Backdrop */}
|
||||
<div className="absolute inset-0 bg-black/70 backdrop-blur-sm" />
|
||||
|
||||
{/* Light rays behind the Blobbi */}
|
||||
{!reducedMotion && (
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none overflow-hidden">
|
||||
{/* Radial glow */}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute size-[500px] rounded-full',
|
||||
'bg-gradient-radial from-amber-300/30 via-amber-200/10 to-transparent',
|
||||
'transition-transform duration-1000 ease-out',
|
||||
isVisible ? 'scale-100' : 'scale-50',
|
||||
)}
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(251,191,36,0.3) 0%, rgba(251,191,36,0.1) 35%, transparent 70%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Rotating rays */}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute size-[600px]',
|
||||
isVisible ? 'animate-spin-slow' : '',
|
||||
)}
|
||||
style={{ animationDuration: '20s' }}
|
||||
>
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute top-1/2 left-1/2 h-[300px] w-[2px] -translate-x-1/2 origin-top"
|
||||
style={{
|
||||
transform: `translateX(-50%) rotate(${i * 45}deg)`,
|
||||
background: 'linear-gradient(to bottom, rgba(251,191,36,0.4), transparent)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Floating particles */}
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
'absolute size-1.5 rounded-full bg-amber-300/60',
|
||||
'animate-float-particle',
|
||||
)}
|
||||
style={{
|
||||
left: `${20 + Math.random() * 60}%`,
|
||||
top: `${20 + Math.random() * 60}%`,
|
||||
animationDelay: `${i * 0.3}s`,
|
||||
animationDuration: `${2 + Math.random() * 2}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Center content */}
|
||||
<div
|
||||
ref={contentRef}
|
||||
@@ -200,16 +145,73 @@ export function BlobbiRevealOverlay({
|
||||
<Sparkles className="size-5" />
|
||||
</div>
|
||||
|
||||
{/* Blobbi visual */}
|
||||
{/* Blobbi visual — rays/glow are positioned relative to this container
|
||||
so the composition is always centered on the Blobbi itself */}
|
||||
<div className={cn(
|
||||
'relative flex items-center justify-center',
|
||||
'transition-all duration-700 delay-200',
|
||||
isVisible ? 'translate-y-0 scale-100' : 'translate-y-4 scale-90',
|
||||
)}>
|
||||
{/* Light rays + glow (centered on the Blobbi) */}
|
||||
{!reducedMotion && (
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none overflow-visible">
|
||||
{/* Radial glow */}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute size-[500px] rounded-full',
|
||||
'transition-transform duration-1000 ease-out',
|
||||
isVisible ? 'scale-100' : 'scale-50',
|
||||
)}
|
||||
style={{
|
||||
background: 'radial-gradient(circle, rgba(251,191,36,0.3) 0%, rgba(251,191,36,0.1) 35%, transparent 70%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Rotating rays */}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute size-[600px]',
|
||||
isVisible ? 'animate-spin-slow' : '',
|
||||
)}
|
||||
style={{ animationDuration: '20s' }}
|
||||
>
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="absolute top-1/2 left-1/2 h-[300px] w-[2px] -translate-x-1/2 origin-top"
|
||||
style={{
|
||||
transform: `translateX(-50%) rotate(${i * 45}deg)`,
|
||||
background: 'linear-gradient(to bottom, rgba(251,191,36,0.4), transparent)',
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Floating particles */}
|
||||
{[...Array(12)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={cn(
|
||||
'absolute size-1.5 rounded-full bg-amber-300/60',
|
||||
'animate-float-particle',
|
||||
)}
|
||||
style={{
|
||||
left: `${20 + Math.random() * 60}%`,
|
||||
top: `${20 + Math.random() * 60}%`,
|
||||
animationDelay: `${i * 0.3}s`,
|
||||
animationDuration: `${2 + Math.random() * 2}s`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* The actual Blobbi */}
|
||||
<BlobbiStageVisual
|
||||
companion={companion}
|
||||
size="lg"
|
||||
animated
|
||||
className="size-48 sm:size-56 drop-shadow-[0_0_30px_rgba(251,191,36,0.3)]"
|
||||
className="relative z-10 size-48 sm:size-56 drop-shadow-[0_0_30px_rgba(251,191,36,0.3)]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -773,6 +773,7 @@ function BlobbiContent() {
|
||||
invalidateCompanion={invalidateCompanion}
|
||||
setStoredSelectedD={setStoredSelectedD}
|
||||
ensureCanonicalBeforeAction={ensureCanonicalBeforeAction}
|
||||
companionsFetching={companionFetching}
|
||||
// DEV ONLY: State editor props
|
||||
showDevEditor={showDevEditor}
|
||||
setShowDevEditor={setShowDevEditor}
|
||||
@@ -837,6 +838,8 @@ interface BlobbiDashboardProps {
|
||||
profileAllTags: string[][];
|
||||
profileStorage: import('@/blobbi/core/lib/blobbi').StorageItem[];
|
||||
} | null>;
|
||||
/** Whether companion data is still being fetched from relays */
|
||||
companionsFetching: boolean;
|
||||
// DEV ONLY: State editor props
|
||||
showDevEditor: boolean;
|
||||
setShowDevEditor: (show: boolean) => void;
|
||||
@@ -870,6 +873,7 @@ function BlobbiDashboard({
|
||||
invalidateCompanion,
|
||||
setStoredSelectedD,
|
||||
ensureCanonicalBeforeAction,
|
||||
companionsFetching,
|
||||
// DEV ONLY
|
||||
showDevEditor,
|
||||
setShowDevEditor,
|
||||
@@ -993,7 +997,7 @@ function BlobbiDashboard({
|
||||
const firstHatchTour = useFirstHatchTour();
|
||||
useFirstHatchTourActivation({
|
||||
companions,
|
||||
isLoading: false,
|
||||
isLoading: companionsFetching,
|
||||
tour: firstHatchTour,
|
||||
profileFirstHatchTourDone: profile?.firstHatchTourDone,
|
||||
});
|
||||
@@ -1177,8 +1181,7 @@ function BlobbiDashboard({
|
||||
}, [firstHatchTour, profile, publishEvent, updateProfileEvent, invalidateProfile]);
|
||||
|
||||
const tourDevActions = useMemo(() => ({
|
||||
skipPostRequirement: () => {
|
||||
// No post requirement anymore - skip to glowing
|
||||
skipToEggGlow: () => {
|
||||
if (firstHatchTour.isStep('idle')) {
|
||||
firstHatchTour.actions.goTo('egg_glowing_waiting_click');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user