diff --git a/docs/development/CENTRAL.md b/docs/development/CENTRAL.md index 159d27b..5140340 100644 --- a/docs/development/CENTRAL.md +++ b/docs/development/CENTRAL.md @@ -84,3 +84,4 @@ - [Gameplay Magnified View & Timeout](./devlog/2025-12-17-161500_gameplay_magnified_view_and_timeout.md): Completed. Added magnified view with full card details (Oracle text, type, mana) to gameplay and disabled timeout. - [Test Deck Feature](./devlog/2025-12-17-162500_test_deck_feature.md): Completed. Implemented "Test Solo" button in Cube Manager to instantly start a solo game with a randomized deck from generated packs. - [Update Deck Auto-Fill](./devlog/2025-12-17-165500_update_deck_autofill.md): Completed. Updated deck builder "Auto-Fill" to add lands as individual cards to the deck list for easier management. +- [Customizable Deck Builder Layout](./devlog/2025-12-17-170000_customizable_deck_builder.md): Completed. Implemented switchable Vertical (Side-by-Side) and Horizontal (Top-Bottom) layouts, with an integrated, improved Land Station. diff --git a/docs/development/devlog/2025-12-17-170000_customizable_deck_builder.md b/docs/development/devlog/2025-12-17-170000_customizable_deck_builder.md new file mode 100644 index 0000000..6e57b1f --- /dev/null +++ b/docs/development/devlog/2025-12-17-170000_customizable_deck_builder.md @@ -0,0 +1,55 @@ +# Customizable Deck Builder Layout + +## Request +The user wants to customize the Deck Builder interface with the following features: +1. **Layout Modes**: + * **Vertical View (Default)**: The current 3-column layout ([Zoom] | [Pool] | [Deck + Lands]). + * **Horizontal View**: A new layout where Pool is above the Deck. Land Station should be to the left of the Card Pool in this mode. +2. **Land Station Updates**: + * Remove "(Unlimited)" text. + * Increase container height. + * Integrate proper Land Advisor into the Land Station container to save space. + +## Design +### New Layout State +- State: `layout: 'vertical' | 'horizontal'` +- Toggle: A button group or switch to change layouts. + +### Component Structure +I will extract the core sections into render functions or variables to move them around easily. +- `renderZoomSidebar()` +- `renderPool()` +- `renderDeck()` +- `renderLandStation()` (This will now include the Land Advisor inside it) + +### Horizontal Layout Grid +Structure: +``` +[Zoom Sidebar (Fixed Left)] | [Main Content (Flex Column)] + | + |-- [Top Row (Flex Row)] + | |-- [Land Station (width fixed or flex)] + | |-- [Pool (Flex 1)] + | + |-- [Bottom Row (Flex 1)] + |-- [Deck] +``` + +### Vertical Layout Grid (Current) +Structure: +``` +[Zoom Sidebar] | [Pool] | [Deck + Land Station] +``` +*Note: In current layout, Land Station is stacked vertically with Deck in the 3rd column. User is fine with "exactly how is it now" for vertical.* + +### Land Station Refactoring +- Combine `Advice Panel` and `Land Station` div. +- Remove `(Unlimited)`. +- Increase height (e.g., `h-64` or `min-h-[200px]`). + +## Implementation Steps +1. Read `DeckBuilderView.tsx` (already read). +2. Refactor to extract render helper functions for clear modularity. +3. Add `layout` state and toggle UI. +4. Implement CSS grids/flex layouts for both modes. +5. Modify Land Station to include Advisor and styling updates. diff --git a/src/client/src/modules/draft/DeckBuilderView.tsx b/src/client/src/modules/draft/DeckBuilderView.tsx index 2410bbc..e61e02d 100644 --- a/src/client/src/modules/draft/DeckBuilderView.tsx +++ b/src/client/src/modules/draft/DeckBuilderView.tsx @@ -1,7 +1,6 @@ - import React, { useState } from 'react'; import { socketService } from '../../services/SocketService'; -import { Save, Layers, Clock } from 'lucide-react'; +import { Save, Layers, Clock, Columns, LayoutTemplate } from 'lucide-react'; interface DeckBuilderViewProps { roomId: string; @@ -13,21 +12,12 @@ interface DeckBuilderViewProps { export const DeckBuilderView: React.FC = ({ initialPool, availableBasicLands = [] }) => { // Unlimited Timer (Static for now) const [timer] = useState("Unlimited"); + const [layout, setLayout] = useState<'vertical' | 'horizontal'>('vertical'); const [pool, setPool] = useState(initialPool); const [deck, setDeck] = useState([]); const [lands, setLands] = useState({ Plains: 0, Island: 0, Swamp: 0, Mountain: 0, Forest: 0 }); const [hoveredCard, setHoveredCard] = useState(null); - /* - // Disable timer countdown - useEffect(() => { - const interval = setInterval(() => { - setTimer(t => t > 0 ? t - 1 : 0); - }, 1000); - return () => clearInterval(interval); - }, []); - */ - // --- Land Advice Logic --- const landSuggestion = React.useMemo(() => { const targetLands = 17; @@ -56,8 +46,6 @@ export const DeckBuilderView: React.FC = ({ initialPool, a totalPips = Object.values(pips).reduce((a, b) => a + b, 0); - // If no colored pips (artifacts only?), suggest even split or just return 0s? - // Let's assume proportional to 1 if 0 to avoid div by zero. if (totalPips === 0) return null; // Distribute @@ -181,9 +169,190 @@ export const DeckBuilderView: React.FC = ({ initialPool, a return [...(availableBasicLands || [])].sort((a, b) => a.name.localeCompare(b.name)); }, [availableBasicLands]); + // --- Sub Actions --- + const renderAdvisorContent = () => { + if (!landSuggestion) return Add colored spells to get advice.; + + return ( +
+
+ {(Object.entries(landSuggestion) as [string, number][]).map(([type, count]) => { + if (count === 0) return null; + let colorClass = "text-slate-300"; + if (type === 'Plains') colorClass = "text-amber-200"; + if (type === 'Island') colorClass = "text-blue-200"; + if (type === 'Swamp') colorClass = "text-purple-200"; + if (type === 'Mountain') colorClass = "text-red-200"; + if (type === 'Forest') colorClass = "text-emerald-200"; + + return ( +
+ {type.substring(0, 1)}: + {count} +
+ ) + })} +
+ +
+ ); + } + + // --- Render Sections --- + const renderLandStation = () => ( +
+
+
+

