From 0487586af98d56d66b6f73655751657d4e519523 Mon Sep 17 00:00:00 2001 From: filemon Date: Wed, 1 Apr 2026 18:11:48 -0300 Subject: [PATCH] Replace first-hatch modal with inline card, hide dashboard controls during tour UX change: the first-hatch experience is now a focused onboarding screen instead of a modal interruption. Layout during first-hatch tour: - Egg visual (top, with tour animations) - Stats (if any visible) - FirstHatchTourCard inline below stats (mission + post CTA) - No floating hero controls (camera, info, companion, incubation) - No bottom action bar (blobbies, missions, actions, shop, inventory) - No inline activity area (music, sing) The page feels like a dedicated guided flow rather than a dashboard with overlays. Normal dashboard controls return after tour completion. Architecture: clean branch in BlobbiDashboard render -- isFirstHatchTourActive gates visibility of controls/bar/activities. The inline card lives at the same level as other content sections. The first egg is treated as already in the hatch onboarding path without requiring the normal 'start incubation' entry point. --- .../tour/components/FirstHatchTourCard.tsx | 99 +++++++++++++++++++ src/blobbi/tour/index.ts | 2 +- src/pages/BlobbiPage.tsx | 96 +++++++++--------- 3 files changed, 145 insertions(+), 52 deletions(-) create mode 100644 src/blobbi/tour/components/FirstHatchTourCard.tsx diff --git a/src/blobbi/tour/components/FirstHatchTourCard.tsx b/src/blobbi/tour/components/FirstHatchTourCard.tsx new file mode 100644 index 00000000..b16ce310 --- /dev/null +++ b/src/blobbi/tour/components/FirstHatchTourCard.tsx @@ -0,0 +1,99 @@ +/** + * FirstHatchTourCard - Inline card shown below the egg during the first-hatch tour. + * + * Replaces the modal. Rendered directly in the BlobbiPage layout so the + * experience feels focused and guided rather than interrupted. + */ + +import { Send, Check } from 'lucide-react'; + +import { Button } from '@/components/ui/button'; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +interface FirstHatchTourCardProps { + /** The Blobbi's display name */ + blobbiName: string; + /** The exact phrase the user needs to include in their post */ + requiredPhrase: string; + /** Whether the post mission has been completed */ + postCompleted: boolean; + /** Open the post composer */ + onCreatePost: () => void; + /** Advance the tour (called after post is confirmed complete) */ + onContinue: () => void; +} + +// ─── Component ──────────────────────────────────────────────────────────────── + +export function FirstHatchTourCard({ + blobbiName, + requiredPhrase, + postCompleted, + onCreatePost, + onContinue, +}: FirstHatchTourCardProps) { + const capitalizedName = blobbiName.charAt(0).toUpperCase() + blobbiName.slice(1); + + return ( +
+ {/* Title + description */} +
+

+ {capitalizedName} is ready to hatch! +

+

+ Share a post to the Nostr network and help {capitalizedName} break free. +

+
+ + {/* Mission card */} +
+
+ {/* Status indicator */} +
+ {postCompleted && } +
+ +
+

+ {postCompleted ? 'Post shared!' : 'Share a hatch post'} +

+

+ Your post must include: +

+

+ {requiredPhrase} +

+
+
+ + {!postCompleted && ( + + )} +
+ + {/* Continue or hint */} + {postCompleted ? ( + + ) : ( +

+ You can add extra text before or after the required phrase. +

+ )} +
+ ); +} diff --git a/src/blobbi/tour/index.ts b/src/blobbi/tour/index.ts index 262e8d4b..564b3240 100644 --- a/src/blobbi/tour/index.ts +++ b/src/blobbi/tour/index.ts @@ -43,4 +43,4 @@ export type { } from './hooks/useFirstHatchTourActivation'; // ── First Hatch Tour - Components ── -export { FirstHatchTourModal } from './components/FirstHatchTourModal'; +export { FirstHatchTourCard } from './components/FirstHatchTourCard'; diff --git a/src/pages/BlobbiPage.tsx b/src/pages/BlobbiPage.tsx index 8be277ff..ac9de9ca 100644 --- a/src/pages/BlobbiPage.tsx +++ b/src/pages/BlobbiPage.tsx @@ -90,7 +90,7 @@ import { getActionEmotion, type ActionType } from '@/blobbi/ui/lib/status-reacti import type { BlobbiEmotion } from '@/blobbi/ui/lib/emotions'; import { useQuery } from '@tanstack/react-query'; import { useNostr } from '@nostrify/react'; -import { useFirstHatchTour, useFirstHatchTourActivation, FirstHatchTourModal } from '@/blobbi/tour'; +import { useFirstHatchTour, useFirstHatchTourActivation, FirstHatchTourCard } from '@/blobbi/tour'; import { buildHatchPhrase, isValidHatchPost } from '@/blobbi/actions'; import type { EggTourVisualState } from '@/blobbi/egg'; @@ -946,26 +946,30 @@ function BlobbiDashboard({ // The required phrase for the first-hatch post const firstHatchPhrase = useMemo(() => buildHatchPhrase(companion.name), [companion.name]); - // Auto-advance from idle to egg_ready_hint, then to show_hatch_modal + // Auto-advance from idle -> egg_ready_hint -> show_hatch_modal (inline card) useEffect(() => { if (!isFirstHatchTourActive) return; if (firstHatchTour.isStep('idle')) { - // Advance immediately to egg_ready_hint - firstHatchTour.actions.advance(); + firstHatchTour.actions.advance(); // -> egg_ready_hint } }, [isFirstHatchTourActive, firstHatchTour]); useEffect(() => { if (!isFirstHatchTourActive) return; if (firstHatchTour.isStep('egg_ready_hint')) { - // Show the ready hint briefly, then move to the modal + // Show the ready hint wiggle briefly, then show the inline card const timer = setTimeout(() => { - firstHatchTour.actions.advance(); + firstHatchTour.actions.advance(); // -> show_hatch_modal (inline card step) }, 3000); return () => clearTimeout(timer); } }, [isFirstHatchTourActive, firstHatchTour]); + // Whether the inline first-hatch card should be shown + const showFirstHatchCard = isFirstHatchTourActive && ( + firstHatchTour.isStep('show_hatch_modal') || firstHatchTour.isStep('await_create_post') + ); + // Detect hatch post completion for the first-hatch tour const { user } = useCurrentUser(); const { nostr } = useNostr(); @@ -1419,8 +1423,8 @@ function BlobbiDashboard({ {/* Hero Section */}
- {/* Floating Dashboard Controls */} -