Add movement gallery wings with period interiors and expand timeline features.

Movement galleries split large catalogs into chronological wings (~55 works), use era-themed 3D interiors with side-wall windows, wing navigator on the back exit, and front archways between wings. Also adds painting annotations, timeline event guides, portrait hover highlights, and documentation/API updates.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-21 16:54:19 +03:00
co-authored by Cursor
parent 0972b5df99
commit df29848d89
121 changed files with 4765 additions and 396 deletions
+24 -4
View File
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState, type ChangeEvent, type SyntheticEvent } fr
import type { InfluenceLink, Painting, PaintingDetail } from '../types';
import { api, debugImageProxyUrl, imageUrl, paintingImageUrl, type DebugImageSearchResult, type DebugImageSearchResultItem, type FixPaintingImageResult } from '../api/client';
import DebugSearchResultsModal from './DebugSearchResultsModal';
import PaintingAnnotationsPanel, { PaintingAnnotationMarkers } from './PaintingAnnotations';
import PaintingLightbox from './PaintingLightbox';
import './PaintingDetail.css';
@@ -190,7 +191,7 @@ export default function PaintingDetailView({
onPaintingImageFixed,
onPaintingCheckupFlagsUpdated,
}: Props) {
const { painting, influencedBy, influenced } = data;
const { painting, influencedBy, influenced, annotations = [] } = data;
const [fullscreen, setFullscreen] = useState(false);
const [imageVersion, setImageVersion] = useState(0);
const [debugSearch, setDebugSearch] = useState<DebugImageSearchResult | null>(null);
@@ -205,6 +206,7 @@ export default function PaintingDetailView({
const [applyingUrl, setApplyingUrl] = useState<string | null>(null);
const [clearing, setClearing] = useState(false);
const [uploading, setUploading] = useState(false);
const [activeAnnotationId, setActiveAnnotationId] = useState<number | null>(null);
const uploadInputRef = useRef<HTMLInputElement>(null);
const baseImageUrl = paintingImageUrl(painting);
@@ -227,6 +229,7 @@ export default function PaintingDetailView({
setMoreOpen(false);
setMoreResults(null);
setMoreError(null);
setActiveAnnotationId(null);
}, [painting.id]);
useEffect(() => {
@@ -475,11 +478,28 @@ export default function PaintingDetailView({
title={imageSrc ? 'View full screen' : undefined}
aria-label={imageSrc ? `View ${painting.title} full screen` : `${painting.title} (no image)`}
>
{imageSrc ? (
<img src={imageSrc} alt={painting.title} onError={handleImageError} draggable={false} />
) : null}
<div className="painting-frame-image-wrap">
{imageSrc ? (
<img src={imageSrc} alt={painting.title} onError={handleImageError} draggable={false} />
) : null}
{imageSrc && annotations.length > 0 && (
<PaintingAnnotationMarkers
annotations={annotations}
activeId={activeAnnotationId}
onSelect={setActiveAnnotationId}
/>
)}
</div>
</div>
{annotations.length > 0 && (
<PaintingAnnotationsPanel
annotations={annotations}
activeId={activeAnnotationId}
onSelect={setActiveAnnotationId}
/>
)}
{showCatalogNav && (
<button
type="button"