Land Station

+
+ + {/* Integrated Advisor */} +
+ + Land Advisor (Target: 17) + + {renderAdvisorContent()} +
+
+ +
+ {availableBasicLands && availableBasicLands.length > 0 ? ( +
+ {/* Note: horizontal layout gets grid-cols-2 for vertical scrolling list feeling, vertical layout gets side-scrolling or wrapped */} +
+ {sortedLands.map((land) => ( +
addLandToDeck(land)} + onMouseEnter={() => setHoveredCard(land)} + onMouseLeave={() => setHoveredCard(null)} + > + {land.name} +
+ + +
+
+ ))} +
+
+ ) : ( + // Fallback counter UI +
+ {Object.keys(lands).map(type => ( +
+
+
+ {type[0]} +
+
+
+ + {lands[type as keyof typeof lands]} + +
+
+ ))} +
+ )} +
+
+ ); + + const renderPool = () => ( + <> +
+

Card Pool ({pool.length})

+
+
+
+ {pool.map((card) => ( + addToDeck(card)} + onMouseEnter={() => setHoveredCard(card)} + onMouseLeave={() => setHoveredCard(null)} + title={card.name} + /> + ))} +
+
+ + ); + + const renderDeck = () => ( + <> +
+

Your Deck ({deck.length + Object.values(lands).reduce((a, b) => a + b, 0)})

+
+
+ {formatTime(timer)} +
+ +
+
+ +
+
+ {deck.map((card) => ( + removeFromDeck(card)} + onMouseEnter={() => setHoveredCard(card)} + onMouseLeave={() => setHoveredCard(null)} + title={card.name} + /> + ))} +
+
+ + ); + return ( -
- {/* Column 1: Zoom Sidebar */} +
+ {/* View Switcher - Absolute Positioned */} +
+ + +
+ + {/* Column 1: Zoom Sidebar (Always visible) */}
{hoveredCard ? (
@@ -212,156 +381,41 @@ export const DeckBuilderView: React.FC = ({ initialPool, a )}
- {/* Column 2: Pool */} -
-
-

Card Pool ({pool.length})

-
-
-
- {pool.map((card) => ( - addToDeck(card)} - onMouseEnter={() => setHoveredCard(card)} - onMouseLeave={() => setHoveredCard(null)} - title={card.name} - /> - ))} + {/* Main Content Area */} + {layout === 'vertical' ? ( + <> + {/* Vertical: Column 2 (Pool) */} +
+ {renderPool()}
-
-
- - {/* Column 3: Deck & Lands */} -
-
-

Your Deck ({deck.length + Object.values(lands).reduce((a, b) => a + b, 0)})

-
-
- {formatTime(timer)} + {/* Vertical: Column 3 (Deck & Lands) */} +
+ {renderDeck()} +
+ {renderLandStation()}
-
-
- - {/* Deck View */} -
-
- {deck.map((card) => ( - removeFromDeck(card)} - onMouseEnter={() => setHoveredCard(card)} - onMouseLeave={() => setHoveredCard(null)} - title={card.name} - /> - ))} -
-
- -
- - {/* Advice Panel */} -
-
- - Land Advisor (Target: 17) - -
- Based on your deck's mana symbols. -
+ + ) : ( + /* Horizontal Layout */ +
+ {/* Top Row: Lands + Pool */} +
+ {/* Land Station (Left of Pool) */} +
+ {renderLandStation()} +
+ {/* Pool */} +
+ {renderPool()}
- {landSuggestion ? ( -
-
- {(Object.entries(landSuggestion) as [string, number][]).map(([type, count]) => { - if (count === 0) return null; - let colorClass = "text-slate-300"; - if (type === 'Plains') colorClass = "text-amber-200"; - if (type === 'Island') colorClass = "text-blue-200"; - if (type === 'Swamp') colorClass = "text-purple-200"; - if (type === 'Mountain') colorClass = "text-red-200"; - if (type === 'Forest') colorClass = "text-emerald-200"; - - return ( -
- {type.substring(0, 1)}: - {count} -
- ) - })} -
- -
- ) : ( - Add colored spells to get advice. - )}
- - {/* Land Station */} -
-

Land Station (Unlimited)

- - {availableBasicLands && availableBasicLands.length > 0 ? ( -
- {sortedLands.map((land) => ( -
addLandToDeck(land)} - onMouseEnter={() => setHoveredCard(land)} - onMouseLeave={() => setHoveredCard(null)} - > - {land.name} -
- + Add -
-
- ))} -
- ) : ( -
- {Object.keys(lands).map(type => ( -
-
- {type[0]} -
-
- - {lands[type as keyof typeof lands]} - -
-
- ))} -
- )} + {/* Bottom Row: Deck */} +
+ {renderDeck()}
-
+ )}
); };