"use client"; import { useEffect, useState, useCallback } from "react"; export interface TutorialStep { target: string; title: string; description: string; padding?: number; } export const TUTORIAL_STEPS: TutorialStep[] = [ { target: "graph", title: "The Dependency Graph", description: "Each circle is a task or issue. The flowing particles between them show the direction of dependencies \u2014 what needs to happen before something else can start. Bigger circles mean more connections.", padding: 0, }, { target: "layouts", title: "Layout Modes", description: "Switch how the graph is arranged. Force is organic and physics-based. DAG gives you a clean top-down tree. Radial spreads nodes in rings. Cluster groups by project. Spread spaces everything out for screenshots.", }, { target: "btn-collapse", title: "Collapse / Expand", description: "Group all epic tasks into a single node, or expand them back out. Great for getting a high-level overview of your project when there are many tasks.", }, { target: "btn-clusters", title: "Cluster Labels", description: "Toggle transparent labels that show project prefixes behind groups of related nodes. Helps you see which project each cluster belongs to at a glance.", }, { target: "btn-autofit", title: "Auto-fit", description: "When enabled (green), the camera automatically re-centers and zooms to fit all nodes after every update. Turn it off to keep the camera fixed while you explore.", }, { target: "btn-pulse", title: "Pulse", description: "Highlights the most recently active node with emerald ripples. Useful for spotting which task just changed status or was just created.", }, { target: "legend-stats", title: "Project Stats", description: "A quick summary of your project: total issues, dependency count, and how many separate projects are tracked. When legend filters are active, these numbers update to reflect only the visible nodes.", }, { target: "legend-color-mode", title: "Color Mode Selector", description: "Switch what the node fill color represents. Status shows open/closed/blocked. Priority shows urgency. Type highlights epics, tasks, bugs, features, and chores. Owner, Assignee, and Prefix color by person or project. The ring around each node always shows which project it belongs to.", }, { target: "legend-items", title: "Legend", description: "Shows what each color means for the current color mode. Click any label to filter the graph — only matching nodes will be shown. Click more labels to combine filters (union). Click again to remove a filter, or use \"Clear filters\" to reset. The colored dots match the node fills in the graph.", }, { target: "minimap", title: "Minimap", description: "A bird\u2019s-eye view of your entire graph. Click anywhere to jump there. Drag the edges to resize it.", }, { target: "overlays", title: "Leaderboard & Activity", description: "These compact overlays live in the top-right corner. The activity feed shows recent changes — tasks created, statuses updated, priorities shifted. The leaderboard ranks contributors by closed issues, created issues, and comments. Click either to expand into a full sidebar.", }, { target: "graph", title: "Multi-Select & Group Drag", description: "Right-click and drag on the canvas background to draw a selection rectangle around multiple nodes. Shift-click individual nodes to toggle them in/out of the selection. Once selected, drag any selected node to move the entire group together. Press Escape to deselect all.", padding: 0, }, { target: "graph", title: "Quick Navigation Shortcuts", description: "Press Shift+0 to instantly zoom to the most recently edited node (the pulsing one). Press Shift+1 through Shift+9 to jump to a node belonging to the corresponding leaderboard contributor — Shift+1 for #1, Shift+2 for #2, and so on. Great for quickly checking what your top contributors are working on.", padding: 0, }, { target: "auth-section", title: "Sign In & Profiles", description: "Sign in with your Bluesky/ATProto account to comment, claim tasks, and like comments. Click any avatar on the graph to see that person\u2019s profile — their stats, recent activity, and contributions. Click your own handle in the dropdown to view your profile.", }, { target: "btn-theme", title: "Dark Mode", description: "Toggle between light and dark themes. Your preference is saved and persists across sessions. The entire UI — canvas, panels, tooltips, and overlays — adapts to your chosen theme.", }, { target: "graph", title: "Interacting with Nodes", description: "Click a node to see its details, labels, assignee, and linked commits. Hover for a quick tooltip. Right-click for actions like viewing descriptions, commenting, claiming tasks, collapsing epics, or focusing on an epic\u2019s subgraph. Avatars on nodes show who claimed or is assigned — hover for details like \u201Ceinstein.climateai.org claimed beads-map-123\u201D, click to open their profile.", padding: 0, }, { target: "nav-replay", title: "Replay", description: "Step through your project\u2019s history one event at a time. Watch issues appear and change status as they were created. Use the scrubber to jump to any point in time.", }, { target: "nav-comments", title: "Comments", description: "Opens a sidebar showing all conversations across your beads. See who said what, reply to threads, and like comments \u2014 all powered by the AT Protocol.", }, { target: "nav-activity", title: "Activity", description: "A real-time feed of changes for your local issues. See when tasks are created, statuses change, priorities shift, and dependencies are added. Use \"Show more\" to page through the full history. During timeline replay, the feed filters to only events up to the current point in time.", }, { target: "nav-learn", title: "Learn", description: "Opens the help panel with keyboard shortcuts, feature documentation, and this interactive tutorial. Click it anytime to come back here.", }, ]; interface TutorialOverlayProps { step: number | null; onNext: () => void; onPrev: () => void; onEnd: () => void; } interface Rect { left: number; top: number; width: number; height: number; } export function TutorialOverlay({ step, onNext, onEnd, }: TutorialOverlayProps) { const [targetRect, setTargetRect] = useState(null); const updateRect = useCallback(() => { if (step === null) { setTargetRect(null); return; } const currentStep = TUTORIAL_STEPS[step]; if (!currentStep) { setTargetRect(null); return; } const el = document.querySelector( `[data-tutorial="${currentStep.target}"]` ); if (!el) { setTargetRect(null); } else { const r = el.getBoundingClientRect(); const padding = currentStep.padding ?? 8; setTargetRect({ left: r.left - padding, top: r.top - padding, width: r.width + padding * 2, height: r.height + padding * 2, }); } }, [step]); useEffect(() => { updateRect(); const handleResize = () => updateRect(); window.addEventListener("resize", handleResize); const timer = setTimeout(updateRect, 150); return () => { window.removeEventListener("resize", handleResize); clearTimeout(timer); }; }, [updateRect]); if (step === null) return null; const isLast = step === TUTORIAL_STEPS.length - 1; const handleClick = () => { if (isLast) { onEnd(); } else { onNext(); } }; // The sidebar is z-[60] during tutorial, so it naturally sits above this z-[55] overlay. // Clicking the dark area advances/ends the tutorial. The spotlight cutout is visual only. return ( <> {/* Full-screen clickable dark overlay */}
{/* SVG with mask to punch a transparent hole for the spotlight */} {targetRect && ( )}
{/* Pulsing ring around spotlight target */} {targetRect && (
)} ); }