diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index b51cad8..d859288 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.5.1", + "version": "2.6.0", "author": { "name": "Lum1104" }, diff --git a/.copilot-plugin/plugin.json b/.copilot-plugin/plugin.json index 9661d91..e7b425f 100644 --- a/.copilot-plugin/plugin.json +++ b/.copilot-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.5.1", + "version": "2.6.0", "author": { "name": "Lum1104" }, diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index b79221b..f222f35 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "understand-anything", "displayName": "Understand Anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.5.1", + "version": "2.6.0", "author": { "name": "Lum1104" }, diff --git a/homepage/src/components/Footer.astro b/homepage/src/components/Footer.astro index 45b58ab..ccd1bac 100644 --- a/homepage/src/components/Footer.astro +++ b/homepage/src/components/Footer.astro @@ -28,6 +28,14 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything'; margin: 0 auto; } + .footer-links { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 0.5rem 0.25rem; + } + .footer-logo { font-family: var(--font-heading); font-size: 1.15rem; @@ -60,4 +68,10 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything'; font-size: 0.8rem; color: var(--text-muted); } + + @media (max-width: 480px) { + .footer { padding: 3rem 1.25rem; } + .footer-sep { display: none; } + .footer-links { gap: 0.25rem 1rem; } + } diff --git a/homepage/src/components/Hero.astro b/homepage/src/components/Hero.astro index 63885a6..af10d52 100644 --- a/homepage/src/components/Hero.astro +++ b/homepage/src/components/Hero.astro @@ -122,12 +122,13 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything'; /* Headline */ .hero-title { font-family: var(--font-heading); - font-size: clamp(3.5rem, 9vw, 7rem); + font-size: clamp(2.75rem, 9vw, 7rem); color: #e8e2d8; line-height: 1.05; letter-spacing: -0.025em; margin-bottom: 2rem; white-space: nowrap; + max-width: 100%; } .hero-grad { @@ -141,7 +142,7 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything'; /* Tagline */ .hero-tagline { - font-size: clamp(1.1rem, 2vw, 1.4rem); + font-size: clamp(1rem, 2vw, 1.4rem); color: #8a8578; line-height: 1.7; max-width: 900px; @@ -329,15 +330,56 @@ const githubUrl = 'https://github.com/Lum1104/Understand-Anything'; .anim-6 { animation-delay: 1.15s; } /* Responsive */ + @media (max-width: 900px) { + .hero-tagline { + white-space: normal; + max-width: 36ch; + margin-left: auto; + margin-right: auto; + } + } + @media (max-width: 768px) { - .hero-pillars { gap: 0.5rem; } + .hero { padding: 5rem 1.5rem 3rem; min-height: auto; } + .hero-content { gap: 0; } + .hero-pillars { gap: 0.5rem; margin-bottom: 2.25rem; } .pillar { font-size: 0.8rem; } + .hero-badge { + font-size: 0.72rem; + padding: 0.35rem 1rem; + margin-bottom: 1.75rem; + } + .hero-title { margin-bottom: 1.5rem; } + } + + @media (max-width: 640px) { + .hero-title { + white-space: normal; + line-height: 1.02; + letter-spacing: -0.03em; + } + /* Stack the two words on dedicated lines for editorial drama */ + .hero-title > span { display: block; } } @media (max-width: 480px) { - .hero-actions { flex-direction: column; gap: 1rem; } + .hero { padding: 4rem 1.25rem 2.25rem; } + .hero-actions { + flex-direction: column; + gap: 0.75rem; + width: 100%; + max-width: 320px; + } + .hero-cta, + .hero-demo { + width: 100%; + text-align: center; + padding: 0.85rem 1.5rem; + } .hero-pillars { flex-direction: column; gap: 0.4rem; } .pillar-dot { display: none; } + .hero-subscribe { margin-top: 1.5rem; } .hero-subscribe-toggle { font-size: 0.85rem; } + .hero-subscribe-frame iframe { height: 130px; } } diff --git a/homepage/src/components/Install.astro b/homepage/src/components/Install.astro index 8d18c67..80f532e 100644 --- a/homepage/src/components/Install.astro +++ b/homepage/src/components/Install.astro @@ -131,4 +131,14 @@ .install-note strong { color: var(--text); } + + @media (max-width: 480px) { + .install { padding: 4.5rem 1.25rem; } + .install-title { margin-bottom: 1.75rem; } + pre { padding: 1rem 1.1rem; } + code { font-size: 0.82rem; line-height: 1.7; } + .install-code-header { padding: 0.65rem 0.85rem; } + .install-code-label { font-size: 0.72rem; } + .install-note { font-size: 0.82rem; line-height: 1.55; } + } diff --git a/understand-anything-plugin/.claude-plugin/plugin.json b/understand-anything-plugin/.claude-plugin/plugin.json index b51cad8..d859288 100644 --- a/understand-anything-plugin/.claude-plugin/plugin.json +++ b/understand-anything-plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "understand-anything", "description": "AI-powered codebase understanding — analyze, visualize, and explain any project", - "version": "2.5.1", + "version": "2.6.0", "author": { "name": "Lum1104" }, diff --git a/understand-anything-plugin/package.json b/understand-anything-plugin/package.json index f29e36d..7269eab 100644 --- a/understand-anything-plugin/package.json +++ b/understand-anything-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@understand-anything/skill", - "version": "2.5.1", + "version": "2.6.0", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/understand-anything-plugin/packages/dashboard/src/App.tsx b/understand-anything-plugin/packages/dashboard/src/App.tsx index efa03bd..f169f62 100644 --- a/understand-anything-plugin/packages/dashboard/src/App.tsx +++ b/understand-anything-plugin/packages/dashboard/src/App.tsx @@ -16,6 +16,8 @@ import ProjectOverview from "./components/ProjectOverview"; import FileExplorer from "./components/FileExplorer"; import WarningBanner from "./components/WarningBanner"; import TokenGate from "./components/TokenGate"; +import MobileLayout from "./components/MobileLayout"; +import { useIsMobile } from "./hooks/useIsMobile"; import { useKeyboardShortcuts } from "./hooks/useKeyboardShortcuts"; import type { KeyboardShortcut } from "./hooks/useKeyboardShortcuts"; import { ThemeProvider } from "./themes/index.ts"; @@ -118,6 +120,7 @@ function Dashboard({ accessToken }: { accessToken: string }) { const domainGraph = useDashboardStore((s) => s.domainGraph); const setDomainGraph = useDashboardStore((s) => s.setDomainGraph); const layoutIssues = useDashboardStore((s) => s.layoutIssues); + const isMobile = useIsMobile(); // Schema issues + ELK layout issues share the WarningBanner — graph-load // problems and dashboard rendering problems are equally surfaced. const allIssues = useMemo( @@ -376,17 +379,32 @@ function Dashboard({ accessToken }: { accessToken: string }) { ); + if (isMobile) { + return ( + + + + ); + } + return (
{/* Header */} -
+
{/* Left — fixed */} -
-

+
+

{graph?.project.name ?? "Understand Anything"}

-
+
{graph && !isKnowledgeGraph && domainGraph && ( <> @@ -463,12 +481,12 @@ function Dashboard({ accessToken }: { accessToken: string }) {
{/* Right — fixed actions */} -
+
- {/* Right sidebar */} -

+ + {/* Body */} +
+
+ Role + +
+ + {showViewToggle && ( +
+ View +
+ + +
+
+ )} + +
+ Diff overlay + +
+ +
+ Node types +
+ {filterDefs.map((cat) => { + const active = nodeTypeFilters[cat.key] !== false; + return ( + + ); + })} +
+
+ + {graph && (graph.layers?.length ?? 0) > 0 && ( +
+ Layers +
+ +
+
+ )} + +
+ Tools +
+ + + + + +
+
+
+ +
+ ); +} diff --git a/understand-anything-plugin/packages/dashboard/src/components/MobileLayout.tsx b/understand-anything-plugin/packages/dashboard/src/components/MobileLayout.tsx new file mode 100644 index 0000000..ab82759 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/components/MobileLayout.tsx @@ -0,0 +1,231 @@ +import { lazy, Suspense, useEffect, useState } from "react"; +import type { GraphIssue } from "@understand-anything/core/schema"; +import { useDashboardStore } from "../store"; +import GraphView from "./GraphView"; +import DomainGraphView from "./DomainGraphView"; +import KnowledgeGraphView from "./KnowledgeGraphView"; +import SearchBar from "./SearchBar"; +import NodeInfo from "./NodeInfo"; +import ProjectOverview from "./ProjectOverview"; +import FileExplorer from "./FileExplorer"; +import WarningBanner from "./WarningBanner"; +import MobileBottomNav from "./MobileBottomNav"; +import type { MobileTab } from "./MobileBottomNav"; +import MobileDrawer from "./MobileDrawer"; + +const CodeViewer = lazy(() => import("./CodeViewer")); +const LearnPanel = lazy(() => import("./LearnPanel")); +const PathFinderModal = lazy(() => import("./PathFinderModal")); +const KeyboardShortcutsHelp = lazy(() => import("./KeyboardShortcutsHelp")); + +interface Props { + accessToken: string; + showKeyboardHelp: boolean; + setShowKeyboardHelp: (value: boolean) => void; + loadError: string | null; + allIssues: GraphIssue[]; + shortcuts: import("../hooks/useKeyboardShortcuts").KeyboardShortcut[]; +} + +export default function MobileLayout({ + accessToken, + showKeyboardHelp, + setShowKeyboardHelp, + loadError, + allIssues, + shortcuts, +}: Props) { + const graph = useDashboardStore((s) => s.graph); + const selectedNodeId = useDashboardStore((s) => s.selectedNodeId); + const tourActive = useDashboardStore((s) => s.tourActive); + const persona = useDashboardStore((s) => s.persona); + const viewMode = useDashboardStore((s) => s.viewMode); + const domainGraph = useDashboardStore((s) => s.domainGraph); + const codeViewerOpen = useDashboardStore((s) => s.codeViewerOpen); + const closeCodeViewer = useDashboardStore((s) => s.closeCodeViewer); + const pathFinderOpen = useDashboardStore((s) => s.pathFinderOpen); + const togglePathFinder = useDashboardStore((s) => s.togglePathFinder); + + const [activeTab, setActiveTab] = useState("graph"); + const [drawerOpen, setDrawerOpen] = useState(false); + const [searchOpen, setSearchOpen] = useState(false); + + // Auto-pivot to Info when a node is selected — keeps feedback visible + // on a small screen where graph and sidebar can't coexist + useEffect(() => { + if (selectedNodeId) setActiveTab("info"); + }, [selectedNodeId]); + + // When a code viewer opens (e.g. from the Files tab) keep focus there + useEffect(() => { + if (codeViewerOpen) setSearchOpen(false); + }, [codeViewerOpen]); + + const isLearnMode = tourActive || persona === "junior"; + const infoContent = ( + <> + {selectedNodeId && } + {isLearnMode && ( + + + + )} + {!selectedNodeId && !isLearnMode && } + + ); + + return ( +
+ {/* Top bar */} +
+ + +

+ {graph?.project.name ?? "Understand Anything"} +

+ + +
+ + {/* Search (collapsible) */} + {searchOpen && } + + {/* Validation warnings */} + {allIssues.length > 0 && !loadError && } + + {/* Load error */} + {loadError && ( +
+ {loadError} +
+ )} + + {/* Tabbed content — all panes stay mounted to preserve layout/state. + Inactive panes are kept in the layout (not display:none) so that + ReactFlow keeps real dimensions and pinch/pan don't collapse on + tab switch. */} +
+
+ {viewMode === "knowledge" ? ( + + ) : viewMode === "domain" && domainGraph ? ( + + ) : ( + + )} +
+ +
+ {infoContent} +
+ +
+ +
+
+ + {/* Bottom tab nav */} + + + {/* Drawer */} + setDrawerOpen(false)} + onTogglePathFinder={togglePathFinder} + onShowKeyboardHelp={() => setShowKeyboardHelp(true)} + /> + + {/* Code viewer — always fullscreen on mobile */} + {codeViewerOpen && ( +
+
event.stopPropagation()} + > + + + +
+
+ )} + + {/* Keyboard help (mobile reads it as a quick reference too) */} + {showKeyboardHelp && ( + + setShowKeyboardHelp(false)} + /> + + )} + + {/* Path finder */} + {pathFinderOpen && ( + + + + )} +
+ ); +} diff --git a/understand-anything-plugin/packages/dashboard/src/components/SearchBar.tsx b/understand-anything-plugin/packages/dashboard/src/components/SearchBar.tsx index 27a1c94..a7135e6 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/SearchBar.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/SearchBar.tsx @@ -84,7 +84,7 @@ export default function SearchBar() { return (
-
+
setDropdownOpen(true)} placeholder="Search nodes by name, summary, or tags..." - className="flex-1 bg-elevated text-text-primary text-sm rounded-lg px-3 py-1.5 border border-border-subtle focus:outline-none focus:border-accent/50 placeholder-text-muted" + className="flex-1 min-w-0 bg-elevated text-text-primary text-sm rounded-lg px-3 py-1.5 border border-border-subtle focus:outline-none focus:border-accent/50 placeholder-text-muted" />
{searchQuery.trim() && ( - + {searchResults.length} result{searchResults.length !== 1 ? "s" : ""}{" "} ({searchMode}) diff --git a/understand-anything-plugin/packages/dashboard/src/hooks/useIsMobile.ts b/understand-anything-plugin/packages/dashboard/src/hooks/useIsMobile.ts new file mode 100644 index 0000000..2e8d154 --- /dev/null +++ b/understand-anything-plugin/packages/dashboard/src/hooks/useIsMobile.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; + +const DEFAULT_BREAKPOINT = 768; + +export function useIsMobile(breakpoint: number = DEFAULT_BREAKPOINT): boolean { + const query = `(max-width: ${breakpoint - 1}px)`; + const [isMobile, setIsMobile] = useState(() => { + if (typeof window === "undefined") return false; + return window.matchMedia(query).matches; + }); + + useEffect(() => { + const mql = window.matchMedia(query); + const handler = (event: MediaQueryListEvent) => setIsMobile(event.matches); + setIsMobile(mql.matches); + mql.addEventListener("change", handler); + return () => mql.removeEventListener("change", handler); + }, [query]); + + return isMobile; +}