diff --git a/src/components/Analysis/AnalysisSidebar.tsx b/src/components/Analysis/AnalysisSidebar.tsx new file mode 100644 index 00000000..e84a54a3 --- /dev/null +++ b/src/components/Analysis/AnalysisSidebar.tsx @@ -0,0 +1,351 @@ +import { + MoveMap, + Highlight, + BlunderMeter, + MovesByRating, +} from 'src/components/Analysis' +import { motion } from 'framer-motion' +import type { DrawShape } from 'chessground/draw' +import { Dispatch, SetStateAction, useCallback, useMemo } from 'react' +import { useAnalysisController } from 'src/hooks/useAnalysisController' +import type { MaiaEvaluation, StockfishEvaluation } from 'src/types' + +interface Props { + hover: (move?: string) => void + makeMove: (move: string) => void + setHoverArrow: Dispatch> + analysisEnabled: boolean + controller: ReturnType + handleToggleAnalysis: () => void + itemVariants?: { + hidden: { + opacity: number + y: number + } + visible: { + opacity: number + y: number + transition: { + duration: number + ease: number[] + type: string + } + } + exit: { + opacity: number + y: number + transition: { + duration: number + ease: number[] + type: string + } + } + } +} + +export const AnalysisSidebar: React.FC = ({ + hover, + makeMove, + controller, + setHoverArrow, + analysisEnabled, + handleToggleAnalysis, + itemVariants, +}) => { + // Mock handlers for when analysis is disabled + const emptyBlunderMeterData = useMemo( + () => ({ + goodMoves: { moves: [], probability: 0 }, + okMoves: { moves: [], probability: 0 }, + blunderMoves: { moves: [], probability: 0 }, + }), + [], + ) + + const emptyRecommendations = useMemo( + () => ({ + maia: undefined, + stockfish: undefined, + }), + [], + ) + const mockHover = useCallback(() => void 0, []) + const mockSetHoverArrow = useCallback(() => void 0, []) + const mockMakeMove = useCallback(() => void 0, []) + + return ( + + {/* Analysis Toggle Bar */} +
+
+ analytics +

Analysis

+
+ +
+ + {/* Large screens : 2-row layout */} +
+ {/* Combined Highlight + MovesByRating container */} +
+
+
+ +
+
+ +
+
+ {!analysisEnabled && ( +
+
+ + lock + +

Analysis Disabled

+

+ Enable analysis to see move evaluations +

+
+
+ )} +
+ + {/* MoveMap + BlunderMeter container */} +
+
+ +
+ + {!analysisEnabled && ( +
+
+ + lock + +

Analysis Disabled

+

+ Enable analysis to see position evaluation +

+
+
+ )} +
+
+ + {/* Smaller screens: 3-row layout */} +
+ {/* Row 1: Combined Highlight + BlunderMeter container */} +
+
+ +
+
+
+ +
+
+ {!analysisEnabled && ( +
+
+ + lock + +

Analysis Disabled

+

+ Enable analysis to see move evaluations +

+
+
+ )} +
+ + {/* Row 2: MoveMap */} +
+
+ +
+ {!analysisEnabled && ( +
+
+ + lock + +

Analysis Disabled

+

+ Enable analysis to see position evaluation +

+
+
+ )} +
+ + {/* Row 3: MovesByRating */} +
+
+ +
+ {!analysisEnabled && ( +
+
+ + lock + +

Analysis Disabled

+

+ Enable analysis to see move evaluations +

+
+
+ )} +
+
+
+ ) +} diff --git a/src/components/Analysis/MoveMap.tsx b/src/components/Analysis/MoveMap.tsx index 8ccba4c1..27b23b83 100644 --- a/src/components/Analysis/MoveMap.tsx +++ b/src/components/Analysis/MoveMap.tsx @@ -7,6 +7,7 @@ import { ScatterChart, CartesianGrid, ResponsiveContainer, + ZAxis, } from 'recharts' import { useContext, useState, useEffect } from 'react' import { ColorSanMapping } from 'src/types' @@ -352,24 +353,18 @@ export const MoveMap: React.FC = ({ dy={getLikelyLabelDy()} /> - {moveMap?.map((entry, index) => { - // Set minimum opacity to 0.5 to ensure visibility - const opacity = Math.max(entry.opacity ?? 1, 0.5) - const size = entry.size ?? (isMobile ? 8 : 10) - const baseColor = colorSanMapping[entry.move]?.color ?? '#fff' - const fillColor = baseColor.startsWith('#') - ? hexToRgba(baseColor, opacity) - : baseColor + + + {moveMap?.map((entry, index) => { + const opacity = Math.max(entry.opacity ?? 1, 0.5) + const baseColor = colorSanMapping[entry.move]?.color ?? '#fff' + const fillColor = baseColor.startsWith('#') + ? hexToRgba(baseColor, opacity) + : baseColor - return ( - + return ( onMouseEnter(entry.move, entry, event) @@ -380,9 +375,9 @@ export const MoveMap: React.FC = ({ } style={{ cursor: 'pointer' }} /> - - ) - })} + ) + })} + diff --git a/src/components/Analysis/index.ts b/src/components/Analysis/index.ts index 190cc8c1..4f68a750 100644 --- a/src/components/Analysis/index.ts +++ b/src/components/Analysis/index.ts @@ -11,3 +11,4 @@ export * from './AnalysisConfigModal' export * from './AnalysisNotification' export * from './AnalysisOverlay' export * from './InteractiveDescription' +export * from './AnalysisSidebar' diff --git a/src/components/Common/Header.tsx b/src/components/Common/Header.tsx index 906207a7..ed33cf81 100644 --- a/src/components/Common/Header.tsx +++ b/src/components/Common/Header.tsx @@ -6,12 +6,16 @@ import { PlayType } from 'src/types' import { useRouter } from 'next/router' import { DiscordIcon } from './Icons' import { useCallback, useContext, useEffect, useState } from 'react' +import { motion, AnimatePresence } from 'framer-motion' import { AuthContext, ModalContext, WindowSizeContext } from 'src/contexts' import { LeaderboardNavBadge } from '../Leaderboard/LeaderboardNavBadge' import { useLeaderboardStatus } from 'src/hooks/useLeaderboardStatus' export const Header: React.FC = () => { const [showMenu, setShowMenu] = useState(false) + const [showPlayDropdown, setShowPlayDropdown] = useState(false) + const [showMoreDropdown, setShowMoreDropdown] = useState(false) + const [showProfileDropdown, setShowProfileDropdown] = useState(false) const { isMobile } = useContext(WindowSizeContext) const { user, connectLichess, logout } = useContext(AuthContext) @@ -70,32 +74,54 @@ export const Header: React.FC = () => { }, [showMenu]) const userInfo = user?.lichessId ? ( -
- account_circle -
-

{user?.displayName}

-

View Info

-
-
- - Profile - - - Settings - - -
+
setShowProfileDropdown(true)} + onMouseLeave={() => setShowProfileDropdown(false)} + > + + account_circle + + + {user?.displayName} + + + arrow_drop_down + + + {showProfileDropdown && ( + + + Profile + + + Settings + + + + )} +
) : ( @@ -108,93 +134,136 @@ export const Header: React.FC = () => { Maia Logo

Maia Chess

-
+
setShowPlayDropdown(true)} + onMouseLeave={() => setShowPlayDropdown(false)} > - -
- - - +

PLAY

+ - Play Maia on Lichess -
-
+ arrow_drop_down + + + + {showPlayDropdown && ( + + + + + Play Maia on Lichess + + + )} +
- Analysis + ANALYSIS - Puzzles + PUZZLES - Openings + OPENINGS - Bot-or-Not + BOT-OR-NOT - Leaderboard + LEADERBOARD -
- - + arrow_drop_down + + + + {showMoreDropdown && ( + + + Blog + + + Watch + + + Feedback + + + )} +
diff --git a/src/components/Openings/OpeningDrillAnalysis.tsx b/src/components/Openings/OpeningDrillAnalysis.tsx index 1069d813..0a50f225 100644 --- a/src/components/Openings/OpeningDrillAnalysis.tsx +++ b/src/components/Openings/OpeningDrillAnalysis.tsx @@ -1,5 +1,11 @@ import React, { useMemo, useCallback, useContext } from 'react' -import { Highlight, MoveMap, BlunderMeter, MovesByRating } from '../Analysis' +import { + Highlight, + MoveMap, + BlunderMeter, + MovesByRating, + AnalysisSidebar, +} from '../Analysis' import { GameNode } from 'src/types' import { GameTree } from 'src/types/base/tree' import type { DrawShape } from 'chessground/draw' @@ -246,288 +252,15 @@ export const OpeningDrillAnalysis: React.FC = ({
) - // Desktop layout (for big and small screens) const desktopLayout = ( -
- {/* Analysis Toggle */} -
-
- analytics -

Analysis

-
- -
- - {/* Large screens (xl+): Side by side layout */} -
-
- {/* Combined Highlight + MovesByRating container */} -
-
-
- -
-
-
- -
- {!analysisEnabled && ( -
-
- - lock - -

Analysis Disabled

-

- Enable analysis to see move evaluations -

-
-
- )} -
-
-
-
- -
- - {!analysisEnabled && ( -
-
- - lock - -

Analysis Disabled

-

- Enable analysis to see position evaluation -

-
-
- )} -
-
- - {/* Small screens (below xl, above mobile): 3-row stacked layout */} -
- {/* Row 1: Combined Highlight + BlunderMeter container */} -
-
- -
-
-
- -
-
- {!analysisEnabled && ( -
-
- - lock - -

Analysis Disabled

-

- Enable analysis to see move evaluations -

-
-
- )} -
- - {/* Row 2: MoveMap */} -
-
- -
- {!analysisEnabled && ( -
-
- - lock - -

Analysis Disabled

-

- Enable analysis to see position evaluation -

-
-
- )} -
- - {/* Row 3: MovesByRating */} -
-
- -
- {!analysisEnabled && ( -
-
- - lock - -

Analysis Disabled

-

- Enable analysis to see move evaluations -

-
-
- )} -
-
-
+ ) return isMobile ? mobileLayout : desktopLayout diff --git a/src/hooks/useAnalysisController/useMoveRecommendations.ts b/src/hooks/useAnalysisController/useMoveRecommendations.ts index 1be4cc2f..657447b5 100644 --- a/src/hooks/useAnalysisController/useMoveRecommendations.ts +++ b/src/hooks/useAnalysisController/useMoveRecommendations.ts @@ -149,8 +149,7 @@ export const useMoveRecommendations = ( const importance = normalizedCp + normalizedProb // Calculate dynamic size based on importance (bigger = more important) - // Increased size range from 4-12 to 6-16 for better visibility - const size = Math.max(6, Math.min(16, 6 + importance * 6)) // 6-16px range + const size = 10 // Get additional data for comprehensive tooltip const rawCp = moveEvaluation.stockfish.cp_vec[move] || 0 @@ -161,6 +160,7 @@ export const useMoveRecommendations = ( move, x: cp, y: prob, + z: 100, opacity, importance, size, diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 767a98da..7716b838 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -39,9 +39,12 @@ const OpenSans = Open_Sans({ subsets: ['latin'] }) function MaiaPlatform({ Component, pageProps }: AppProps) { const router = useRouter() const isAnalysisPage = router.pathname.startsWith('/analysis') - const isPageWithAnalysis = ['/analysis', '/openings', '/puzzles'].some( - (path) => router.pathname.includes(path), - ) + const isPageWithAnalysis = [ + '/analysis', + '/openings', + '/puzzles', + '/settings', + ].some((path) => router.pathname.includes(path)) useEffect(() => { posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY as string, { diff --git a/src/pages/analysis/[...id].tsx b/src/pages/analysis/[...id].tsx index 8767c829..d81f911a 100644 --- a/src/pages/analysis/[...id].tsx +++ b/src/pages/analysis/[...id].tsx @@ -28,6 +28,7 @@ import { AuthenticatedWrapper } from 'src/components/Common/AuthenticatedWrapper import { PlayerInfo } from 'src/components/Common/PlayerInfo' import { MoveMap } from 'src/components/Analysis/MoveMap' import { Highlight } from 'src/components/Analysis/Highlight' +import { AnalysisSidebar } from 'src/components/Analysis' import { BlunderMeter } from 'src/components/Analysis/BlunderMeter' import { MovesByRating } from 'src/components/Analysis/MovesByRating' import { AnalysisGameList } from 'src/components/Analysis/AnalysisGameList' @@ -658,17 +659,17 @@ const Analysis: React.FC = ({ const desktopLayout = ( -
+
@@ -723,7 +724,7 @@ const Analysis: React.FC = ({
@@ -742,7 +743,7 @@ const Analysis: React.FC = ({ color={controller.orientation === 'white' ? 'black' : 'white'} termination={analyzedGame.termination.winner} /> -
+
= ({ isAnalysisInProgress={controller.gameAnalysis.progress.isAnalyzing} /> - - {/* Analysis Toggle Bar */} -
-
- - analytics - -

Analysis

-
- -
- - {/* Large screens (xl+): Side by side layout */} -
-
- {/* Combined Highlight + MovesByRating container */} -
-
- -
-
- -
-
- {!analysisEnabled && ( -
-
- - lock - -

- Analysis Disabled -

-

- Enable analysis to see move evaluations -

-
-
- )} -
-
-
- -
- - {!analysisEnabled && ( -
-
- - lock - -

- Analysis Disabled -

-

- Enable analysis to see position evaluation -

-
-
- )} -
-
- - {/* Smaller screens (below xl): 3-row stacked layout */} -
- {/* Row 1: Combined Highlight + BlunderMeter container */} -
-
- -
-
-
- -
-
- {!analysisEnabled && ( -
-
- - lock - -

- Analysis Disabled -

-

- Enable analysis to see move evaluations -

-
-
- )} -
- - {/* Row 2: MoveMap */} -
-
- -
- {!analysisEnabled && ( -
-
- - lock - -

- Analysis Disabled -

-

- Enable analysis to see position evaluation -

-
-
- )} -
- - {/* Row 3: MovesByRating */} -
-
- -
- {!analysisEnabled && ( -
-
- - lock - -

- Analysis Disabled -

-

- Enable analysis to see move evaluations -

-
-
- )} -
-
-
+
) diff --git a/src/pages/openings/index.tsx b/src/pages/openings/index.tsx index 86969a59..ee269d33 100644 --- a/src/pages/openings/index.tsx +++ b/src/pages/openings/index.tsx @@ -683,10 +683,10 @@ const OpeningsPage: NextPage = () => { } const desktopLayout = () => ( -
-
+
+
{/* Left Sidebar */} -
+
{
{/* Center - Board */} -
+
-
+
{ {/* Right Panel - Analysis */}
= ({ const desktopLayout = ( -
+
@@ -707,13 +708,13 @@ const Train: React.FC = ({
= ({ />
- - {/* Analysis Toggle Bar - only show when puzzle is complete */} - {showAnalysis && ( -
-
- - analytics - -

Analysis

-
- -
- )} - -
- {/* Large screens (xl+): Side by side layout */} -
-
-
-
- void 0 - } - hover={ - analysisEnabled && showAnalysis ? hover : mockHover - } - makeMove={ - analysisEnabled && showAnalysis - ? makeMove - : mockMakeMove - } - currentMaiaModel={ - analysisEnabled && showAnalysis - ? analysisController.currentMaiaModel - : 'maia_kdd_1500' - } - recommendations={ - analysisEnabled && showAnalysis - ? analysisController.moveRecommendations - : emptyRecommendations - } - moveEvaluation={ - analysisEnabled && showAnalysis - ? (analysisController.moveEvaluation as { - maia?: MaiaEvaluation - stockfish?: StockfishEvaluation - }) - : { - maia: undefined, - stockfish: undefined, - } - } - colorSanMapping={ - analysisEnabled && showAnalysis - ? analysisController.colorSanMapping - : {} - } - boardDescription={ - analysisEnabled && showAnalysis - ? analysisController.boardDescription - : { - segments: [ - { - type: 'text', - content: - 'Complete the puzzle to unlock analysis, or analysis is disabled.', - }, - ], - } - } - /> - {!analysisEnabled && showAnalysis && ( -
-
- - lock - -

- Analysis Disabled -

-

- Enable analysis to see detailed evaluations -

-
-
- )} - {!showAnalysis && ( -
-
- - lock - -

- Analysis Locked -

-

- Complete the puzzle to unlock analysis -

-
-
- )} -
-
-
- -
-
-
- - {/* Smaller screens (below xl): Combined Highlight + BlunderMeter container */} -
-
- void 0 - } - hover={analysisEnabled && showAnalysis ? hover : mockHover} - makeMove={ - analysisEnabled && showAnalysis ? makeMove : mockMakeMove - } - currentMaiaModel={ - analysisEnabled && showAnalysis - ? analysisController.currentMaiaModel - : 'maia_kdd_1500' - } - recommendations={ - analysisEnabled && showAnalysis - ? analysisController.moveRecommendations - : emptyRecommendations - } - moveEvaluation={ - analysisEnabled && showAnalysis - ? (analysisController.moveEvaluation as { - maia?: MaiaEvaluation - stockfish?: StockfishEvaluation - }) - : { - maia: undefined, - stockfish: undefined, - } - } - colorSanMapping={ - analysisEnabled && showAnalysis - ? analysisController.colorSanMapping - : {} - } - boardDescription={ - analysisEnabled && showAnalysis - ? analysisController.boardDescription - : { - segments: [ - { - type: 'text', - content: - 'Complete the puzzle to unlock analysis, or analysis is disabled.', - }, - ], - } - } - /> -
-
-
- -
-
-
- - {!analysisEnabled && showAnalysis && ( -
-
- - lock - -

Analysis Disabled

-

- Enable analysis to see detailed evaluations -

-
-
- )} - {!showAnalysis && ( -
-
- - lock - -

Analysis Locked

-

- Complete the puzzle to unlock analysis -

-
-
- )} -
- -
- {/* Large screens (xl+): Side by side layout */} -
-
- -
- -
- - {!analysisEnabled && showAnalysis && ( -
-
- - lock - -

Analysis Disabled

-

- Enable analysis to see detailed evaluations -

-
-
- )} - {!showAnalysis && ( -
-
- - lock - -

Analysis Locked

-

- Complete the puzzle to unlock analysis -

-
-
- )} - - {/* Smaller screens (below xl): MoveMap full width */} -
-
- -
-
- - {!analysisEnabled && showAnalysis && ( -
-
- - lock - -

Analysis Disabled

-

- Enable analysis to see detailed evaluations -

-
-
- )} - {!showAnalysis && ( -
-
- - lock - -

Analysis Locked

-

- Complete the puzzle to unlock analysis -

-
-
- )} -
- - {/* Smaller screens (below xl): MovesByRating full width */} -
-
- - {!analysisEnabled && showAnalysis && ( -
-
- - lock - -

- Analysis Disabled -

-

- Enable analysis to see detailed evaluations -

-
-
- )} - {!showAnalysis && ( -
-
- - lock - -

Analysis Locked

-

- Complete the puzzle to unlock analysis -

-
-
- )} -
-
-
+
) diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css index eef4418a..9a5c14c9 100644 --- a/src/styles/tailwind.css +++ b/src/styles/tailwind.css @@ -94,6 +94,57 @@ svg { fill: rgb(var(--color-text-primary)); } +.desktop-left-column-container { + width: 16vw; + min-width: max(14rem, 16vw); + height: 85vh; +} + +.desktop-middle-column-container { + width: 28vw; + min-width: calc(max(20rem, min(28vw, 50vh))); + height: 85vh; +} + +.desktop-board-container { + min-width: calc(max(20rem, min(28vw, 50vh))); + min-height: calc(max(20rem, min(28vw, 50vh))); + width: min(28vw, 50vh); + height: min(28vw, 50vh); +} + +.desktop-right-column-container { + width: 100%; + min-width: 40vw; + height: 85vh; +} + +@media (min-width: 1280px) { + .desktop-right-column-container { + height: 85vh; + } +} + +.desktop-analysis-big-row-1-container { + height: calc((85vh - 2.5rem - 1rem) / 2); +} + +.desktop-analysis-big-row-2-container { + height: calc((85vh - 2.5rem - 1rem) / 2); +} + +.desktop-analysis-small-row-1-container { + height: calc((85vh - 2.5rem - 1.5rem) * 0.4); +} + +.desktop-analysis-small-row-2-container { + height: calc((85vh - 2.5rem - 1.5rem) * 0.3); +} + +.desktop-analysis-small-row-3-container { + height: calc((85vh - 2.5rem - 1.5rem) * 0.3); +} + .no-scrollbar::-webkit-scrollbar { display: none; }