updated interactive timeline and added social class view

This commit is contained in:
Harivansh Rathi 2024-12-04 14:40:29 -05:00
parent 9609248b69
commit 59657ce287
8 changed files with 2063 additions and 0 deletions

View file

@ -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<Character | null>(
null
);
const [comparisonCharacter, setComparisonCharacter] =
useState<Character | null>(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

View file

@ -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() {
<Route path="/analysis" element={<Analysis />} />
<Route path="/comparative" element={<ComparativeAnalysis />} />
<Route path="/network" element={<NetworkVisualization />} />
<Route path="/timeline" element={<Timeline />} />
<Route path="/social-class" element={<SocialClass />} />
<Route path="*" element={<div>Page not found</div>} />
</Routes>
</Suspense>

View file

@ -58,6 +58,18 @@ const Navbar = () => {
>
Character Network
</Link>
<Link
to="/timeline"
className="bg-sage-100 text-sage-700 hover:bg-sage-200 px-4 py-2 rounded-md transition-colors"
>
Timeline
</Link>
<Link
to="/social-class"
className="bg-sage-100 text-sage-700 hover:bg-sage-200 px-4 py-2 rounded-md transition-colors"
>
Social Class
</Link>
</div>
</nav>
</div>

View file

@ -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<Character | null>(null);
const [comparisonCharacter, setComparisonCharacter] = useState<Character | null>(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 (
<Box sx={{ p: 3 }}>
<Typography variant="h5" gutterBottom>
Social Class in Austen's Novels
</Typography>
{/* Social Pyramid */}
<Box sx={{ mb: 6, position: 'relative', height: 300 }}>
{socialClasses.map((socialClass, index) => (
<Tooltip
key={socialClass.name}
title={
<Box>
<Typography variant="subtitle2">{socialClass.name}</Typography>
<Typography variant="body2">{socialClass.description}</Typography>
<Typography variant="caption">Income: {socialClass.incomeRange}</Typography>
</Box>
}
arrow
>
<Paper
elevation={3}
sx={{
position: 'absolute',
left: '50%',
width: `${100 - index * 20}%`,
height: 80,
transform: `translateX(-50%) translateY(${index * 100}px)`,
bgcolor: `primary.${100 + index * 100}`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
transition: 'all 0.2s',
'&:hover': {
transform: `translateX(-50%) translateY(${index * 100}px) scale(1.02)`,
}
}}
>
<Typography variant="h6" color="text.primary">
{socialClass.name}
</Typography>
</Paper>
</Tooltip>
))}
</Box>
{/* Character Grid */}
<Typography variant="h6" gutterBottom sx={{ mt: 4 }}>
Character Examples {selectedCharacter && '(Select another character to compare)'}
</Typography>
<Grid container spacing={2}>
{characters.map((character) => (
<Grid item xs={12} sm={6} md={4} key={character.id}>
<Card
onClick={() => handleCharacterClick(character)}
sx={{
cursor: 'pointer',
transition: 'all 0.2s',
'&:hover': {
transform: 'scale(1.02)',
},
bgcolor: selectedCharacter?.id === character.id ? 'primary.50' : 'background.paper'
}}
>
<CardContent>
<Typography variant="h6" gutterBottom>
{character.name}
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
{character.novel}
</Typography>
<Chip
label={character.socialClass.charAt(0).toUpperCase() + character.socialClass.slice(1)}
size="small"
sx={{ mb: 1 }}
/>
<Typography variant="body2">
Annual Income: {character.annualIncome}
</Typography>
<Typography variant="caption" color="text.secondary" display="block">
Modern Equivalent: {character.modernEquivalent}
</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
{/* Comparison Dialog */}
<Dialog open={dialogOpen} onClose={handleCloseDialog} maxWidth="md" fullWidth>
<DialogTitle>Character Comparison</DialogTitle>
<DialogContent>
{selectedCharacter && comparisonCharacter && (
<Grid container spacing={3}>
<Grid item xs={6}>
<Typography variant="h6" gutterBottom>{selectedCharacter.name}</Typography>
<Typography variant="body2" gutterBottom>Novel: {selectedCharacter.novel}</Typography>
<Typography variant="body2" gutterBottom>Class: {selectedCharacter.socialClass}</Typography>
<Typography variant="body2" gutterBottom>Income: {selectedCharacter.annualIncome}</Typography>
<Typography variant="body2" gutterBottom>Modern: {selectedCharacter.modernEquivalent}</Typography>
<Typography variant="body2">{selectedCharacter.description}</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="h6" gutterBottom>{comparisonCharacter.name}</Typography>
<Typography variant="body2" gutterBottom>Novel: {comparisonCharacter.novel}</Typography>
<Typography variant="body2" gutterBottom>Class: {comparisonCharacter.socialClass}</Typography>
<Typography variant="body2" gutterBottom>Income: {comparisonCharacter.annualIncome}</Typography>
<Typography variant="body2" gutterBottom>Modern: {comparisonCharacter.modernEquivalent}</Typography>
<Typography variant="body2">{comparisonCharacter.description}</Typography>
</Grid>
</Grid>
)}
</DialogContent>
</Dialog>
</Box>
);
}

View file

@ -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<TimelineEvent | null>(null);
const [activeFilters, setActiveFilters] = useState<string[]>(['works', 'context', 'legacy', 'adaptations']);
const [dragPosition, setDragPosition] = useState<number>(50);
const timelineRef = useRef<HTMLDivElement>(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 (
<Box sx={{ p: 3 }}>
{/* Filters */}
<Box sx={{ mb: 4 }}>
<ButtonGroup variant="outlined" size="small">
<Button
onClick={() => toggleFilter('works')}
variant={activeFilters.includes('works') ? 'contained' : 'outlined'}
sx={{
color: activeFilters.includes('works') ? 'white' : 'primary.main',
borderColor: 'primary.main',
'&:hover': {
borderColor: 'primary.dark',
bgcolor: activeFilters.includes('works') ? 'primary.dark' : 'primary.50'
}
}}
>
Austen's Works
</Button>
<Button
onClick={() => toggleFilter('context')}
variant={activeFilters.includes('context') ? 'contained' : 'outlined'}
sx={{
color: activeFilters.includes('context') ? 'white' : 'secondary.main',
borderColor: 'secondary.main',
bgcolor: activeFilters.includes('context') ? 'secondary.main' : 'transparent',
'&:hover': {
borderColor: 'secondary.dark',
bgcolor: activeFilters.includes('context') ? 'secondary.dark' : 'secondary.50'
}
}}
>
Historical Context
</Button>
<Button
onClick={() => toggleFilter('legacy')}
variant={activeFilters.includes('legacy') ? 'contained' : 'outlined'}
sx={{
color: activeFilters.includes('legacy') ? 'white' : 'warning.main',
borderColor: 'warning.main',
bgcolor: activeFilters.includes('legacy') ? 'warning.main' : 'transparent',
'&:hover': {
borderColor: 'warning.dark',
bgcolor: activeFilters.includes('legacy') ? 'warning.dark' : 'warning.50'
}
}}
>
Legacy & Reception
</Button>
<Button
onClick={() => toggleFilter('adaptations')}
variant={activeFilters.includes('adaptations') ? 'contained' : 'outlined'}
sx={{
color: activeFilters.includes('adaptations') ? 'white' : 'success.main',
borderColor: 'success.main',
bgcolor: activeFilters.includes('adaptations') ? 'success.main' : 'transparent',
'&:hover': {
borderColor: 'success.dark',
bgcolor: activeFilters.includes('adaptations') ? 'success.dark' : 'success.50'
}
}}
>
Modern Adaptations
</Button>
</ButtonGroup>
</Box>
{/* Timeline Container */}
<Box sx={{ position: 'relative', mt: 6 }}>
{/* Navigation Buttons Container */}
<Box
sx={{
position: 'absolute',
top: 200, // Half of the timeline height (400px)
left: 0,
right: 0,
height: 0, // Zero height to not affect layout
pointerEvents: 'none',
zIndex: 3
}}
>
<IconButton
onClick={() => 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'
}
}}
>
<ChevronLeft />
</IconButton>
<IconButton
onClick={() => 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'
}
}}
>
<ChevronRight />
</IconButton>
</Box>
{/* Time Period Labels */}
<Box sx={{
position: 'absolute',
top: -60,
left: 0,
right: 0,
display: 'flex',
justifyContent: 'space-between',
px: 2,
mb: 2,
overflow: 'hidden'
}}>
{timePeriods.map((period) => {
const startPos = ((period.start - actualMinYear) / timeSpan) * 100;
const width = ((period.end - period.start) / timeSpan) * 100;
return (
<Box
key={period.name}
sx={{
position: 'absolute',
left: `${startPos}%`,
width: `${width}%`,
textAlign: 'center'
}}
>
<Paper
elevation={0}
sx={{
display: 'inline-block',
bgcolor: period.color,
border: '1px solid',
borderColor: 'divider',
px: 2,
py: 0.5,
borderRadius: '16px',
whiteSpace: 'nowrap',
fontFamily: 'cormorant',
}}
>
<Typography
variant="caption"
sx={{
color: theme => theme.palette.text.primary,
fontWeight: 500,
fontSize: '0.875rem'
}}
>
{period.name}
</Typography>
</Paper>
</Box>
);
})}
</Box>
{/* Main Timeline */}
<Box
ref={timelineRef}
sx={{
position: 'relative',
height: 400,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: theme => `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}
>
<Box sx={{ width: '300%', height: '100%', position: 'relative' }}>
{/* Time Period Backgrounds */}
{timePeriods.map((period) => {
const startPos = ((period.start - actualMinYear) / timeSpan) * 100;
const width = ((period.end - period.start) / timeSpan) * 100;
return (
<Box
key={period.name}
sx={{
position: 'absolute',
left: `${startPos}%`,
width: `${width}%`,
height: '100%',
bgcolor: period.color,
opacity: 0.5
}}
/>
);
})}
{/* Year Markers */}
<Box sx={{
position: 'absolute',
top: 16,
left: 0,
right: 0,
height: 40,
display: 'flex',
zIndex: 2
}}>
{yearMarkers.map((year) => {
const isDecade = year % 10 === 0;
return (
<Box
key={year}
sx={{
position: 'absolute',
left: `${((year - actualMinYear) / timeSpan) * 100}%`,
transform: 'translateX(-50%)',
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Box sx={{
width: isDecade ? 2 : 1,
height: isDecade ? 10 : 6,
bgcolor: isDecade ? 'primary.main' : 'text.secondary',
opacity: isDecade ? 0.8 : 0.5
}} />
<Typography
variant="caption"
sx={{
color: isDecade ? 'primary.main' : 'text.secondary',
mt: 0.5,
fontWeight: isDecade ? 600 : 400,
bgcolor: 'rgba(255, 255, 255, 0.9)',
px: 1,
py: 0.25,
borderRadius: 1,
fontSize: isDecade ? '0.75rem' : '0.7rem',
boxShadow: isDecade ? '0 1px 3px rgba(0,0,0,0.1)' : 'none',
border: isDecade ? '1px solid' : 'none',
borderColor: 'primary.100'
}}
>
{year}
</Typography>
</Box>
);
})}
</Box>
{/* Timeline Base Line */}
<Box
sx={{
position: 'absolute',
top: '50%',
left: 0,
right: 0,
height: 2,
bgcolor: 'grey.300',
transform: 'translateY(-50%)'
}}
/>
{/* 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 (
<Box
key={`${event.year}-${event.title}`}
onClick={() => 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)'
}
}}
>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
position: 'relative',
height: 140
}}
>
{/* Top Connection Line */}
<Box
sx={{
position: 'absolute',
top: 0,
left: '50%',
transform: 'translateX(-50%)',
width: 2,
height: '30px',
bgcolor: colors.main,
opacity: 0.5
}}
/>
{/* Bottom Connection Line */}
<Box
sx={{
position: 'absolute',
bottom: 0,
left: '50%',
transform: 'translateX(-50%)',
width: 2,
height: '30px',
bgcolor: colors.main,
opacity: 0.5
}}
/>
{/* Event Dot */}
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: isSelected ? 16 : 12,
height: isSelected ? 16 : 12,
borderRadius: '50%',
bgcolor: colors.main,
transition: 'all 0.2s',
zIndex: 2,
'&::after': {
content: '""',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '200%',
height: '200%',
borderRadius: '50%',
bgcolor: colors.light,
opacity: isSelected ? 1 : 0,
transition: 'all 0.2s'
}
}}
/>
{/* Event Label */}
<Paper
elevation={isSelected ? 3 : 1}
sx={{
position: 'absolute',
top: isTop ? 'auto' : '70%',
bottom: isTop ? '70%' : 'auto',
p: 1.5,
maxWidth: 180,
minWidth: 120,
bgcolor: isSelected ? colors.light : 'background.paper',
transform: isSelected ? 'scale(1.05)' : 'scale(1)',
transition: 'all 0.2s',
zIndex: isSelected ? 2 : 1,
mx: 2,
borderRadius: 2,
boxShadow: theme => 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)}`
}
}}
>
<Typography
variant="caption"
sx={{
display: 'block',
textAlign: 'center',
fontWeight: isSelected ? 600 : 500,
color: isSelected ? 'text.primary' : 'text.secondary',
fontSize: '0.75rem',
lineHeight: 1.4,
whiteSpace: 'normal',
wordWrap: 'break-word'
}}
>
{event.title}
</Typography>
</Paper>
</Box>
</Box>
);
})}
{/* Draggable Indicator */}
<Box
sx={{
position: 'absolute',
left: `${dragPosition}%`,
top: 0,
bottom: 0,
width: 3,
bgcolor: 'text.primary',
transform: 'translateX(-50%)',
zIndex: 3,
'&::after': {
content: '""',
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 24,
height: 24,
borderRadius: '50%',
bgcolor: 'background.paper',
border: '3px solid',
borderColor: 'text.primary'
}
}}
>
<DragIndicator
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
color: 'text.primary',
fontSize: 16
}}
/>
</Box>
</Box>
</Box>
{/* Event Details Section */}
{selectedEvent && (
<Paper
sx={{
p: 4,
mt: 4,
bgcolor: 'background.paper',
borderRadius: 2,
boxShadow: 2,
transition: 'all 0.3s ease',
border: '1px solid',
borderColor: 'divider'
}}
>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
<Box>
<Typography variant="h5" gutterBottom color="primary">
{selectedEvent.title}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
{selectedEvent.year}
</Typography>
</Box>
<Chip
label={selectedEvent.type.charAt(0).toUpperCase() + selectedEvent.type.slice(1)}
color={
selectedEvent.type === 'works'
? 'primary'
: selectedEvent.type === 'context'
? 'secondary'
: selectedEvent.type === 'legacy'
? 'warning'
: 'success'
}
size="small"
/>
</Box>
<Divider sx={{ my: 2 }} />
<Typography variant="body1" paragraph>
{selectedEvent.description}
</Typography>
{selectedEvent.significance && (
<Box sx={{ mt: 2, p: 2, bgcolor: 'grey.50', borderRadius: 1 }}>
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
Historical Significance
</Typography>
<Typography variant="body2" color="text.secondary">
{selectedEvent.significance}
</Typography>
</Box>
)}
</Paper>
)}
</Box>
</Box>
);
}

194
src/pages/SocialClass.tsx Normal file
View file

@ -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 (
<Box sx={{ width: '100%', p: 3, maxWidth: 1200, mx: 'auto' }}>
{/* Header Section */}
<Box sx={{ mb: 6, textAlign: 'center' }}>
<Typography variant="h3" component="h1" gutterBottom fontFamily="cormorant" color="primary.main">
Social Class in Jane Austen's Works
</Typography>
<Typography variant="h6" color="text.secondary" sx={{ mb: 3, maxWidth: 800, mx: 'auto' }}>
An analysis of how social class shapes character relationships, marriage prospects, and social mobility across Austen's novels
and their modern retellings.
</Typography>
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'center', flexWrap: 'wrap' }}>
<Chip label="Class Mobility" color="primary" />
<Chip label="Marriage Market" color="secondary" />
<Chip label="Social Status" color="success" />
<Chip label="Wealth & Property" color="warning" />
</Box>
</Box>
{/* Character Analysis Section */}
<Paper elevation={3} sx={{ p: 4, mb: 4, borderRadius: 2 }}>
<Typography variant="h5" gutterBottom color="primary" fontFamily="cormorant">
Character Studies Across Class Lines
</Typography>
<Accordion defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6" color="secondary">Pride and Prejudice: The Economics of Marriage</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="primary" gutterBottom>Upper Class</Typography>
<Typography paragraph>
<strong>Mr. Darcy (£10,000 per year)</strong>: 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.
</Typography>
<Typography paragraph>
<strong>Lady Catherine de Bourgh</strong>: 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.
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="primary" gutterBottom>Middle & Lower Classes</Typography>
<Typography paragraph>
<strong>The Bennet Family</strong>: 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.
</Typography>
<Typography paragraph>
<strong>The Servants (via Longbourn)</strong>: 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.
</Typography>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6" color="secondary">Mansfield Park: Moral Worth vs. Social Status</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="primary" gutterBottom>The Privileged Circle</Typography>
<Typography paragraph>
<strong>The Bertram Family</strong>: 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.
</Typography>
<Typography paragraph>
<strong>Mary and Henry Crawford</strong>: Their London sophistication and wealth mask moral
bankruptcy, contrasting with Fanny's humble virtue. They represent the corruption of urban wealth
versus rural values.
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="primary" gutterBottom>The Dependent Relations</Typography>
<Typography paragraph>
<strong>Fanny Price</strong>: 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.
</Typography>
<Typography paragraph>
<strong>The Price Family in Portsmouth</strong>: Their cramped, chaotic household provides a
stark contrast to Mansfield's luxury, highlighting the material realities of class difference
in the period.
</Typography>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6" color="secondary">Contemporary Perspectives: Modern Retellings</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="primary" gutterBottom>Pride by Ibi Zoboi</Typography>
<Typography paragraph>
<strong>Zuri Benitez</strong>: Reimagines Elizabeth Bennet as a proud Afro-Latina teenager in
Brooklyn, exploring how gentrification and cultural identity intersect with class in contemporary
America.
</Typography>
<Typography paragraph>
<strong>Darius Darcy</strong>: 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.
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="primary" gutterBottom>Longbourn's Legacy</Typography>
<Typography paragraph>
<strong>The Servants' Perspective</strong>: 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.
</Typography>
<Typography paragraph>
<strong>Class Intersections</strong>: 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.
</Typography>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
</Paper>
{/* Interactive View */}
<Paper elevation={3} sx={{ p: 4, mb: 4, borderRadius: 2 }}>
<Typography variant="h5" gutterBottom color="primary" fontFamily="cormorant" sx={{ mb: 3 }}>
Interactive Class Analysis
</Typography>
<SocialClassView />
</Paper>
{/* Critical Analysis */}
<Paper elevation={3} sx={{ p: 4, borderRadius: 2 }}>
<Typography variant="h5" gutterBottom color="primary" fontFamily="cormorant">
Critical Insights
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, bgcolor: 'background.default', borderRadius: 1 }}>
<Typography variant="h6" gutterBottom color="secondary">
Economic Realities
</Typography>
<Typography>
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.
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, bgcolor: 'background.default', borderRadius: 1 }}>
<Typography variant="h6" gutterBottom color="secondary">
Social Mobility
</Typography>
<Typography>
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.
</Typography>
</Box>
</Grid>
<Grid item xs={12} md={4}>
<Box sx={{ p: 2, bgcolor: 'background.default', borderRadius: 1 }}>
<Typography variant="h6" gutterBottom color="secondary">
Modern Relevance
</Typography>
<Typography>
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.
</Typography>
</Box>
</Grid>
</Grid>
</Paper>
</Box>
);
}

322
src/pages/Timeline.tsx Normal file
View file

@ -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 (
<div
role="tabpanel"
hidden={value !== index}
id={`timeline-tabpanel-${index}`}
aria-labelledby={`timeline-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ py: 3 }}>
{children}
</Box>
)}
</div>
);
}
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 (
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={tabValue}
onChange={handleTabChange}
aria-label="timeline views"
sx={{
'& .MuiTab-root': {
color: 'text.secondary',
'&.Mui-selected': {
color: 'primary.main',
},
},
}}
>
<Tab label="Interactive Timeline" />
<Tab label="Basic Timeline" />
</Tabs>
</Box>
<TabPanel value={tabValue} index={0}>
<InteractiveTimeline events={timelineEvents} />
</TabPanel>
<TabPanel value={tabValue} index={1}>
<Box sx={{ p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
<Typography variant="h4" component="h1">
Literary Timeline & Historical Context
</Typography>
<Tooltip title="Explore the chronological development of Austen's works and their historical/literary context" arrow>
<IconButton size="small" sx={{ ml: 2 }}>
<Help />
</IconButton>
</Tooltip>
</Box>
<Box sx={{ mb: 4 }}>
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
Discover how Austen's novels interact with their historical moment and continue to inspire contemporary retellings.
</Typography>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label="All Events"
onClick={() => setSelectedType('all')}
color={selectedType === 'all' ? 'primary' : 'default'}
/>
<Chip
label="Austen's Works"
onClick={() => setSelectedType('works')}
color={selectedType === 'works' ? 'primary' : 'default'}
/>
<Chip
label="Historical Context"
onClick={() => setSelectedType('context')}
color={selectedType === 'context' ? 'secondary' : 'default'}
/>
<Chip
label="Legacy & Reception"
onClick={() => setSelectedType('legacy')}
color={selectedType === 'legacy' ? 'warning' : 'default'}
/>
<Chip
label="Modern Adaptations"
onClick={() => setSelectedType('adaptations')}
color={selectedType === 'adaptations' ? 'success' : 'default'}
/>
</Box>
</Box>
<Grid container spacing={3}>
{filteredEvents.map((event, index) => (
<Grid item xs={12} key={index}>
<Paper
elevation={3}
sx={{
p: 3,
position: 'relative',
'&::before': {
content: '""',
position: 'absolute',
left: 0,
top: 0,
bottom: 0,
width: 4,
backgroundColor: event.type === 'works'
? 'primary.main'
: event.type === 'context'
? 'secondary.main'
: event.type === 'legacy'
? 'warning.main'
: 'success.main'
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between' }}>
<Box>
<Typography variant="h6" component="h2">
{event.title}
</Typography>
<Typography variant="subtitle1" color="text.secondary" sx={{ mb: 1 }}>
{event.year}
</Typography>
<Typography variant="body1" sx={{ mb: 2 }}>
{event.description}
</Typography>
{event.significance && (
<Typography variant="body2" color="text.secondary">
<strong>Significance:</strong> {event.significance}
</Typography>
)}
</Box>
<Chip
label={event.type.charAt(0).toUpperCase() + event.type.slice(1)}
size="small"
color={
event.type === 'works'
? 'primary'
: event.type === 'context'
? 'secondary'
: event.type === 'legacy'
? 'warning'
: 'success'
}
sx={{ ml: 2 }}
/>
</Box>
</Paper>
</Grid>
))}
</Grid>
</Box>
</TabPanel>
</Box>
);
}

33
src/types/timeline.ts Normal file
View file

@ -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;
}[];
}