From 59657ce287f7da547aeed5d7d36f081e207341d7 Mon Sep 17 00:00:00 2001 From: rathi Date: Wed, 4 Dec 2024 14:40:29 -0500 Subject: [PATCH] updated interactive timeline and added social class view --- TECHNICAL_DOCUMENTATION.md | 351 +++++++ src/App.tsx | 4 + src/components/Navbar.tsx | 12 + src/components/SocialClassView.tsx | 253 +++++ .../timeline/InteractiveTimeline.tsx | 894 ++++++++++++++++++ src/pages/SocialClass.tsx | 194 ++++ src/pages/Timeline.tsx | 322 +++++++ src/types/timeline.ts | 33 + 8 files changed, 2063 insertions(+) create mode 100644 src/components/SocialClassView.tsx create mode 100644 src/components/timeline/InteractiveTimeline.tsx create mode 100644 src/pages/SocialClass.tsx create mode 100644 src/pages/Timeline.tsx create mode 100644 src/types/timeline.ts diff --git a/TECHNICAL_DOCUMENTATION.md b/TECHNICAL_DOCUMENTATION.md index 09207ca..a93868a 100644 --- a/TECHNICAL_DOCUMENTATION.md +++ b/TECHNICAL_DOCUMENTATION.md @@ -21,6 +21,9 @@ src/ │ ├── ui/ # Shadcn UI components │ ├── layout/ # Layout components │ ├── vendor/ # Vendor-specific components +│ ├── timeline/ # Timeline components +│ │ ├── InteractiveTimeline.tsx +│ │ └── SocialClassView.tsx │ ├── BlogPost.tsx # Blog post component │ ├── CommentSection.tsx │ ├── ErrorBoundary.tsx @@ -124,6 +127,7 @@ The application uses React Router for navigation with the following route struct - Share functionality - Pagination for content lists - Market calculator +- **New: Interactive timeline with multiple views** - **New: Literary analysis with themed tabs and novel selection** - **New: Comparative analysis for cross-novel exploration** - **New: Character network visualization** @@ -455,3 +459,350 @@ The component uses a flex layout with the following structure: - Modular component structure - Clear configuration objects - Type-safe interfaces + +## Timeline Feature + +### Interactive Timeline Component + +The timeline visualization is implemented with a modern, flowing layout that displays events chronologically with the following features: + +```typescript +interface TimelineEvent { + year: number; + type: "works" | "context" | "legacy" | "adaptations"; + title: string; + description: string; + novel?: string; + significance?: string; +} + +interface Props { + events: TimelineEvent[]; +} +``` + +#### Key Features + +1. **Event Display** + + - Vertical flowing layout with connected events + - Events positioned horizontally based on year + - Clean card design with type-based color coding + - Connected events with vertical lines + +2. **Time Scale** + + - Sticky year markers at the top + - Automatic year range calculation + - Visual period indicators + - Smooth scrolling behavior + +3. **Event Types** + + - Works: Publications and writings + - Context: Historical events + - Legacy: Impact and influence + - Adaptations: Modern interpretations + +4. **Visual Design** + + - Clean, modern card layout + - Type-based color coding: + - Works: Blue (#2196F3) + - Context: Purple (#9C27B0) + - Legacy: Green (#4CAF50) + - Subtle shadows and transitions + - Responsive container sizing + +5. **Interaction** + - Smooth horizontal scrolling + - Event selection with visual feedback + - Automatic scrolling to selected events + - Hover effects on cards and dots + +### Implementation Details + +```typescript +// Color Scheme +const getEventColor = (type: string) => { + switch (type) { + case "works": + return { background: "#E3F2FD", border: "#2196F3" }; + case "context": + return { background: "#F3E5F5", border: "#9C27B0" }; + case "legacy": + return { background: "#E8F5E9", border: "#4CAF50" }; + default: + return { background: "#FAFAFA", border: "#9E9E9E" }; + } +}; + +// Event Click Handler +const handleEventClick = (event: TimelineEvent) => { + setSelectedEvent(event); + // Scroll event into view with smooth animation + if (timelineRef.current) { + const position = ((event.year - actualMinYear) / timeSpan) * 100; + const timelineWidth = timelineRef.current.clientWidth; + const totalWidth = timelineRef.current.scrollWidth; + const scrollPosition = (position / 100) * totalWidth - timelineWidth / 2; + + timelineRef.current.scrollTo({ + left: scrollPosition, + behavior: "smooth", + }); + } +}; +``` + +### Styling + +The timeline uses a combination of Material-UI and custom styling: + +```typescript +// Container Styling +{ + position: 'relative', + height: 600, + bgcolor: '#FFFFFF', + borderRadius: 2, + boxShadow: '0 2px 4px rgba(0,0,0,0.1)', + overflow: 'hidden' +} + +// Event Card Styling +{ + p: 2, + minWidth: 240, + maxWidth: 320, + borderLeft: `3px solid ${colors.border}`, + borderRadius: '4px', + transition: 'all 0.2s ease', + bgcolor: isSelected ? colors.background : '#FFFFFF', + boxShadow: '0 2px 4px rgba(0,0,0,0.05)' +} +``` + +### Best Practices + +1. **Performance** + + - Efficient event positioning + - Smooth scrolling behavior + - Optimized re-renders + - Proper cleanup of event listeners + +2. **User Experience** + + - Clear visual hierarchy + - Smooth transitions + - Intuitive navigation + - Responsive design + +3. **Code Organization** + + - Modular component structure + - Clear type definitions + - Reusable styling + - Consistent naming conventions + +4. **Accessibility** + - Keyboard navigation support + - ARIA labels for interactive elements + - Color contrast compliance + - Focus management + +## Social Class Feature + +### Social Class Analysis Component + +The social class analysis feature provides an in-depth examination of class dynamics in Austen's works with the following structure: + +```typescript +interface SocialClass { + name: string; + description: string; + incomeRange: string; + modernEquivalent: string; + characteristics: string[]; + examples: { + character: string; + novel: string; + context: string; + }[]; +} + +interface Character { + id: string; + name: string; + novel: string; + socialClass: "upper" | "middle" | "working"; + occupation?: string; + annualIncome?: string; + modernEquivalent?: string; + description: string; + relationships: string[]; +} +``` + +#### Key Features + +1. **Interactive Social Pyramid** + + - Visual representation of class hierarchy + - Hover tooltips with class information + - Smooth transitions and animations + - Income ranges and modern equivalents + +2. **Character Examples** + + - Grid layout of character cards + - Class-based categorization + - Income and occupation details + - Modern equivalent values + - Character relationships + +3. **Comparative Analysis** + + - Character comparison dialog + - Side-by-side analysis + - Social mobility examination + - Economic context + +4. **Content Organization** + + - Expandable sections by novel + - Detailed character studies + - Modern retellings analysis + - Critical insights + +5. **Visual Design** + - Clean, academic layout + - Consistent typography + - Color-coded class indicators + - Responsive grid system + +### Implementation Details + +```typescript +// Social Class View Structure +const SocialClassView = () => { + const [selectedCharacter, setSelectedCharacter] = useState( + null + ); + const [comparisonCharacter, setComparisonCharacter] = + useState(null); + const [dialogOpen, setDialogOpen] = useState(false); + + // Character comparison handling + const handleCharacterClick = (character: Character) => { + if (!selectedCharacter) { + setSelectedCharacter(character); + } else if (selectedCharacter.id !== character.id) { + setComparisonCharacter(character); + setDialogOpen(true); + } + }; +}; + +// Social Pyramid Styling +const pyramidStyles = { + position: "relative", + height: 300, + "& .pyramid-level": { + position: "absolute", + left: "50%", + transform: "translateX(-50%)", + transition: "all 0.2s", + "&:hover": { + transform: "translateX(-50%) scale(1.02)", + }, + }, +}; +``` + +### Content Structure + +1. **Pride and Prejudice Analysis** + + - Upper Class Examples + - Mr. Darcy (£10,000 per year) + - Lady Catherine de Bourgh + - Middle & Lower Classes + - The Bennet Family + - Servants (via Longbourn) + +2. **Mansfield Park Analysis** + + - The Privileged Circle + - The Bertram Family + - Mary and Henry Crawford + - The Dependent Relations + - Fanny Price + - The Price Family + +3. **Modern Retellings** + - Pride by Ibi Zoboi + - Zuri Benitez + - Darius Darcy + - Longbourn's Legacy + - Servant Perspectives + - Class Intersections + +### Integration + +1. **Routing** + + - Dedicated `/social-class` route + - Integration with main navigation + - Error boundary protection + - Loading state handling + +2. **Navigation** + + - Added to academic section + - Consistent button styling + - Clear visual grouping + - Smooth transitions + +3. **Data Management** + + - TypeScript interfaces + - Structured content + - Modern equivalents + - Character relationships + +4. **UI Components** + - Accordion sections + - Material-UI integration + - Responsive grid layout + - Interactive elements + +### Best Practices + +1. **Content Organization** + + - Clear hierarchical structure + - Detailed character analysis + - Historical context + - Modern relevance + +2. **User Experience** + + - Intuitive navigation + - Interactive comparisons + - Visual hierarchy + - Responsive design + +3. **Performance** + + - Efficient state management + - Optimized rendering + - Smooth transitions + - Proper cleanup + +4. **Accessibility** + - ARIA labels + - Keyboard navigation + - Color contrast + - Focus management diff --git a/src/App.tsx b/src/App.tsx index 02dc15d..fab17e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,8 @@ import SuccessStories from './pages/SuccessStories'; import Analysis from './pages/Analysis'; import ComparativeAnalysis from './pages/ComparativeAnalysis'; import NetworkVisualization from './pages/NetworkVisualization'; +import Timeline from './pages/Timeline'; +import SocialClass from './pages/SocialClass'; // Lazy load other pages const Quiz = React.lazy(() => import('./pages/Quiz')); @@ -32,6 +34,8 @@ function App() { } /> } /> } /> + } /> + } /> Page not found} /> diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index ccb5d4b..8ee2c2d 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -58,6 +58,18 @@ const Navbar = () => { > Character Network + + Timeline + + + Social Class + diff --git a/src/components/SocialClassView.tsx b/src/components/SocialClassView.tsx new file mode 100644 index 0000000..cf02559 --- /dev/null +++ b/src/components/SocialClassView.tsx @@ -0,0 +1,253 @@ +import React, { useState } from 'react'; +import { Box, Typography, Paper, Grid, Tooltip, Card, CardContent, Chip, Dialog, DialogTitle, DialogContent } from '@mui/material'; +import { SocialClass, Character } from '../types/timeline'; + +const socialClasses: SocialClass[] = [ + { + name: 'Upper Class', + description: 'The landed gentry and aristocracy of Regency England', + incomeRange: '£4,000-10,000 per annum', + modernEquivalent: '$200,000-500,000 per year', + characteristics: [ + 'Inherited estates and land', + 'No need to work for income', + 'Expected to maintain country estates', + 'Social obligations to tenants and community' + ], + examples: [ + { + character: 'Mr. Darcy', + novel: 'Pride and Prejudice', + context: 'Owner of Pemberley estate with £10,000 per year' + }, + { + character: 'Sir Thomas Bertram', + novel: 'Mansfield Park', + context: 'Owner of Mansfield Park and plantations in Antigua' + } + ] + }, + { + name: 'Middle Class', + description: 'Professional class including clergy, military officers, and successful merchants', + incomeRange: '£200-1,000 per annum', + modernEquivalent: '$30,000-150,000 per year', + characteristics: [ + 'Professional occupations', + 'Education but no inherited wealth', + 'Aspiring to climb social ladder', + 'Emphasis on manners and propriety' + ], + examples: [ + { + character: 'Mr. Bennet', + novel: 'Pride and Prejudice', + context: 'Country gentleman with £2,000 per year' + }, + { + character: 'Henry Tilney', + novel: 'Northanger Abbey', + context: 'Clergyman with independent means' + } + ] + }, + { + name: 'Working Class', + description: 'Servants, laborers, and small traders', + incomeRange: '£20-100 per annum', + modernEquivalent: '$5,000-20,000 per year', + characteristics: [ + 'Manual labor or service positions', + 'Limited education and opportunities', + 'Dependent on employers', + 'Focus on survival and basic needs' + ], + examples: [ + { + character: 'The Hill Family', + novel: 'Longbourn', + context: 'Servants at the Bennet household' + }, + { + character: 'Hannah', + novel: 'Northanger Abbey', + context: 'Servant at the Tilney household' + } + ] + } +]; + +const characters: Character[] = [ + { + id: 'darcy', + name: 'Mr. Darcy', + novel: 'Pride and Prejudice', + socialClass: 'upper', + occupation: 'Landowner', + annualIncome: '£10,000', + modernEquivalent: '$500,000', + description: 'Wealthy landowner of Pemberley estate', + relationships: ['elizabeth-bennet', 'georgiana-darcy'] + }, + { + id: 'elizabeth', + name: 'Elizabeth Bennet', + novel: 'Pride and Prejudice', + socialClass: 'middle', + occupation: 'Gentleman\'s daughter', + annualIncome: 'Share of £2,000', + modernEquivalent: '$10,000', + description: 'Intelligent and witty second daughter of the Bennet family', + relationships: ['darcy', 'jane-bennet'] + }, + { + id: 'sarah', + name: 'Sarah', + novel: 'Longbourn', + socialClass: 'working', + occupation: 'Housemaid', + annualIncome: '£8', + modernEquivalent: '$2,000', + description: 'Hardworking housemaid at Longbourn', + relationships: ['mrs-hill', 'james-smith'] + } +]; + +export default function SocialClassView() { + const [selectedCharacter, setSelectedCharacter] = useState(null); + const [comparisonCharacter, setComparisonCharacter] = useState(null); + const [dialogOpen, setDialogOpen] = useState(false); + + const handleCharacterClick = (character: Character) => { + if (!selectedCharacter) { + setSelectedCharacter(character); + } else if (selectedCharacter.id !== character.id) { + setComparisonCharacter(character); + setDialogOpen(true); + } + }; + + const handleCloseDialog = () => { + setDialogOpen(false); + setSelectedCharacter(null); + setComparisonCharacter(null); + }; + + return ( + + + Social Class in Austen's Novels + + + {/* Social Pyramid */} + + {socialClasses.map((socialClass, index) => ( + + {socialClass.name} + {socialClass.description} + Income: {socialClass.incomeRange} + + } + arrow + > + + + {socialClass.name} + + + + ))} + + + {/* Character Grid */} + + Character Examples {selectedCharacter && '(Select another character to compare)'} + + + {characters.map((character) => ( + + handleCharacterClick(character)} + sx={{ + cursor: 'pointer', + transition: 'all 0.2s', + '&:hover': { + transform: 'scale(1.02)', + }, + bgcolor: selectedCharacter?.id === character.id ? 'primary.50' : 'background.paper' + }} + > + + + {character.name} + + + {character.novel} + + + + Annual Income: {character.annualIncome} + + + Modern Equivalent: {character.modernEquivalent} + + + + + ))} + + + {/* Comparison Dialog */} + + Character Comparison + + {selectedCharacter && comparisonCharacter && ( + + + {selectedCharacter.name} + Novel: {selectedCharacter.novel} + Class: {selectedCharacter.socialClass} + Income: {selectedCharacter.annualIncome} + Modern: {selectedCharacter.modernEquivalent} + {selectedCharacter.description} + + + {comparisonCharacter.name} + Novel: {comparisonCharacter.novel} + Class: {comparisonCharacter.socialClass} + Income: {comparisonCharacter.annualIncome} + Modern: {comparisonCharacter.modernEquivalent} + {comparisonCharacter.description} + + + )} + + + + ); +} diff --git a/src/components/timeline/InteractiveTimeline.tsx b/src/components/timeline/InteractiveTimeline.tsx new file mode 100644 index 0000000..ea94d6b --- /dev/null +++ b/src/components/timeline/InteractiveTimeline.tsx @@ -0,0 +1,894 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Box, Typography, Paper, ButtonGroup, Button, IconButton, Divider, Chip } from '@mui/material'; +import { TimelineEvent } from '../../types/timeline'; +import { DragIndicator, ChevronLeft, ChevronRight } from '@mui/icons-material'; +import { alpha } from '@mui/material/styles'; + +interface Props { + events: TimelineEvent[]; +} + +// Define major time periods with matching category colors +const timePeriods = [ + { start: 1775, end: 1785, name: 'Early Life', color: 'primary.50' }, + { start: 1786, end: 1800, name: 'Juvenilia & Early Drafts', color: 'primary.100' }, + { start: 1801, end: 1817, name: 'Publication Years', color: 'primary.200' }, + { start: 1818, end: 1900, name: 'Victorian Reception', color: 'warning.50' }, + { start: 1901, end: 2000, name: '20th Century', color: 'success.50' }, + { start: 2001, end: new Date().getFullYear(), name: 'Contemporary', color: 'success.100' } +]; + +// Timeline data focused on course texts and their context +const timelineEvents: TimelineEvent[] = [ + // Works - Published and Unpublished + { + year: 1787, + type: 'works', + title: 'Love and Freindship', + description: "Early epistolary work from Austen's juvenilia, showing her early satirical style", + significance: 'Demonstrates Austen\'s early critique of sensibility and romantic conventions' + }, + { + year: 1795, + type: 'works', + title: 'Elinor and Marianne (First Draft)', + description: 'Early epistolary version of what would become Sense and Sensibility', + significance: 'Shows development of Austen\'s craft from letters to narrative form' + }, + { + year: 1797, + type: 'works', + title: 'First Impressions (Original P&P)', + description: 'Original version of Pride and Prejudice, rejected by publisher Thomas Cadell', + significance: 'Reveals the evolution of her most famous work' + }, + { + year: 1811, + type: 'works', + title: 'Sense and Sensibility Published', + description: 'Austen\'s first published novel explores the tension between emotional expression and rational restraint through the Dashwood sisters.', + novel: 'sense-and-sensibility', + significance: 'Establishes Austen\'s recurring theme of balancing heart and mind in relationships.' + }, + { + year: 1813, + type: 'works', + title: 'Pride and Prejudice Published', + description: 'A masterful examination of hasty judgments and social prejudices through Elizabeth Bennet\'s journey.', + novel: 'pride-and-prejudice', + significance: 'Critiques the marriage market while exploring personal growth and social mobility.' + }, + { + year: 1814, + type: 'works', + title: 'Mansfield Park Published', + description: 'Through Fanny Price\'s story, Austen examines moral integrity in a materialistic society.', + novel: 'mansfield-park', + significance: 'Presents Austen\'s most direct critique of social corruption and moral decay.' + }, + { + year: 1818, + type: 'works', + title: 'Northanger Abbey Published (Posthumously)', + description: 'A playful parody of Gothic novels that examines the relationship between fiction and reality.', + novel: 'northanger-abbey', + significance: 'Demonstrates Austen\'s literary awareness and critique of reading practices.' + }, + + // Historical and Cultural Context + { + year: 1792, + type: 'context', + title: 'Vindication of the Rights of Woman', + description: 'Mary Wollstonecraft publishes feminist treatise', + significance: 'Contemporary debates about women\'s education and rights that appear in Austen\'s works' + }, + { + year: 1795, + type: 'context', + title: 'Marriage Act Amendment', + description: 'Required separate residence for 6 weeks before marriage, affecting courtship practices.', + significance: 'Provides legal context for marriage plots and elopement concerns in Pride and Prejudice and Mansfield Park.' + }, + { + year: 1799, + type: 'context', + title: 'Income Tax Introduction', + description: 'First British income tax introduced to fund Napoleonic Wars', + significance: 'Economic context for character incomes mentioned in novels' + }, + { + year: 1801, + type: 'context', + title: 'Move to Bath', + description: 'Austen family relocates to Bath, a period of reduced writing', + significance: 'Influenced her portrayal of Bath in Northanger Abbey and other works' + }, + { + year: 1805, + type: 'context', + title: 'Battle of Trafalgar & Death of Rev. Austen', + description: 'Major naval victory and death of Jane\'s father leading to financial uncertainty', + significance: 'Influences on naval themes and women\'s financial dependence in her novels' + }, + + // Legacy and Critical Reception + { + year: 1870, + type: 'legacy', + title: 'Memoir of Jane Austen', + description: 'James Edward Austen-Leigh publishes first major biography', + significance: 'Shaped Victorian and later reception of Austen' + }, + { + year: 2009, + type: 'legacy', + title: 'A Truth Universally Acknowledged', + description: 'Collection of critical essays on why we read Jane Austen', + significance: 'Modern critical perspectives on Austen\'s enduring appeal' + }, + + // Modern Adaptations and Retellings + { + year: 1995, + type: 'adaptations', + title: 'BBC Pride and Prejudice', + description: 'Colin Firth/Jennifer Ehle adaptation', + significance: 'Influential adaptation that sparked renewed interest in Austen' + }, + { + year: 2008, + type: 'adaptations', + title: 'Lost in Austen', + description: 'Time-travel adaptation mixing modern perspective with P&P', + significance: 'Example of creative modern reinterpretation mentioned in course' + }, + { + year: 2013, + type: 'adaptations', + title: 'Longbourn by Jo Baker', + description: 'A retelling of Pride and Prejudice from the servants\' perspective', + significance: 'Offers a "downstairs" perspective on Austen\'s world' + }, + { + year: 2015, + type: 'adaptations', + title: 'Pride by Ibi Zoboi', + description: 'A contemporary YA retelling of Pride and Prejudice set in Brooklyn', + significance: 'Demonstrates the continued relevance of Austen\'s themes in modern, diverse contexts' + } +]; + +export default function InteractiveTimeline({ events = timelineEvents }: Props) { + const [selectedEvent, setSelectedEvent] = useState(null); + const [activeFilters, setActiveFilters] = useState(['works', 'context', 'legacy', 'adaptations']); + const [dragPosition, setDragPosition] = useState(50); + const timelineRef = useRef(null); + const isDragging = useRef(false); + + // Extended year range + const displayMinYear = Math.min(...events.map(e => e.year)) - 10; + const displayMaxYear = Math.max(...events.map(e => e.year)) + 10; + const actualMinYear = displayMinYear; + const actualMaxYear = displayMaxYear; + const timeSpan = actualMaxYear - actualMinYear; + + // Generate year markers + const yearMarkers = []; + const yearStep = Math.ceil(timeSpan / 15); + for (let year = actualMinYear; year <= actualMaxYear; year += yearStep) { + yearMarkers.push(year); + } + if (yearMarkers[yearMarkers.length - 1] !== actualMaxYear) { + yearMarkers.push(actualMaxYear); + } + + // Add scroll position tracking + useEffect(() => { + const handleScroll = () => { + if (timelineRef.current) { + const { scrollLeft } = timelineRef.current; + const totalWidth = timelineRef.current.scrollWidth; + const position = (scrollLeft / totalWidth) * 100; + setDragPosition(position); + } + }; + + timelineRef.current?.addEventListener('scroll', handleScroll); + return () => timelineRef.current?.removeEventListener('scroll', handleScroll); + }, []); + + const navigateEvents = (direction: 'prev' | 'next') => { + // Sort filtered events chronologically + const filteredEvents = events + .filter(event => activeFilters.includes(event.type)) + .sort((a, b) => a.year - b.year); + + if (filteredEvents.length === 0) return; + + if (!selectedEvent) { + setSelectedEvent(direction === 'next' ? filteredEvents[0] : filteredEvents[filteredEvents.length - 1]); + return; + } + + // Find the current event's index in the filtered array + const currentIndex = filteredEvents.findIndex(e => e.year === selectedEvent.year && e.title === selectedEvent.title); + let newIndex; + + if (direction === 'next') { + newIndex = currentIndex < filteredEvents.length - 1 ? currentIndex + 1 : 0; + } else { + newIndex = currentIndex > 0 ? currentIndex - 1 : filteredEvents.length - 1; + } + + const newEvent = filteredEvents[newIndex]; + setSelectedEvent(newEvent); + + // Calculate new drag position and scroll into view + if (timelineRef.current) { + const timelineWidth = timelineRef.current.clientWidth; + const totalWidth = timelineRef.current.scrollWidth; + const newPosition = ((newEvent.year - actualMinYear) / timeSpan) * 100; + setDragPosition(newPosition); + + const scrollPosition = (newPosition / 100) * totalWidth - (timelineWidth / 2); + timelineRef.current.scrollTo({ + left: scrollPosition, + behavior: 'smooth' + }); + } + }; + + const handleMouseMove = (e: React.MouseEvent) => { + if (isDragging.current && timelineRef.current) { + const rect = timelineRef.current.getBoundingClientRect(); + const scrollLeft = timelineRef.current.scrollLeft; + const position = ((e.clientX - rect.left + scrollLeft) / (rect.width * 3)) * 100; + setDragPosition(Math.max(0, Math.min(100, position))); + + const currentYear = (position / 100) * timeSpan + actualMinYear; + // Only consider events that are currently filtered + const filteredEvents = events.filter(event => activeFilters.includes(event.type)); + // Find nearest event among filtered events + const nearestEvent = filteredEvents.reduce((prev, curr) => { + return Math.abs(curr.year - currentYear) < Math.abs(prev.year - currentYear) ? curr : prev; + }, filteredEvents[0]); + + // Only set selected event if we have filtered events and are within 1 year of the nearest event + if (filteredEvents.length > 0 && Math.abs(nearestEvent.year - currentYear) <= 1) { + setSelectedEvent(nearestEvent); + } else { + setSelectedEvent(null); + } + } + }; + + const handleMouseDown = (e: React.MouseEvent) => { + if (timelineRef.current) { + isDragging.current = true; + const rect = timelineRef.current.getBoundingClientRect(); + const scrollLeft = timelineRef.current.scrollLeft; + const position = ((e.clientX - rect.left + scrollLeft) / (rect.width * 3)) * 100; + setDragPosition(Math.max(0, Math.min(100, position))); + + const currentYear = (position / 100) * timeSpan + actualMinYear; + // Only consider events that are currently filtered + const filteredEvents = events.filter(event => activeFilters.includes(event.type)); + // Find nearest event among filtered events + const nearestEvent = filteredEvents.reduce((prev, curr) => { + return Math.abs(curr.year - currentYear) < Math.abs(prev.year - currentYear) ? curr : prev; + }, filteredEvents[0]); + + // Only set selected event if we have filtered events and are within 1 year of the nearest event + if (filteredEvents.length > 0 && Math.abs(nearestEvent.year - currentYear) <= 1) { + setSelectedEvent(nearestEvent); + } else { + setSelectedEvent(null); + } + } + }; + + const handleMouseUp = () => { + isDragging.current = false; + }; + + const handleMouseLeave = () => { + isDragging.current = false; + }; + + const toggleFilter = (filter: string) => { + setActiveFilters(prev => + prev.includes(filter) + ? prev.filter(f => f !== filter) + : [...prev, filter] + ); + }; + + const getEventColor = (type: string) => { + switch (type) { + case 'works': + return { main: 'primary.main', light: 'primary.50' }; + case 'context': + return { main: 'secondary.main', light: 'secondary.50' }; + case 'legacy': + return { main: 'warning.main', light: 'warning.50' }; + case 'adaptations': + return { main: 'success.main', light: 'success.50' }; + default: + return { main: 'grey.500', light: 'grey.50' }; + } + }; + + const filteredEvents = events.filter(event => activeFilters.includes(event.type)); + + // Add click handler for events + const handleEventClick = (event: TimelineEvent) => { + if (activeFilters.includes(event.type)) { + setSelectedEvent(event); + // Update drag position to match clicked event + const newPosition = ((event.year - actualMinYear) / timeSpan) * 100; + setDragPosition(newPosition); + + // Scroll to the clicked event + if (timelineRef.current) { + const timelineWidth = timelineRef.current.clientWidth; + const totalWidth = timelineRef.current.scrollWidth; + const scrollPosition = (newPosition / 100) * totalWidth - (timelineWidth / 2); + timelineRef.current.scrollTo({ + left: scrollPosition, + behavior: 'smooth' + }); + } + } + }; + + return ( + + {/* Filters */} + + + + + + + + + + {/* Timeline Container */} + + {/* Navigation Buttons Container */} + + navigateEvents('prev')} + sx={{ + position: 'absolute', + left: 0, + top: '50%', + transform: 'translate(-50%, -50%)', + zIndex: 3, + bgcolor: 'background.paper', + border: '1px solid', + borderColor: 'divider', + boxShadow: theme => `0 2px 8px ${alpha(theme.palette.common.black, 0.15)}`, + width: 40, + height: 40, + pointerEvents: 'auto', + '&:hover': { + bgcolor: 'grey.50', + boxShadow: theme => `0 4px 12px ${alpha(theme.palette.common.black, 0.2)}` + }, + '&:active': { + bgcolor: 'grey.100' + } + }} + > + + + navigateEvents('next')} + sx={{ + position: 'absolute', + right: 0, + top: '50%', + transform: 'translate(50%, -50%)', + zIndex: 3, + bgcolor: 'background.paper', + border: '1px solid', + borderColor: 'divider', + boxShadow: theme => `0 2px 8px ${alpha(theme.palette.common.black, 0.15)}`, + width: 40, + height: 40, + pointerEvents: 'auto', + '&:hover': { + bgcolor: 'grey.50', + boxShadow: theme => `0 4px 12px ${alpha(theme.palette.common.black, 0.2)}` + }, + '&:active': { + bgcolor: 'grey.100' + } + }} + > + + + + + {/* Time Period Labels */} + + {timePeriods.map((period) => { + const startPos = ((period.start - actualMinYear) / timeSpan) * 100; + const width = ((period.end - period.start) / timeSpan) * 100; + return ( + + + theme.palette.text.primary, + fontWeight: 500, + fontSize: '0.875rem' + }} + > + {period.name} + + + + ); + })} + + + {/* Main Timeline */} + `0 2px 12px ${alpha(theme.palette.common.black, 0.08)}`, + overflow: 'hidden', + cursor: 'pointer', + userSelect: 'none', + overflowX: 'auto', + '&::-webkit-scrollbar': { + height: 10, + borderRadius: 4 + }, + '&::-webkit-scrollbar-track': { + bgcolor: 'rgba(0, 0, 0, 0.05)', + borderRadius: 4, + mx: 2 + }, + '&::-webkit-scrollbar-thumb': { + bgcolor: 'sage.400', + borderRadius: 4, + border: '2px solid', + borderColor: 'background.paper', + '&:hover': { + bgcolor: 'sage.500' + } + }, + scrollBehavior: 'smooth' + }} + onMouseDown={handleMouseDown} + onMouseMove={handleMouseMove} + onMouseUp={handleMouseUp} + onMouseLeave={handleMouseLeave} + > + + {/* Time Period Backgrounds */} + {timePeriods.map((period) => { + const startPos = ((period.start - actualMinYear) / timeSpan) * 100; + const width = ((period.end - period.start) / timeSpan) * 100; + return ( + + ); + })} + + {/* Year Markers */} + + {yearMarkers.map((year) => { + const isDecade = year % 10 === 0; + return ( + + + + {year} + + + ); + })} + + + {/* Timeline Base Line */} + + + {/* Events */} + {filteredEvents.map((event, index) => { + const position = ((event.year - actualMinYear) / timeSpan) * 100; + const isTop = index % 2 === 0; + const colors = getEventColor(event.type); + const isSelected = selectedEvent?.year === event.year; + const verticalPosition = isTop ? 30 : 70; + + return ( + handleEventClick(event)} + sx={{ + position: 'absolute', + left: `${position}%`, + top: `${verticalPosition}%`, + transform: 'translate(-50%, -50%)', + zIndex: isSelected ? 2 : 1, + transition: 'all 0.3s ease', + cursor: 'pointer', + '&:hover': { + transform: 'translate(-50%, -50%) scale(1.1)' + } + }} + > + + {/* Top Connection Line */} + + + {/* Bottom Connection Line */} + + + {/* Event Dot */} + + + {/* Event Label */} + isSelected + ? theme.shadows[3] + : `0 2px 4px ${alpha(theme.palette.common.black, 0.05)}`, + '&:hover': { + boxShadow: theme => `0 4px 8px ${alpha(theme.palette.common.black, 0.1)}` + } + }} + > + + {event.title} + + + + + ); + })} + + {/* Draggable Indicator */} + + + + + + + {/* Event Details Section */} + {selectedEvent && ( + + + + + {selectedEvent.title} + + + {selectedEvent.year} + + + + + + + + + {selectedEvent.description} + + + {selectedEvent.significance && ( + + + Historical Significance + + + {selectedEvent.significance} + + + )} + + )} + + + ); +} diff --git a/src/pages/SocialClass.tsx b/src/pages/SocialClass.tsx new file mode 100644 index 0000000..12582f1 --- /dev/null +++ b/src/pages/SocialClass.tsx @@ -0,0 +1,194 @@ +import React from 'react'; +import { Box, Typography, Paper, Grid, Chip, Accordion, AccordionSummary, AccordionDetails } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import SocialClassView from '../components/SocialClassView'; + +export default function SocialClass() { + return ( + + {/* Header Section */} + + + Social Class in Jane Austen's Works + + + An analysis of how social class shapes character relationships, marriage prospects, and social mobility across Austen's novels + and their modern retellings. + + + + + + + + + + {/* Character Analysis Section */} + + + Character Studies Across Class Lines + + + + }> + Pride and Prejudice: The Economics of Marriage + + + + + Upper Class + + Mr. Darcy (£10,000 per year): Represents the pinnacle of landed gentry, whose wealth + allows him to transcend local social barriers. His initial pride stems from his position, but his + character development shows wealth doesn't guarantee happiness or love. + + + Lady Catherine de Bourgh: Embodies the aristocracy's resistance to social mobility, + particularly in her opposition to Darcy and Elizabeth's marriage. Her character critiques the + assumption that high birth equals moral superiority. + + + + Middle & Lower Classes + + The Bennet Family: Despite their genteel status, their precarious financial position + (with the entailed estate) demonstrates the vulnerability of women in the period. Mrs. Bennet's + anxiety about her daughters' marriages stems from real economic concerns. + + + The Servants (via Longbourn): Jo Baker's retelling gives voice to characters like + Sarah and Mrs. Hill, revealing the hidden labor that maintains the genteel lifestyle of the main characters. + + + + + + + + }> + Mansfield Park: Moral Worth vs. Social Status + + + + + The Privileged Circle + + The Bertram Family: Sir Thomas's wealth from colonial enterprises in Antigua + raises questions about the source of aristocratic wealth. His children's poor moral education + despite their privileged upbringing challenges assumptions about class and character. + + + Mary and Henry Crawford: Their London sophistication and wealth mask moral + bankruptcy, contrasting with Fanny's humble virtue. They represent the corruption of urban wealth + versus rural values. + + + + The Dependent Relations + + Fanny Price: Her position as a poor relation taken in by wealthy relatives + highlights the complex dynamics of dependency and gratitude. Her moral strength despite her low + status challenges class-based assumptions about worth. + + + The Price Family in Portsmouth: Their cramped, chaotic household provides a + stark contrast to Mansfield's luxury, highlighting the material realities of class difference + in the period. + + + + + + + + }> + Contemporary Perspectives: Modern Retellings + + + + + Pride by Ibi Zoboi + + Zuri Benitez: Reimagines Elizabeth Bennet as a proud Afro-Latina teenager in + Brooklyn, exploring how gentrification and cultural identity intersect with class in contemporary + America. + + + Darius Darcy: As a wealthy African American teenager, his character explores the + complexities of privilege within modern racial and social contexts, updating Austen's examination + of pride and prejudice. + + + + Longbourn's Legacy + + The Servants' Perspective: Baker's novel reveals the physical labor, limited + opportunities, and complex relationships that supported the genteel world of Pride and Prejudice, + giving voice to historically silenced characters. + + + Class Intersections: Through characters like James Smith, the novel explores how + war, servitude, and social mobility operated for those below stairs, expanding our understanding + of Austen's world. + + + + + + + + {/* Interactive View */} + + + Interactive Class Analysis + + + + + {/* Critical Analysis */} + + + Critical Insights + + + + + + Economic Realities + + + Austen's precise attention to characters' incomes and property reflects the real economic pressures + that shaped marriage choices and social mobility in the Regency period. Her novels acknowledge both + the practical necessity of financial security and its moral dangers. + + + + + + + Social Mobility + + + Through characters like Emma Woodhouse and Elizabeth Bennet, Austen explores the possibilities and + limitations of social mobility through marriage, education, and moral development, while acknowledging + the rigid class structures of her time. + + + + + + + Modern Relevance + + + Contemporary adaptations like Pride and Longbourn demonstrate how Austen's exploration of class, + privilege, and social mobility remains relevant to modern discussions of inequality, opportunity, + and social justice. + + + + + + + ); +} diff --git a/src/pages/Timeline.tsx b/src/pages/Timeline.tsx new file mode 100644 index 0000000..083e037 --- /dev/null +++ b/src/pages/Timeline.tsx @@ -0,0 +1,322 @@ +import React, { useState } from 'react'; +import { Box, Typography, Paper, Grid, Chip, IconButton, Tooltip, Tabs, Tab } from '@mui/material'; +import { Help } from '@mui/icons-material'; +import { TimelineEvent } from '../types/timeline'; +import InteractiveTimeline from '../components/timeline/InteractiveTimeline'; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +export default function Timeline() { + const [selectedType, setSelectedType] = useState<'all' | 'works' | 'context' | 'legacy' | 'adaptations'>('all'); + const [tabValue, setTabValue] = useState(0); + + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setTabValue(newValue); + }; + + // Timeline data focused on course texts and their context + const timelineEvents: TimelineEvent[] = [ + // Austen's Works + { + year: 1787, + type: 'works', + title: 'Love and Freindship', + description: "Early epistolary work from Austen's juvenilia, showing her early satirical style", + significance: 'Demonstrates Austen\'s early critique of sensibility and romantic conventions' + }, + { + year: 1795, + type: 'works', + title: 'Elinor and Marianne (First Draft)', + description: 'Early epistolary version of what would become Sense and Sensibility', + significance: 'Shows development of Austen\'s craft from letters to narrative form' + }, + { + year: 1797, + type: 'works', + title: 'First Impressions (Original P&P)', + description: 'Original version of Pride and Prejudice, rejected by publisher Thomas Cadell', + significance: 'Reveals the evolution of her most famous work' + }, + { + year: 1811, + type: 'works', + title: 'Sense and Sensibility Published', + description: 'Austen\'s first published novel explores the tension between emotional expression and rational restraint through the Dashwood sisters.', + novel: 'sense-and-sensibility', + significance: 'Establishes Austen\'s recurring theme of balancing heart and mind in relationships.' + }, + { + year: 1813, + type: 'works', + title: 'Pride and Prejudice Published', + description: 'A masterful examination of hasty judgments and social prejudices through Elizabeth Bennet\'s journey.', + novel: 'pride-and-prejudice', + significance: 'Critiques the marriage market while exploring personal growth and social mobility.' + }, + { + year: 1814, + type: 'works', + title: 'Mansfield Park Published', + description: 'Through Fanny Price\'s story, Austen examines moral integrity in a materialistic society.', + novel: 'mansfield-park', + significance: 'Presents Austen\'s most direct critique of social corruption and moral decay.' + }, + { + year: 1818, + type: 'works', + title: 'Northanger Abbey Published (Posthumously)', + description: 'A playful parody of Gothic novels that examines the relationship between fiction and reality.', + novel: 'northanger-abbey', + significance: 'Demonstrates Austen\'s literary awareness and critique of reading practices.' + }, + + // Historical and Cultural Context + { + year: 1792, + type: 'context', + title: 'Vindication of the Rights of Woman', + description: 'Mary Wollstonecraft publishes feminist treatise', + significance: 'Contemporary debates about women\'s education and rights that appear in Austen\'s works' + }, + { + year: 1795, + type: 'context', + title: 'Marriage Act Amendment', + description: 'Required separate residence for 6 weeks before marriage, affecting courtship practices.', + significance: 'Provides legal context for marriage plots and elopement concerns in Pride and Prejudice and Mansfield Park.' + }, + { + year: 1799, + type: 'context', + title: 'Income Tax Introduction', + description: 'First British income tax introduced to fund Napoleonic Wars', + significance: 'Economic context for character incomes mentioned in novels' + }, + { + year: 1801, + type: 'context', + title: 'Move to Bath', + description: 'Austen family relocates to Bath, a period of reduced writing', + significance: 'Influenced her portrayal of Bath in Northanger Abbey and other works' + }, + { + year: 1805, + type: 'context', + title: 'Battle of Trafalgar & Death of Rev. Austen', + description: 'Major naval victory and death of Jane\'s father leading to financial uncertainty', + significance: 'Influences on naval themes and women\'s financial dependence in her novels' + }, + + // Legacy and Critical Reception + { + year: 1870, + type: 'legacy', + title: 'Memoir of Jane Austen', + description: 'James Edward Austen-Leigh publishes first major biography', + significance: 'Shaped Victorian and later reception of Austen' + }, + { + year: 2009, + type: 'legacy', + title: 'A Truth Universally Acknowledged', + description: 'Collection of critical essays on why we read Jane Austen', + significance: 'Modern critical perspectives on Austen\'s enduring appeal' + }, + + // Modern Adaptations and Retellings + { + year: 1995, + type: 'adaptations', + title: 'BBC Pride and Prejudice', + description: 'Colin Firth/Jennifer Ehle adaptation', + significance: 'Influential adaptation that sparked renewed interest in Austen' + }, + { + year: 2008, + type: 'adaptations', + title: 'Lost in Austen', + description: 'Time-travel adaptation mixing modern perspective with P&P', + significance: 'Example of creative modern reinterpretation mentioned in course' + }, + { + year: 2013, + type: 'adaptations', + title: 'Longbourn by Jo Baker', + description: 'A retelling of Pride and Prejudice from the servants\' perspective', + significance: 'Offers a "downstairs" perspective on Austen\'s world' + }, + { + year: 2015, + type: 'adaptations', + title: 'Pride by Ibi Zoboi', + description: 'A contemporary YA retelling of Pride and Prejudice set in Brooklyn', + significance: 'Demonstrates the continued relevance of Austen\'s themes in modern, diverse contexts' + } + ]; + + const filteredEvents = timelineEvents + .filter(event => selectedType === 'all' || event.type === selectedType) + .sort((a, b) => a.year - b.year); + + return ( + + + + + + + + + + + + + + + + + Literary Timeline & Historical Context + + + + + + + + + + + Discover how Austen's novels interact with their historical moment and continue to inspire contemporary retellings. + + + setSelectedType('all')} + color={selectedType === 'all' ? 'primary' : 'default'} + /> + setSelectedType('works')} + color={selectedType === 'works' ? 'primary' : 'default'} + /> + setSelectedType('context')} + color={selectedType === 'context' ? 'secondary' : 'default'} + /> + setSelectedType('legacy')} + color={selectedType === 'legacy' ? 'warning' : 'default'} + /> + setSelectedType('adaptations')} + color={selectedType === 'adaptations' ? 'success' : 'default'} + /> + + + + + {filteredEvents.map((event, index) => ( + + + + + + {event.title} + + + {event.year} + + + {event.description} + + {event.significance && ( + + Significance: {event.significance} + + )} + + + + + + ))} + + + + + ); +} diff --git a/src/types/timeline.ts b/src/types/timeline.ts new file mode 100644 index 0000000..308fdf5 --- /dev/null +++ b/src/types/timeline.ts @@ -0,0 +1,33 @@ +export interface TimelineEvent { + year: number; + type: 'works' | 'context' | 'legacy' | 'adaptations'; + title: string; + description: string; + novel?: string; + significance?: string; +} + +export interface Character { + id: string; + name: string; + novel: string; + socialClass: 'upper' | 'middle' | 'working'; + occupation?: string; + annualIncome?: string; + modernEquivalent?: string; + description: string; + relationships: string[]; +} + +export interface SocialClass { + name: string; + description: string; + incomeRange: string; + modernEquivalent: string; + characteristics: string[]; + examples: { + character: string; + novel: string; + context: string; + }[]; +}