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 && (
+
+
+ Create Post
+
+ )}
+
+
+ {/* Continue or hint */}
+ {postCompleted ? (
+
+ Continue
+
+ ) : (
+
+ 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 */}
-
setShowDevEditor(true)}
// DEV ONLY: Open emotion tester panel
onDevOpenEmotionPanel={() => setShowEmotionPanel(true)}
- />
+ />}
{/* Blobbi Name */}
@@ -1528,8 +1532,23 @@ function BlobbiDashboard({
- {/* Inline Activity Area - inside padded container for proper spacing above bottom bar */}
- {inlineActivity.type === 'music' && (
+ {/* First Hatch Tour: inline card below stats */}
+ {showFirstHatchCard && (
+
+ setShowPostModal(true)}
+ onContinue={() => {
+ firstHatchTour.actions.goTo('egg_glowing_waiting_click');
+ }}
+ />
+
+ )}
+
+ {/* Inline Activity Area - hidden during first-hatch tour */}
+ {!isFirstHatchTourActive && inlineActivity.type === 'music' && (
)}
- {inlineActivity.type === 'sing' && (
+ {!isFirstHatchTourActive && inlineActivity.type === 'sing' && (
- {/* Bottom Action Bar */}
- setShowSelector(true)}
- onMissionsClick={() => {
- if (isFirstHatchTourActive) {
- // During the first-hatch tour, open the tour modal instead
- if (!firstHatchTour.isStep('show_hatch_modal') && !firstHatchTour.isStep('await_create_post')) {
- firstHatchTour.actions.goTo('show_hatch_modal');
- }
- } else {
- setShowMissionsModal(true);
- }
- }}
- onActionsClick={() => setShowActionsModal(true)}
- onShopClick={() => setShowShopModal(true)}
- onInventoryClick={() => setShowInventoryModal(true)}
- needyBlobbiesCount={companions.filter(companionNeedsCare).length}
- isInTaskProcess={isFirstHatchTourActive || isInTaskProcess}
- remainingTasksCount={isFirstHatchTourActive ? (tourPostFound ? 0 : 1) : remainingTasksCount}
- allTasksComplete={isFirstHatchTourActive ? !!tourPostFound : allTasksComplete}
- />
+ {/* Bottom Action Bar - hidden during first-hatch tour */}
+ {!isFirstHatchTourActive && (
+ setShowSelector(true)}
+ onMissionsClick={() => setShowMissionsModal(true)}
+ onActionsClick={() => setShowActionsModal(true)}
+ onShopClick={() => setShowShopModal(true)}
+ onInventoryClick={() => setShowInventoryModal(true)}
+ needyBlobbiesCount={companions.filter(companionNeedsCare).length}
+ isInTaskProcess={isInTaskProcess}
+ remainingTasksCount={remainingTasksCount}
+ allTasksComplete={allTasksComplete}
+ />
+ )}
{/* Blobbi Selector Modal */}
@@ -1724,24 +1736,6 @@ function BlobbiDashboard({
onSuccess={refetchCurrentTasks}
/>
- {/* First Hatch Tour Modal */}
- {
- if (!open && isFirstHatchTourActive && firstHatchTour.isStep('show_hatch_modal')) {
- // User dismissed the modal -- move to await_create_post so they can
- // come back to it (the modal stays open for await_create_post too)
- firstHatchTour.actions.advance();
- }
- }}
- blobbiName={companion.name}
- requiredPhrase={firstHatchPhrase}
- postCompleted={!!tourPostFound}
- onCreatePost={() => setShowPostModal(true)}
- onContinue={() => {
- firstHatchTour.actions.goTo('egg_glowing_waiting_click');
- }}
- />
{/* Blobbi Photo Modal - polaroid-style photo capture */}