Add vertical bottom-up timeline with center-out streams and smooth layout transitions.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
4088d7d57b
commit
971c1e8dd8
9 files changed
+1695
-41
No files matched your search
@@ -24,6 +24,45 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.home-timeline-stack-vertical {
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.home-movements-section-vertical {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.site-layout-switch {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 16px;
|
||||
z-index: 120;
|
||||
}
|
||||
|
||||
.site-layout-link {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(201, 169, 110, 0.35);
|
||||
background: rgba(15, 15, 26, 0.85);
|
||||
color: rgba(201, 169, 110, 0.9);
|
||||
font-size: 12px;
|
||||
font-family: 'Georgia', serif;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-layout-link:hover {
|
||||
background: rgba(201, 169, 110, 0.18);
|
||||
color: #f5e6c8;
|
||||
}
|
||||
|
||||
.gallery-session-suspended {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import Timeline from '../components/Timeline';
|
||||
import TimelineEventGuides from '../components/TimelineEventGuides';
|
||||
import MovementBands from '../components/MovementBands';
|
||||
import VerticalTimeline from '../components/VerticalTimeline';
|
||||
import VerticalMovementBands from '../components/VerticalMovementBands';
|
||||
import VirtualGallery from '../components/VirtualGallery';
|
||||
import PaintingDetailView from '../components/PaintingDetail';
|
||||
import ArtistBio from '../components/ArtistBio';
|
||||
@@ -47,6 +49,7 @@ import './HomePage.css';
|
||||
|
||||
type View =
|
||||
| { type: 'timeline' }
|
||||
| { type: 'timeline-vertical' }
|
||||
| { type: 'checkup' }
|
||||
| { type: 'translations' }
|
||||
| { type: 'influences' }
|
||||
@@ -207,7 +210,7 @@ export default function HomePage() {
|
||||
setGallerySession({ kind: 'movement', movementId: view.movementId, data: view.data });
|
||||
} else if (view.type === 'tour-gallery') {
|
||||
setGallerySession({ kind: 'tour', tourId: view.tourId, data: view.data });
|
||||
} else if (view.type === 'timeline') {
|
||||
} else if (view.type === 'timeline' || view.type === 'timeline-vertical') {
|
||||
setGallerySession(null);
|
||||
}
|
||||
}, [view]);
|
||||
@@ -271,6 +274,17 @@ export default function HomePage() {
|
||||
setView({ type: 'timeline' });
|
||||
}, [bounds.min, bounds.max]);
|
||||
|
||||
const openHorizontalTimeline = () => {
|
||||
setView({ type: 'timeline' });
|
||||
};
|
||||
|
||||
const openVerticalTimeline = () => {
|
||||
setView({ type: 'timeline-vertical' });
|
||||
};
|
||||
|
||||
const isTimelineHome = view.type === 'timeline' || view.type === 'timeline-vertical';
|
||||
const isVerticalTimeline = view.type === 'timeline-vertical';
|
||||
|
||||
const toggleDebugMode = () => {
|
||||
setDebugMode((prev) => {
|
||||
const next = !prev;
|
||||
@@ -1204,9 +1218,30 @@ export default function HomePage() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{view.type === 'timeline' && (
|
||||
{isTimelineHome && (
|
||||
<div className="home-page">
|
||||
<header className="site-header">
|
||||
<div className="site-layout-switch">
|
||||
{isVerticalTimeline ? (
|
||||
<button
|
||||
type="button"
|
||||
className="site-layout-link"
|
||||
onClick={openHorizontalTimeline}
|
||||
title="Switch to classic left-to-right timeline"
|
||||
>
|
||||
{t('layoutHorizontal')}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="site-layout-link"
|
||||
onClick={openVerticalTimeline}
|
||||
title="Switch to bottom-up vertical timeline"
|
||||
>
|
||||
{t('layoutVertical')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="site-dev-tools">
|
||||
{isCurator ? (
|
||||
<>
|
||||
@@ -1334,7 +1369,7 @@ export default function HomePage() {
|
||||
/>
|
||||
</header>
|
||||
|
||||
<div className="home-timeline-stack">
|
||||
<div className={`home-timeline-stack${isVerticalTimeline ? ' home-timeline-stack-vertical' : ''}`}>
|
||||
{loading && (
|
||||
<GalleryLoadingMarker overlay message="Loading art history…" />
|
||||
)}
|
||||
@@ -1345,37 +1380,67 @@ export default function HomePage() {
|
||||
<GalleryLoadingMarker banner message="Loading portraits…" />
|
||||
)}
|
||||
|
||||
<Timeline
|
||||
eras={timelineData.eras}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
onViewChange={handleViewChange}
|
||||
absoluteMin={bounds.min}
|
||||
absoluteMax={bounds.max}
|
||||
lifespanHighlight={hoveredLifespan}
|
||||
/>
|
||||
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
|
||||
{!loading && (
|
||||
{isVerticalTimeline ? (
|
||||
<>
|
||||
<TimelineEventGuides viewStart={viewStart} viewEnd={viewEnd} />
|
||||
<div className="home-movements-section">
|
||||
<MovementBands
|
||||
movements={timelineData.movements}
|
||||
artists={artists}
|
||||
portraitRevisions={portraitRevisions}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
absoluteMin={bounds.min}
|
||||
absoluteMax={bounds.max}
|
||||
onViewChange={handleViewChange}
|
||||
onArtistClick={handleArtistClick}
|
||||
onMovementClick={handleMovementClick}
|
||||
onArtistHover={setHoveredLifespan}
|
||||
onPortraitsLoadingChange={setPortraitsLoading}
|
||||
/>
|
||||
</div>
|
||||
<VerticalTimeline
|
||||
eras={timelineData.eras}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
onViewChange={handleViewChange}
|
||||
absoluteMin={bounds.min}
|
||||
absoluteMax={bounds.max}
|
||||
lifespanHighlight={hoveredLifespan}
|
||||
/>
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
{!loading && (
|
||||
<div className="home-movements-section-vertical">
|
||||
<VerticalMovementBands
|
||||
movements={timelineData.movements}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
absoluteMin={bounds.min}
|
||||
absoluteMax={bounds.max}
|
||||
onViewChange={handleViewChange}
|
||||
onMovementClick={handleMovementClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Timeline
|
||||
eras={timelineData.eras}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
onViewChange={handleViewChange}
|
||||
absoluteMin={bounds.min}
|
||||
absoluteMax={bounds.max}
|
||||
lifespanHighlight={hoveredLifespan}
|
||||
/>
|
||||
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
|
||||
{!loading && (
|
||||
<>
|
||||
<TimelineEventGuides viewStart={viewStart} viewEnd={viewEnd} />
|
||||
<div className="home-movements-section">
|
||||
<MovementBands
|
||||
movements={timelineData.movements}
|
||||
artists={artists}
|
||||
portraitRevisions={portraitRevisions}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
absoluteMin={bounds.min}
|
||||
absoluteMax={bounds.max}
|
||||
onViewChange={handleViewChange}
|
||||
onArtistClick={handleArtistClick}
|
||||
onMovementClick={handleMovementClick}
|
||||
onArtistHover={setHoveredLifespan}
|
||||
onPortraitsLoadingChange={setPortraitsLoading}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user