mirror of
https://github.com/harivansh-afk/Austens-Wedding-Guide.git
synced 2026-04-15 06:04:39 +00:00
updated interactive timeline and added social class view
This commit is contained in:
parent
9609248b69
commit
59657ce287
8 changed files with 2063 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
253
src/components/SocialClassView.tsx
Normal file
253
src/components/SocialClassView.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
894
src/components/timeline/InteractiveTimeline.tsx
Normal file
894
src/components/timeline/InteractiveTimeline.tsx
Normal 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
194
src/pages/SocialClass.tsx
Normal 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
322
src/pages/Timeline.tsx
Normal 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
33
src/types/timeline.ts
Normal 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;
|
||||
}[];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue