diff --git a/TECHNICAL_DOCUMENTATION.md b/TECHNICAL_DOCUMENTATION.md index 47bcd77..09207ca 100644 --- a/TECHNICAL_DOCUMENTATION.md +++ b/TECHNICAL_DOCUMENTATION.md @@ -30,7 +30,6 @@ src/ │ ├── Navbar.tsx │ ├── Pagination.tsx │ ├── QuoteDisplay.tsx -│ ├── Routes.tsx │ ├── ShareButtons.tsx │ └── theme-provider.tsx │ @@ -38,22 +37,21 @@ src/ │ ├── blog-posts.ts # Character blog content │ ├── quotes.ts # Austen quotes │ ├── quiz.ts # Character quiz data -│ ├── dear-jane.ts # Advice column content │ ├── success-stories.ts │ ├── literary-analysis.ts # Literary analysis data +│ ├── comparative-analysis.ts # Comparative analysis data │ └── vendors.ts │ ├── pages/ │ ├── BlogPost/ -│ ├── Advice.tsx │ ├── Analysis.tsx # Literary analysis page │ ├── Blogs.tsx -│ ├── DearJane.tsx │ ├── Home.tsx │ ├── MarketCalculator.tsx │ ├── Quiz.tsx │ ├── Stories.tsx │ ├── SuccessStories.tsx +│ ├── ComparativeAnalysis.tsx # Comparative analysis page │ └── Vendors.tsx ``` @@ -67,20 +65,22 @@ The application uses React Router for navigation with the following route struct } /> } /> } /> - } /> } /> } /> } /> } /> + } /> + } /> ``` ### Navigation Updates - Main navigation in `Navbar.tsx` includes: - - Primary navigation links - - Literary Analysis link with distinct styling - - Responsive design for all screen sizes + - Primary navigation group with consistent button styling + - Secondary navigation group for analysis features + - Visual separation between groups using border + - All buttons styled with sage color scheme - Home page features: - Literary Analysis card in featured sections - Direct link to analysis content @@ -92,6 +92,8 @@ The application uses React Router for navigation with the following route struct ### Route Integration - Analysis page is directly accessible via `/analysis` +- Comparative analysis accessible via `/comparative` +- Character network visualization via `/network` - Integrated into main navigation flow - Maintains consistent layout and styling - Proper error handling and loading states @@ -123,6 +125,8 @@ The application uses React Router for navigation with the following route struct - Pagination for content lists - Market calculator - **New: Literary analysis with themed tabs and novel selection** +- **New: Comparative analysis for cross-novel exploration** +- **New: Character network visualization** ### 4. Performance Considerations @@ -297,3 +301,157 @@ const novelAnalyses = { 5. Content management system integration 6. **New: Additional novel analysis features** 7. **New: Comparative analysis tools** + +## Character Network Feature + +### Interactive Graph Component + +The character network visualization is implemented using a force-directed graph with the following features: + +```typescript +interface CharacterNode { + id: string; + name: string; + novel: string; + class: string; + type: "protagonist" | "antagonist" | "supporting"; +} + +interface GraphConfig { + nodeRelSize: number; + nodeVal: number; + width: number; + height: number; + staticGraph: boolean; + enableNodeDrag: boolean; + enableZoom: boolean; +} +``` + +#### Key Features + +1. **Static Node Positioning** + + - Nodes are arranged in a circular layout + - Fixed positions prevent unwanted movement + - Calculated using mathematical formulas for even distribution + +2. **Interaction Handling** + + - Single-click node selection + - Debounced click handling (300ms) + - Click processing state management + - Cleanup of timeouts on unmount + +3. **Visual Configuration** + + - Node colors based on character type: + - Protagonist: Green (#4CAF50) + - Antagonist: Red (#f44336) + - Supporting: Blue (#2196F3) + - Node size: 8 units (nodeRelSize) + - Link width: 2 units + - Link opacity: 0.6 + +4. **Performance Optimizations** + + - Disabled force simulation cooldown + - Zero warmup ticks + - Removed hover effects and tooltips + - Static graph configuration + +5. **UI Components** + - Main graph container (600x600px) + - Right-side information panel (300px width) + - Smooth transitions for panel visibility + - Responsive layout with flex container + +### Implementation Details + +```javascript +// Node Click Handler +const handleNodeClick = useCallback( + (node) => { + if (!node || isProcessingClick) return; + setIsProcessingClick(true); + setSelectedNode(node); + onNodeSelect(node); + // Reset after 300ms + clickTimeoutRef.current = setTimeout(() => { + setIsProcessingClick(false); + }, 300); + }, + [onNodeSelect, isProcessingClick] +); + +// Graph Configuration +const graphConfig = { + staticGraph: true, + enableNodeDrag: false, + enableZoom: true, + minZoom: 0.5, + maxZoom: 2.5, + cooldownTime: 0, + warmupTicks: 0, + nodeLabel: null, + enableNodeHover: false, +}; +``` + +### Styling + +The component uses a flex layout with the following structure: + +```css +// Container + { + display: flex; + gap: 20px; + width: 100%; + maxwidth: 1000px; + margin: 0 auto; +} + +// Graph Container + { + width: 600px; + height: 600px; + border: 1px solid #eee; + position: relative; +} + +// Information Panel + { + width: 300px; + padding: 20px; + backgroundcolor: #fff; + border: 1px solid #eee; + borderradius: 4px; + transition: opacity 0.2s ease-in-out; +} +``` + +### Best Practices + +1. **State Management** + + - Use of React hooks for state + - Proper cleanup of timeouts + - Controlled component updates + +2. **Performance** + + - Debounced click handling + - Static node positioning + - Minimal re-renders + +3. **User Experience** + + - Smooth transitions + - Clear visual feedback + - Responsive layout + +4. **Code Organization** + - Modular component structure + - Clear configuration objects + - Type-safe interfaces diff --git a/src/App.tsx b/src/App.tsx index a72c9dd..02dc15d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,7 +12,6 @@ import NetworkVisualization from './pages/NetworkVisualization'; // Lazy load other pages const Quiz = React.lazy(() => import('./pages/Quiz')); -const Advice = React.lazy(() => import('./pages/Advice')); const Vendors = React.lazy(() => import('./pages/Vendors')); const MarketCalculator = React.lazy(() => import('./pages/MarketCalculator')); @@ -27,7 +26,6 @@ function App() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 101c1b0..ccb5d4b 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -11,26 +11,41 @@ const Navbar = () => { Austen's Wedding Guide - - - Character Blogs - - - Bride Quiz - - - Dear Jane - - - Vendors - - - Success Stories - - - Market Value - - + + + + Character Blogs + + + Bride Quiz + + + Vendors + + + Success Stories + + + Market Value + + + + { - return `q-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; -}; - -export const saveUserQuestion = (question: Omit): UserSubmittedQuestion => { - const newQuestion: UserSubmittedQuestion = { - ...question, - id: generateQuestionId(), - date: new Date().toISOString(), - status: 'pending' - }; - - // Get existing questions from localStorage - const existingQuestions = getUserQuestions(); - - // Add new question to the list - const updatedQuestions = [newQuestion, ...existingQuestions]; - - // Save back to localStorage - localStorage.setItem('userQuestions', JSON.stringify(updatedQuestions)); - - return newQuestion; -}; - -export const getUserQuestions = (): UserSubmittedQuestion[] => { - const stored = localStorage.getItem('userQuestions'); - if (!stored) return []; - try { - return JSON.parse(stored); - } catch { - return []; - } -}; diff --git a/src/pages/DearJane.tsx b/src/pages/DearJane.tsx deleted file mode 100644 index 81c4d18..0000000 --- a/src/pages/DearJane.tsx +++ /dev/null @@ -1,290 +0,0 @@ -import { useState, useEffect } from 'react'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Button } from '@/components/ui/button'; -import { ScrollArea } from '@/components/ui/scroll-area'; -import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; -import { Mail, Heart, Book, Users, Home, MessageCircle, PenTool, Clock } from 'lucide-react'; -import { dearJaneLetters, DearJaneLetter, UserSubmittedQuestion, saveUserQuestion, getUserQuestions } from '@/data/dear-jane'; -import { useToast } from '@/components/ui/use-toast'; - -const categoryIcons = { - courtship: , - marriage: , - family: , - society: , - heartbreak: -}; - -const categoryNames = { - courtship: 'Matters of Courtship', - marriage: 'Marriage & Partnership', - family: 'Family Relations', - society: 'Social Etiquette', - heartbreak: 'Healing Hearts' -}; - -const categoryDescriptions = { - courtship: 'Navigate the delicate dance of courtship with Jane\'s guidance on matters of the heart.', - marriage: 'Discover wisdom for maintaining harmony and growth in matrimonial life.', - family: 'Learn to handle family matters with grace, wisdom, and understanding.', - society: 'Master the art of social etiquette while staying true to yourself.', - heartbreak: 'Find comfort and strength in Jane\'s advice for healing a wounded heart.' -}; - -const DearJane = () => { - const { toast } = useToast(); - const [selectedLetter, setSelectedLetter] = useState(dearJaneLetters[0]); - const [selectedCategory, setSelectedCategory] = useState('courtship'); - const [showSubmitForm, setShowSubmitForm] = useState(false); - const [userQuestions, setUserQuestions] = useState([]); - const [formData, setFormData] = useState({ - subject: '', - from: '', - question: '', - category: 'courtship' as DearJaneLetter['category'] - }); - - useEffect(() => { - setUserQuestions(getUserQuestions()); - }, []); - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - - if (!formData.subject || !formData.from || !formData.question) { - toast({ - title: "Missing Information", - description: "Please fill in all fields before submitting.", - variant: "destructive" - }); - return; - } - - try { - const newQuestion = saveUserQuestion(formData); - setUserQuestions(prev => [newQuestion, ...prev]); - setShowSubmitForm(false); - setFormData({ - subject: '', - from: '', - question: '', - category: 'courtship' - }); - toast({ - title: "Question Submitted", - description: "Your question has been submitted successfully. Jane will respond soon!" - }); - } catch (err) { - console.error('Error submitting question:', err); - toast({ - title: "Submission Error", - description: err instanceof Error ? err.message : "There was an error submitting your question. Please try again.", - variant: "destructive" - }); - } - }; - - const allLetters = [...dearJaneLetters, ...userQuestions]; - const filteredLetters = allLetters.filter(letter => letter.category === selectedCategory); - - return ( - - - Dear Jane - - Seeking counsel in matters of the heart? Let Jane Austen's timeless wisdom guide you through your romantic predicaments. - - setShowSubmitForm(!showSubmitForm)} - className="bg-sage-500 hover:bg-sage-600 text-white" - > - - {showSubmitForm ? 'Close Form' : 'Ask Jane for Advice'} - - - - {showSubmitForm && ( - - - Submit Your Question - - Pour your heart out to Jane, and she shall guide you with her timeless wisdom. - - - - - - - Subject - - setFormData(prev => ({ ...prev, subject: e.target.value }))} - className="w-full p-3 rounded-lg border-sage-200 focus:ring-sage-500 focus:border-sage-500 bg-white" - placeholder="e.g., Advice on a Delicate Matter" - required - /> - - - - Your Question - - setFormData(prev => ({ ...prev, question: e.target.value }))} - className="w-full h-32 p-3 rounded-lg border-sage-200 focus:ring-sage-500 focus:border-sage-500 bg-white" - placeholder="Dear Jane..." - required - /> - - - - Sign As - - setFormData(prev => ({ ...prev, from: e.target.value }))} - className="w-full p-3 rounded-lg border-sage-200 focus:ring-sage-500 focus:border-sage-500 bg-white" - placeholder="e.g., Hopelessly Romantic" - required - /> - - - - Category - - setFormData(prev => ({ ...prev, category: e.target.value as DearJaneLetter['category'] }))} - className="w-full p-3 rounded-lg border-sage-200 focus:ring-sage-500 focus:border-sage-500 bg-white" - > - {Object.entries(categoryNames).map(([key, name]) => ( - {name} - ))} - - - - - Send Letter to Jane - - - - - )} - - - - - Browse by Category - - {categoryDescriptions[selectedCategory]} - - - - setSelectedCategory(value as DearJaneLetter['category'])}> - - {Object.entries(categoryNames).map(([key, name]) => ( - - {categoryIcons[key as keyof typeof categoryIcons]} - - {name} - - {filteredLetters.length} letters - - - - ))} - - - - - - {filteredLetters.map((letter) => ( - setSelectedLetter(letter as DearJaneLetter)} - > - - - {letter.subject} - - From: {letter.from} - {'status' in letter && ( - <> - • - - {letter.status === 'pending' ? 'Awaiting Response' : 'Answered'} - > - )} - - - - ))} - - - - - - - - - - {selectedLetter.subject} - - - From: {selectedLetter.from} - • - {new Date(selectedLetter.date).toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric' - })} - - - - - - - {selectedLetter.question} - - - {selectedLetter.answer.map((paragraph, index) => ( - - {paragraph} - - ))} - - Yours truly,Jane Austen - - - - {selectedLetter.relatedBook && ( - - - From {selectedLetter.relatedBook.title} - - - "{selectedLetter.relatedBook.quote}" - - - - )} - - - - - - ); -}; - -export default DearJane; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index c713e2c..33e1f9d 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -55,16 +55,6 @@ const Home = () => { - {/* Dear Jane */} - - - Dear Jane - - Seeking counsel? Let Jane's timeless wisdom guide you through modern romance - - - - {/* Vendor Directory */}
- Seeking counsel in matters of the heart? Let Jane Austen's timeless wisdom guide you through your romantic predicaments. -
- Pour your heart out to Jane, and she shall guide you with her timeless wisdom. -
- {categoryDescriptions[selectedCategory]} -
{selectedLetter.question}
- {paragraph} -
- Yours truly,Jane Austen -
- "{selectedLetter.relatedBook.quote}" - -
- Seeking counsel? Let Jane's timeless wisdom guide you through modern romance -