@gramps-connect/story 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +23 -5
  2. package/dist/editor.js +2065 -177
  3. package/dist/editor.js.map +4 -4
  4. package/dist/embed.js +1 -1
  5. package/dist/map-edit.js +361 -0
  6. package/dist/map-edit.js.map +7 -0
  7. package/dist/player.js +3 -1
  8. package/dist/player.js.map +2 -2
  9. package/dist/types/editor/MapEditor.d.ts +89 -0
  10. package/dist/types/editor/PlaceField.d.ts +24 -0
  11. package/dist/types/editor/PlaceMap.d.ts +12 -0
  12. package/dist/types/editor/PlaceMapDialog.d.ts +18 -0
  13. package/dist/types/editor/StoryEditor.d.ts +8 -1
  14. package/dist/types/editor/index.d.ts +7 -1
  15. package/dist/types/editor/mapEditorStrings.d.ts +50 -0
  16. package/dist/types/editor/model.d.ts +17 -1
  17. package/dist/types/editor/placeSearch.d.ts +22 -0
  18. package/dist/types/editor/placeShapes.d.ts +13 -0
  19. package/dist/types/editor/strings.d.ts +28 -0
  20. package/dist/types/mapEdit/geometry.d.ts +91 -0
  21. package/dist/types/mapEdit/index.d.ts +3 -0
  22. package/dist/types/mapEdit/mapFile.d.ts +77 -0
  23. package/dist/types/mapEdit/model.d.ts +55 -0
  24. package/package.json +18 -2
  25. package/src/editor/MapEditor.tsx +1545 -0
  26. package/src/editor/PlaceField.tsx +170 -0
  27. package/src/editor/PlaceMap.tsx +88 -0
  28. package/src/editor/PlaceMapDialog.tsx +228 -0
  29. package/src/editor/StoryEditor.tsx +50 -14
  30. package/src/editor/index.ts +11 -2
  31. package/src/editor/mapEditorStrings.ts +109 -0
  32. package/src/editor/model.ts +38 -1
  33. package/src/editor/placeSearch.ts +61 -0
  34. package/src/editor/placeShapes.ts +63 -0
  35. package/src/editor/strings.ts +56 -0
  36. package/src/mapEdit/geometry.ts +211 -0
  37. package/src/mapEdit/index.ts +15 -0
  38. package/src/mapEdit/mapFile.ts +199 -0
  39. package/src/mapEdit/model.ts +250 -0
  40. package/src/player/MapBackground.tsx +3 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/player/StoryPlayer.tsx", "../src/player/dates.ts", "../src/player/geo.ts", "../src/player/MapBackground.tsx", "../src/player/mapStyles.ts", "../src/player/theme.ts", "../src/player/MapModeControl.tsx", "../src/player/text.ts", "../src/player/strings.ts", "../src/player/TimelineStrip.tsx", "../src/player/timelineLayout.ts", "../src/player/brand.tsx"],
4
- "sourcesContent": ["// Presents a story: one slide at a time, with a full-bleed map behind the\n// slides when any of them has a place (modeled on a Knight Lab StoryMapJS\n// slide), or a plain centered card when none does, plus a timeline strip\n// when two or more slides are dated. Fills its container; the host decides\n// whether that's a fullscreen modal, a page, or a box on a blog.\nimport { useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode, type TouchEvent } from \"react\";\nimport type { MediaItem, Slide, StoryDoc } from \"../types\";\nimport { timelineYear } from \"./dates\";\nimport { placeCenter, storyPlaces } from \"./geo\";\nimport { MapBackground, type MapSlide } from \"./MapBackground\";\nimport { MapModeControl, type MapMode, type MapModeControlProps } from \"./MapModeControl\";\nimport { parseStoryText } from \"./text\";\nimport { stringsFor, type PlayerStrings } from \"./strings\";\nimport { defaultColors, type PlayerColors } from \"./theme\";\nimport { TIMELINE_HEIGHT, TimelineStrip, type TimelinePoint } from \"./TimelineStrip\";\n\n/** How much of the width the slide panel takes over a map; the map\n * centers each point in what's left. */\nconst PANEL_FRACTION = 0.62;\nconst BAR_HEIGHT = 56;\n\nexport interface StoryPlayerProps {\n story: StoryDoc;\n /** A URL the browser can load for a media key: a blob: URL for a file\n * out of a zip, or anything else the host serves. The host owns these\n * URLs (and revoking them); the player may ask for the same key again.\n * Null when it can't be had: the slide shows without it. */\n resolveMedia: (key: string, item: MediaItem) => Promise<string | null>;\n dark?: boolean;\n colors?: Partial<PlayerColors>;\n /** The player's own words. By default, the built-in table for the\n * story's language, else the browser's, else English (strings.ts). */\n strings?: Partial<PlayerStrings>;\n /** Shows a close button that calls this. */\n onClose?: () => void;\n /** Arrow keys move between slides: true listens on the whole window (a\n * fullscreen presentation), \"focus\" only while the player has focus (one\n * of several on a page), false not at all. Default true. */\n keyboard?: boolean | \"focus\";\n /** Called with the new index whenever the slide changes. */\n onSlideChange?: (index: number) => void;\n /** Replaces the built-in map-mode control, e.g. with one from the host's\n * own UI kit. */\n renderMapModeControl?: (props: MapModeControlProps) => ReactNode;\n /** Shown before the title, e.g. ViewerBrand on a standalone page. */\n brand?: ReactNode;\n /** The host's own buttons, at the right of the title bar (before the\n * close button), e.g. the viewer site's Edit. */\n actions?: ReactNode;\n initialSlide?: number;\n}\n\n/** The story's basemap hint (spec section 6) as the control's starting mode:\n * \"historical\" -- a map of each slide's era -- is what the control calls\n * Auto; \"auto\" (viewer's choice) starts on the standard map. */\nfunction initialMapMode(story: StoryDoc): MapMode {\n return story.presentation?.basemap === \"historical\" ? \"auto\" : \"standard\";\n}\n\nfunction SlideText({ text, colors }: { text: string; colors: PlayerColors }) {\n return (\n <>\n {parseStoryText(text).map((lines, p) => (\n <p key={p} dir=\"auto\" style={{ margin: \"0 0 0.6em\", color: colors.text, lineHeight: 1.55 }}>\n {lines.map((spans, l) => (\n <span key={l}>\n {l > 0 && <br />}\n {spans.map((span, s) => {\n if (span.bold) return <b key={s}>{span.text}</b>;\n if (span.italic) return <i key={s}>{span.text}</i>;\n return <span key={s}>{span.text}</span>;\n })}\n </span>\n ))}\n </p>\n ))}\n </>\n );\n}\n\nfunction SlidePhoto({ mediaKey, item, caption, credit, resolveMedia, colors }: {\n mediaKey: string;\n item: MediaItem;\n caption?: string;\n credit?: string;\n resolveMedia: StoryPlayerProps[\"resolveMedia\"];\n colors: PlayerColors;\n}) {\n const [src, setSrc] = useState<string | null>(null);\n useEffect(() => {\n setSrc(null);\n let cancelled = false;\n resolveMedia(mediaKey, item).then((url) => { if (!cancelled) setSrc(url); }, () => {});\n return () => {\n cancelled = true;\n };\n }, [mediaKey, item, resolveMedia]);\n if (!src) return null;\n return (\n <figure style={{ margin: 0, display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 4 }}>\n <img\n src={src}\n alt={item.alt ?? \"\"}\n width={item.width}\n height={item.height}\n style={{ maxHeight: \"42vh\", maxWidth: \"100%\", width: \"auto\", height: \"auto\", objectFit: \"contain\", borderRadius: 4 }}\n />\n {(caption || credit) && (\n <figcaption style={{ fontSize: 13, color: colors.muted }}>\n {[caption, credit && `\u00A9 ${credit}`].filter(Boolean).join(\" \u00B7 \")}\n </figcaption>\n )}\n </figure>\n );\n}\n\n/** A small button showing one of a slide's photos. */\nfunction PhotoThumb({ mediaKey, item, label, selected, onSelect, resolveMedia, colors }: {\n mediaKey: string;\n item: MediaItem;\n label: string;\n selected: boolean;\n onSelect: () => void;\n resolveMedia: StoryPlayerProps[\"resolveMedia\"];\n colors: PlayerColors;\n}) {\n const [src, setSrc] = useState<string | null>(null);\n useEffect(() => {\n let cancelled = false;\n resolveMedia(mediaKey, item).then((url) => { if (!cancelled) setSrc(url); }, () => {});\n return () => { cancelled = true; };\n }, [mediaKey, item, resolveMedia]);\n return (\n <button\n type=\"button\" aria-label={label} title={label} aria-pressed={selected} onClick={onSelect}\n style={{\n width: 44, height: 44, padding: 0, borderRadius: 4, overflow: \"hidden\", cursor: \"pointer\",\n border: `2px solid ${selected ? colors.mark : \"transparent\"}`, background: colors.border,\n opacity: selected ? 1 : 0.7,\n }}\n >\n {src && <img src={src} alt=\"\" style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\", display: \"block\" }} />}\n </button>\n );\n}\n\nfunction SlideContent({ slide, story, resolveMedia, colors, strings }: {\n slide: Slide | undefined;\n story: StoryDoc;\n resolveMedia: StoryPlayerProps[\"resolveMedia\"];\n colors: PlayerColors;\n strings: PlayerStrings;\n}) {\n // Every photo whose media exists; with several, thumbnails pick which\n // one is shown. Keyed by slide in the caller, so each slide starts on\n // its first photo.\n const photos = (slide?.media ?? []).filter((m) => Object.prototype.hasOwnProperty.call(story.media, m.ref));\n const [shown, setShown] = useState(0);\n const photo = photos[Math.min(shown, photos.length - 1)];\n const eyebrow = [slide?.date?.text, slide?.place?.name].filter(Boolean).join(\" \u00B7 \");\n return (\n <>\n {photo && (\n <SlidePhoto\n mediaKey={photo.ref}\n item={story.media[photo.ref]}\n caption={photo.caption}\n credit={photo.credit}\n resolveMedia={resolveMedia}\n colors={colors}\n />\n )}\n {photos.length > 1 && (\n <div style={{ display: \"flex\", gap: 6, flexWrap: \"wrap\", justifyContent: \"center\" }}>\n {photos.map((m, i) => (\n <PhotoThumb\n key={`${i}:${m.ref}`}\n mediaKey={m.ref}\n item={story.media[m.ref]}\n label={strings.photoOf(i + 1, photos.length)}\n selected={photo === m}\n onSelect={() => setShown(i)}\n resolveMedia={resolveMedia}\n colors={colors}\n />\n ))}\n </div>\n )}\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4, alignItems: \"center\", maxWidth: 640, textAlign: \"center\" }}>\n {eyebrow && <div dir=\"auto\" style={{ fontSize: 14, color: colors.muted }}>{eyebrow}</div>}\n {slide?.title && <div dir=\"auto\" style={{ fontSize: 20, fontWeight: 700, color: colors.text }}>{slide.title}</div>}\n {slide?.text && <div><SlideText text={slide.text} colors={colors} /></div>}\n </div>\n </>\n );\n}\n\nfunction RoundButton({ label, onClick, disabled, size, children, style }: {\n label: string;\n onClick: () => void;\n disabled?: boolean;\n size: number;\n children: ReactNode;\n style?: CSSProperties;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n title={label}\n onClick={onClick}\n disabled={disabled}\n style={{\n width: size, height: size, borderRadius: \"50%\", border: \"none\",\n background: \"#25262b\", color: \"#ffffff\", fontSize: size >= 48 ? 22 : 18,\n cursor: disabled ? \"default\" : \"pointer\", opacity: disabled ? 0.35 : 0.8,\n display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0, fontFamily: \"inherit\",\n ...style,\n }}\n >\n {children}\n </button>\n );\n}\n\nfunction NavArrows({ index, total, onPrev, onNext, variant, strings, colors }: {\n index: number;\n total: number;\n onPrev: () => void;\n onNext: () => void;\n /** \"edge\": large buttons at the screen edges, dark so they stay legible\n * over any map. \"inline\": small buttons and an \"n / N\" counter. */\n variant: \"inline\" | \"edge\";\n strings: PlayerStrings;\n colors: PlayerColors;\n}) {\n if (variant === \"edge\") {\n const edge: CSSProperties = { position: \"absolute\", top: \"50%\", transform: \"translateY(-50%)\", zIndex: 2 };\n return (\n <>\n <RoundButton label={strings.previous} size={56} disabled={index === 0} onClick={onPrev} style={{ ...edge, left: 24 }}>\u2190</RoundButton>\n <RoundButton label={strings.next} size={56} disabled={index === total - 1} onClick={onNext} style={{ ...edge, right: 24 }}>\u2192</RoundButton>\n </>\n );\n }\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", gap: 16, marginTop: 16 }}>\n <RoundButton label={strings.previous} size={34} disabled={index === 0} onClick={onPrev}>\u2190</RoundButton>\n <span style={{ fontSize: 14, color: colors.muted }}>{index + 1} / {total}</span>\n <RoundButton label={strings.next} size={34} disabled={index === total - 1} onClick={onNext}>\u2192</RoundButton>\n </div>\n );\n}\n\nexport function StoryPlayer({\n story, resolveMedia, dark = false, colors: colorOverrides, strings: stringOverrides, onClose,\n keyboard = true, onSlideChange, renderMapModeControl, brand, actions, initialSlide = 0,\n}: StoryPlayerProps) {\n const colors = useMemo(() => ({ ...defaultColors(dark), ...colorOverrides }), [dark, colorOverrides]);\n const strings = useMemo(\n () => ({\n ...stringsFor(story.language, typeof navigator === \"undefined\" ? undefined : navigator.language),\n ...stringOverrides,\n }),\n [story.language, stringOverrides],\n );\n const rootRef = useRef<HTMLDivElement>(null);\n const slides = story.slides;\n const [index, setIndex] = useState(initialSlide);\n const slide = slides[index];\n\n useEffect(() => {\n setIndex(Math.min(Math.max(initialSlide, 0), Math.max(slides.length - 1, 0)));\n }, [story, initialSlide, slides.length]);\n\n const datedSlides = useMemo(() => {\n const points: TimelinePoint[] = [];\n slides.forEach((s, i) => {\n const year = timelineYear(s.date);\n if (year !== null) points.push({ index: i, year });\n });\n return points;\n }, [slides]);\n const showTimeline = datedSlides.length >= 2;\n\n // Map modes: \"auto\" follows the current slide's year as the story moves.\n const [mapMode, setMapMode] = useState<MapMode>(() => initialMapMode(story));\n const [historicalYear, setHistoricalYear] = useState(() => new Date().getFullYear());\n const currentYear = timelineYear(slide?.date);\n const autoYear = currentYear !== null ? Math.floor(currentYear) : null;\n const yearBounds = useMemo<[number, number]>(() => {\n if (datedSlides.length === 0) {\n const thisYear = new Date().getFullYear();\n return [thisYear - 200, thisYear];\n }\n const years = datedSlides.map((d) => d.year);\n return [Math.floor(Math.min(...years)), Math.ceil(Math.max(...years))];\n }, [datedSlides]);\n // Entering Historical starts the slider on this slide's year.\n const wasHistoricalRef = useRef(false);\n useEffect(() => {\n if (mapMode === \"historical\" && !wasHistoricalRef.current) setHistoricalYear(autoYear ?? yearBounds[1]);\n wasHistoricalRef.current = mapMode === \"historical\";\n }, [mapMode, autoYear, yearBounds]);\n const ohmYear = mapMode === \"standard\" ? null : mapMode === \"auto\" ? autoYear : historicalYear;\n\n const goPrev = () => setIndex((i) => Math.max(i - 1, 0));\n const goNext = () => setIndex((i) => Math.min(i + 1, slides.length - 1));\n\n const onSlideChangeRef = useRef(onSlideChange);\n onSlideChangeRef.current = onSlideChange;\n useEffect(() => {\n onSlideChangeRef.current?.(index);\n }, [index]);\n\n useEffect(() => {\n if (!keyboard) return;\n const target: HTMLElement | Window | null = keyboard === \"focus\" ? rootRef.current : window;\n if (!target) return;\n function onKey(e: Event) {\n const key = (e as KeyboardEvent).key;\n const from = e.target as HTMLElement | null;\n // Not while typing a year into the map control.\n if (from && (from.tagName === \"INPUT\" || from.tagName === \"TEXTAREA\" || from.isContentEditable)) return;\n if (key === \"ArrowRight\") setIndex((i) => Math.min(i + 1, slides.length - 1));\n if (key === \"ArrowLeft\") setIndex((i) => Math.max(i - 1, 0));\n }\n target.addEventListener(\"keydown\", onKey);\n return () => target.removeEventListener(\"keydown\", onKey);\n }, [keyboard, slides.length]);\n\n // A horizontal swipe on the slide (not the map, which a drag pans)\n // moves between slides.\n const swipeStart = useRef<{ x: number; y: number } | null>(null);\n const swipeHandlers = {\n onTouchStart: (e: TouchEvent) => {\n const t = e.touches[0];\n swipeStart.current = e.touches.length === 1 ? { x: t.clientX, y: t.clientY } : null;\n },\n onTouchEnd: (e: TouchEvent) => {\n const start = swipeStart.current;\n swipeStart.current = null;\n const t = e.changedTouches[0];\n if (!start || !t) return;\n const dx = t.clientX - start.x;\n const dy = t.clientY - start.y;\n if (Math.abs(dx) < 50 || Math.abs(dx) < Math.abs(dy) * 1.5) return;\n if (dx < 0) goNext();\n else goPrev();\n },\n };\n\n // Stable per slide, so a re-render for an unrelated reason (a map-mode\n // click) never looks like a new slide to the map's camera.\n const places = useMemo(() => storyPlaces(slides.map((s) => s.place)), [slides]);\n const mapSlide = useMemo<MapSlide>(() => ({\n key: slide?.id ?? String(index),\n place: slide?.place,\n // The opening card, with no place of its own, shows where the story goes.\n overview: index === 0 && !placeCenter(slide?.place) ? places : null,\n }), [slide, index, places]);\n const firstCenter = useMemo(() => {\n if (story.presentation?.basemap === \"none\") return null;\n for (const s of slides) {\n const center = placeCenter(s.place);\n if (center) return center;\n }\n return null;\n }, [story, slides]);\n\n const modeControlProps: MapModeControlProps = {\n mode: mapMode, onModeChange: setMapMode, autoYear, year: historicalYear, onYearChange: setHistoricalYear, yearBounds,\n };\n const mediaResolver = useMemo(() => (key: string) => {\n const item = Object.prototype.hasOwnProperty.call(story.media, key) ? story.media[key] : undefined;\n return item ? resolveMedia(key, item) : Promise.resolve(null);\n }, [story, resolveMedia]);\n\n const content = (\n <SlideContent key={slide?.id ?? index} slide={slide} story={story} resolveMedia={resolveMedia} colors={colors} strings={strings} />\n );\n const timeline = showTimeline && (\n <TimelineStrip points={datedSlides} currentIndex={index} onSelect={setIndex} colors={colors} />\n );\n\n return (\n <div\n ref={rootRef}\n lang={story.language}\n tabIndex={keyboard === \"focus\" ? 0 : undefined}\n style={{\n position: \"relative\", width: \"100%\", height: \"100%\", overflow: \"hidden\",\n background: colors.surface, color: colors.text,\n }}\n >\n {/* Title bar: translucent, so it reads over the map too. */}\n <div\n style={{\n position: \"absolute\", top: 0, left: 0, right: 0, height: BAR_HEIGHT, zIndex: 3,\n background: dark ? \"rgba(20,20,20,0.55)\" : \"rgba(255,255,255,0.55)\",\n }}\n />\n <div\n dir=\"auto\"\n style={{\n position: \"absolute\", top: 0, left: 20, height: BAR_HEIGHT, maxWidth: \"26%\", zIndex: 3,\n display: \"flex\", alignItems: \"center\", gap: 10, fontWeight: 600,\n }}\n >\n {brand && <span style={{ flex: \"none\", display: \"flex\" }}>{brand}</span>}\n <span style={{ whiteSpace: \"nowrap\", overflow: \"hidden\", textOverflow: \"ellipsis\" }}>{story.title}</span>\n </div>\n {/* Centered on the whole bar, as a toolbar for the presentation; the\n full-width wrapper lets clicks through to anything under it. */}\n {firstCenter && (\n <div\n style={{\n position: \"absolute\", top: 0, left: 0, right: 0, height: BAR_HEIGHT, zIndex: 3,\n display: \"flex\", alignItems: \"center\", justifyContent: \"center\", pointerEvents: \"none\",\n }}\n >\n <div style={{ pointerEvents: \"auto\" }}>\n {renderMapModeControl\n ? renderMapModeControl(modeControlProps)\n : <MapModeControl {...modeControlProps} strings={strings} colors={colors} />}\n </div>\n </div>\n )}\n {actions && (\n <div\n style={{\n position: \"absolute\", top: 0, right: onClose ? 60 : 12, height: BAR_HEIGHT, zIndex: 3,\n display: \"flex\", alignItems: \"center\", gap: 8,\n }}\n >\n {actions}\n </div>\n )}\n {onClose && (\n <RoundButton\n label={strings.close} size={36} onClick={onClose}\n style={{ position: \"absolute\", top: 10, right: 12, zIndex: 3 }}\n >\n \u00D7\n </RoundButton>\n )}\n\n {firstCenter ? (\n <div style={{ position: \"absolute\", inset: 0 }}>\n <MapBackground\n initialCenter={firstCenter}\n current={mapSlide}\n dark={dark}\n colors={colors}\n panelFraction={PANEL_FRACTION}\n ohmYear={ohmYear}\n resolveMedia={mediaResolver}\n />\n <div\n style={{\n position: \"absolute\", top: 0, bottom: 0, right: 0, width: `${PANEL_FRACTION * 100}%`, zIndex: 1,\n padding: `${BAR_HEIGHT}px 32px ${showTimeline ? TIMELINE_HEIGHT : 32}px`, boxSizing: \"border-box\",\n display: \"flex\", flexDirection: \"column\", justifyContent: \"center\", alignItems: \"center\", gap: 16,\n overflowY: \"auto\",\n // Transparent where the panel starts, mid-screen, and solid\n // well before the centered content, so text never sits on map.\n background: `linear-gradient(to right, transparent 0%, ${colors.panel} 22%)`,\n }}\n {...swipeHandlers}\n >\n {content}\n </div>\n <NavArrows index={index} total={slides.length} onPrev={goPrev} onNext={goNext} variant=\"edge\" strings={strings} colors={colors} />\n {timeline}\n </div>\n ) : (\n <div\n style={{\n position: \"absolute\", inset: 0, overflowY: \"auto\", boxSizing: \"border-box\",\n padding: `${BAR_HEIGHT + 16}px 24px ${showTimeline ? TIMELINE_HEIGHT + 16 : 24}px`,\n display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", gap: 16,\n }}\n {...swipeHandlers}\n >\n {content}\n <NavArrows index={index} total={slides.length} onPrev={goPrev} onNext={goNext} variant=\"inline\" strings={strings} colors={colors} />\n {timeline}\n </div>\n )}\n </div>\n );\n}\n", "// What the player needs from a story's dates: which years an EDTF value\n// covers (to show a historical map overlay only in its own era), and a\n// position on the timeline finer than a whole year when the date allows.\nimport type { StoryDate } from \"../types\";\n\nexport interface YearRange {\n /** Undefined for an open start (\"../1850\"). */\n min?: number;\n /** Undefined for an open end (\"1850/..\"). */\n max?: number;\n}\n\nconst DATE = /^(-?\\d{4})(?:-(\\d{2}|XX))?(?:-(\\d{2}|XX))?[~?%]?$/;\n\nfunction yearOf(part: string): number | null {\n const match = DATE.exec(part.trim());\n return match ? Number(match[1]) : null;\n}\n\n/** The years an EDTF date covers, or null when it can't be read (which\n * callers treat the same as having no date). Handles single dates\n * (\"1850\", \"1850-03-12\", \"1850~\"), intervals (\"1850/1860\", \"../1850\",\n * \"1850/..\") and one-of sets (\"[1850..1860]\"). */\nexport function edtfYearRange(edtf: string | undefined): YearRange | null {\n if (!edtf) return null;\n const value = edtf.trim();\n const set = /^\\[(.+)\\.\\.(.+)\\]$/.exec(value);\n if (set) {\n const min = yearOf(set[1]);\n const max = yearOf(set[2]);\n return min !== null && max !== null ? { min: Math.min(min, max), max: Math.max(min, max) } : null;\n }\n if (value.includes(\"/\")) {\n const [startText, endText] = value.split(\"/\");\n const openStart = startText === \"..\" || startText === \"\";\n const openEnd = endText === \"..\" || endText === \"\";\n const start = openStart ? undefined : yearOf(startText);\n const end = openEnd ? undefined : yearOf(endText);\n if (start === null || end === null) return null;\n if (start === undefined && end === undefined) return null;\n if (start !== undefined && end !== undefined) return { min: Math.min(start, end), max: Math.max(start, end) };\n return { min: start, max: end };\n }\n const year = yearOf(value);\n return year === null ? null : { min: year, max: year };\n}\n\n/** Whether something dated `date` belongs on a map of `year`. No date, an\n * unreadable one, or no year to compare against (a standard, non-historical\n * map) all mean visible: an undated overlay is never hidden. */\nexport function dateCoversYear(date: StoryDate | undefined, year: number | null): boolean {\n if (!date || year === null) return true;\n const range = edtfYearRange(date.edtf);\n if (!range) return true;\n if (range.min !== undefined && year < range.min) return false;\n if (range.max !== undefined && year > range.max) return false;\n return true;\n}\n\n/** A slide's position on the timeline, as a fractional year: its\n * `sortYear`, nudged within the year by the EDTF month and day when they\n * agree with it -- so two events in the same year don't land on the exact\n * same spot. Null for a slide that isn't on the timeline. */\nexport function timelineYear(date: StoryDate | undefined): number | null {\n if (!date) return null;\n const match = date.edtf ? DATE.exec(date.edtf.trim()) : null;\n const sortYear = date.sortYear ?? (match ? Number(match[1]) : undefined);\n if (sortYear === undefined) return null;\n if (!match || Number(match[1]) !== sortYear) return sortYear;\n const month = match[2] && match[2] !== \"XX\" ? Number(match[2]) : 0;\n const day = match[3] && match[3] !== \"XX\" ? Number(match[3]) : 0;\n if (!month) return sortYear;\n return sortYear + (month - 1) / 12 + (day ? (day - 1) / 365 : 0);\n}\n", "// Where a slide is on the map: its point, or the box around its shape and\n// overlays -- pure geometry, no map library.\nimport type { LonLat, Place } from \"../types\";\n\nexport type Bounds = [west: number, south: number, east: number, north: number];\n\nfunction visit(coords: unknown, bounds: Bounds): void {\n if (!Array.isArray(coords)) return;\n if (coords.length >= 2 && typeof coords[0] === \"number\" && typeof coords[1] === \"number\") {\n const [lon, lat] = coords as number[];\n bounds[0] = Math.min(bounds[0], lon);\n bounds[1] = Math.min(bounds[1], lat);\n bounds[2] = Math.max(bounds[2], lon);\n bounds[3] = Math.max(bounds[3], lat);\n return;\n }\n for (const child of coords) visit(child, bounds);\n}\n\n/** Every coordinate in a GeoJSON object, whatever its nesting. */\nfunction visitGeoJson(node: unknown, bounds: Bounds): void {\n if (!node || typeof node !== \"object\") return;\n const obj = node as { type?: string; coordinates?: unknown; geometry?: unknown; geometries?: unknown[]; features?: unknown[] };\n if (obj.coordinates !== undefined) visit(obj.coordinates, bounds);\n if (obj.geometry) visitGeoJson(obj.geometry, bounds);\n for (const g of obj.geometries ?? []) visitGeoJson(g, bounds);\n for (const f of obj.features ?? []) visitGeoJson(f, bounds);\n}\n\n/** The box around a place's shape and overlays, or null when it has\n * neither (or they hold no coordinates). */\nexport function placeBounds(place: Place | undefined): Bounds | null {\n if (!place) return null;\n const bounds: Bounds = [Infinity, Infinity, -Infinity, -Infinity];\n visitGeoJson(place.geometry, bounds);\n for (const overlay of place.overlays ?? []) visit(overlay.corners as LonLat[], bounds);\n return Number.isFinite(bounds[0]) ? bounds : null;\n}\n\n/** Where to put the pin: the place's own point, else the middle of its\n * shape and overlays. Null for a slide with no position at all. */\nexport function placeCenter(place: Place | undefined): LonLat | null {\n if (place?.lat !== undefined && place.lon !== undefined) return [place.lon, place.lat];\n const bounds = placeBounds(place);\n return bounds ? [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] : null;\n}\n\nexport interface StoryPlaces {\n points: LonLat[];\n bounds: Bounds;\n}\n\n/** Every place in the story at once, for its opening view: one point per\n * distinct place, and the box around all of them, shapes and overlays\n * included. Null when no slide has a position. */\nexport function storyPlaces(places: (Place | undefined)[]): StoryPlaces | null {\n const bounds: Bounds = [Infinity, Infinity, -Infinity, -Infinity];\n const points = new Map<string, LonLat>();\n for (const place of places) {\n const center = placeCenter(place);\n if (!center) continue;\n points.set(`${center[0].toFixed(5)},${center[1].toFixed(5)}`, center);\n visit(center, bounds);\n const box = placeBounds(place);\n if (box) visit([[box[0], box[1]], [box[2], box[3]]], bounds);\n }\n return points.size ? { points: [...points.values()], bounds } : null;\n}\n", "// The full-bleed map behind a located story's slides: flies to the current\n// slide's place (or frames its shape and overlays), draws that shape and\n// those overlays, and holds still -- marker dimmed -- on a slide with no\n// place. Its own small maplibre instance; the host page loads no map of\n// its own for this.\nimport { useEffect, useRef, useState } from \"react\";\nimport * as maplibregl from \"maplibre-gl\";\n// Positions the marker and styles the attribution control; without it the\n// pin sits in normal flow at the top left instead of on its coordinate.\nimport \"maplibre-gl/dist/maplibre-gl.css\";\nimport type { GeoJSONSource, Map as MapLibreMap, Marker as MapLibreMarker } from \"maplibre-gl\";\nimport type { FeatureCollection, GeoJSON } from \"geojson\";\nimport type { Overlay, Place } from \"../types\";\nimport { dateCoversYear } from \"./dates\";\nimport { placeBounds, placeCenter, type StoryPlaces } from \"./geo\";\nimport { applyOhmYear, crossfadeStyleSwap, mapStyleKey, mapStyleUrl } from \"./mapStyles\";\nimport { PIN_PATH_D, type PlayerColors } from \"./theme\";\n\nconst SHAPE_SOURCE = \"story-shape\";\nconst FILL_LAYER = \"story-shape-fill\";\nconst LINE_LAYER = \"story-shape-line\";\nconst POINT_LAYER = \"story-shape-points\";\nconst LABEL_LAYER = \"story-shape-labels\";\nconst OVERLAY_PREFIX = \"story-overlay-\";\nconst PLACES_SOURCE = \"story-places\";\nconst PLACES_LAYER = \"story-places-dots\";\n// Room around the outermost dots of the opening view, inside the part of\n// the map the slide panel leaves free.\nconst OVERVIEW_PADDING = 60;\nconst EMPTY: FeatureCollection = { type: \"FeatureCollection\", features: [] };\n\n/** The current slide, as far as the map cares. `key` is the slide id:\n * what tells \"a new slide\" from \"the same slide, re-rendered\". */\nexport interface MapSlide {\n key: string;\n place: Place | undefined;\n /** Every place in the story, on an opening slide with no place of its\n * own: framed together, a dot each, instead of the pin. */\n overview: StoryPlaces | null;\n}\n\n/** Frame the story's places for an overview; else the place's shape or\n * overlays (usually a field, a route or an old map, far tighter than a\n * town-level zoom); else fly to its point. */\nfunction moveCamera(map: MapLibreMap, slide: MapSlide, duration: number) {\n if (slide.overview) {\n const [west, south, east, north] = slide.overview.bounds;\n map.fitBounds([[west, south], [east, north]], { maxZoom: 9, padding: OVERVIEW_PADDING, duration });\n return;\n }\n const place = slide.place;\n const bounds = placeBounds(place);\n if (bounds) {\n map.fitBounds([[bounds[0], bounds[1]], [bounds[2], bounds[3]]], { maxZoom: 17, duration });\n return;\n }\n const center = placeCenter(place);\n if (center) map.flyTo({ center, zoom: 9, essential: true, duration });\n}\n\n/** The pin on the place; hidden for an overview (the dots stand in for\n * it), else held where it was, dimmed, for a slide with no place. */\nfunction placeMarker(marker: MapLibreMarker | null, slide: MapSlide) {\n if (!marker) return;\n const center = placeCenter(slide.place);\n if (center) marker.setLngLat(center);\n // Through the marker, not its element's style: maplibre rewrites that\n // from the marker's own opacity every frame.\n marker.setOpacity(slide.overview ? 0 : center ? 1 : 0.4);\n}\n\nconst placeDots = (overview: StoryPlaces | null): FeatureCollection => ({\n type: \"FeatureCollection\",\n features: (overview?.points ?? []).map((coordinates) => ({\n type: \"Feature\", properties: {}, geometry: { type: \"Point\", coordinates },\n })),\n});\n\nexport function MapBackground({ initialCenter, current, dark, colors, panelFraction, ohmYear, resolveMedia }: {\n initialCenter: [number, number];\n current: MapSlide;\n dark: boolean;\n colors: PlayerColors;\n /** How much of the right side the slide panel covers: a point is\n * centered in the remaining left part, not the whole width. */\n panelFraction: number;\n /** Non-null switches to OpenHistoricalMap filtered to this year. */\n ohmYear: number | null;\n resolveMedia: (key: string) => Promise<string | null>;\n}) {\n // A callback ref (via state), not useRef: a host that mounts the player\n // inside a modal may not have it in the DOM on the first render, and a\n // plain ref would leave the create-effect below nothing to re-run on.\n const [containerEl, setContainerEl] = useState<HTMLDivElement | null>(null);\n const mapRef = useRef<MapLibreMap | null>(null);\n const markerRef = useRef<MapLibreMarker | null>(null);\n // True after each \"style.load\" (initial load, and again after every\n // style swap, which wipes the layers this component adds).\n const [ready, setReady] = useState(false);\n\n const darkRef = useRef(dark);\n darkRef.current = dark;\n const ohmYearRef = useRef(ohmYear);\n ohmYearRef.current = ohmYear;\n const colorsRef = useRef(colors);\n colorsRef.current = colors;\n // overlay layer id -> its date, for re-checking visibility on a year change.\n const overlayDatesRef = useRef<Map<string, Overlay[\"date\"]>>(new Map());\n // What the map was built or last swapped with, so a re-render with the\n // same effective style never triggers a reload.\n const appliedStyleKeyRef = useRef(mapStyleKey(dark, ohmYear));\n const currentRef = useRef(current);\n currentRef.current = current;\n\n useEffect(() => {\n if (!containerEl) return;\n setReady(false);\n appliedStyleKeyRef.current = mapStyleKey(darkRef.current, ohmYearRef.current);\n const map = new maplibregl.Map({\n container: containerEl,\n style: mapStyleUrl(darkRef.current, ohmYearRef.current),\n zoom: 5,\n attributionControl: { compact: true },\n // So crossfadeStyleSwap's snapshot has the last frame in it.\n canvasContextAttributes: { preserveDrawingBuffer: true },\n });\n mapRef.current = map;\n map.on(\"style.load\", () => setReady(true));\n // Center points in the part of the map the slide panel doesn't cover.\n map.setPadding({ top: 0, bottom: 0, left: 0, right: containerEl.clientWidth * panelFraction });\n // Straight to the opening slide's place, unanimated. The camera effect\n // below ran for it before this map existed (the container arrives a\n // render late), so a story opened partway through starts there too.\n const start = currentRef.current;\n map.jumpTo({ center: placeCenter(start.place) ?? initialCenter });\n moveCamera(map, start, 0);\n map.on(\"load\", () => {\n const c = colorsRef.current;\n const el = document.createElement(\"div\");\n el.style.width = \"26px\";\n el.style.height = \"26px\";\n el.style.transition = \"opacity 300ms\";\n el.innerHTML = `<svg viewBox=\"0 0 24 24\" width=\"26\" height=\"26\">`\n + `<path d=\"${PIN_PATH_D}\" fill=\"${c.mark}\" stroke=\"${c.surface}\" stroke-width=\"1.5\" />`\n + `</svg>`;\n // The pin's tip, not its middle, sits on the coordinate.\n markerRef.current = new maplibregl.Marker({ element: el, anchor: \"bottom\" }).setLngLat(initialCenter).addTo(map);\n placeMarker(markerRef.current, currentRef.current);\n });\n return () => {\n markerRef.current?.remove();\n markerRef.current = null;\n map.remove();\n mapRef.current = null;\n };\n // initialCenter is only the first frame; moving is the effect below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [containerEl]);\n\n useEffect(() => {\n const map = mapRef.current;\n const key = mapStyleKey(dark, ohmYear);\n if (!map || appliedStyleKeyRef.current === key) return;\n appliedStyleKeyRef.current = key;\n crossfadeStyleSwap(map, mapStyleUrl(dark, ohmYear), () => setReady(false));\n }, [dark, ohmYear]);\n\n useEffect(() => {\n const map = mapRef.current;\n if (!map || !ready || ohmYear == null) return;\n applyOhmYear(map, ohmYear);\n }, [ready, ohmYear]);\n\n // The shape layers, (re-)added after every style load, under the marker.\n useEffect(() => {\n const map = mapRef.current;\n if (!map || !ready || map.getSource(SHAPE_SOURCE)) return;\n const c = colorsRef.current;\n map.addSource(SHAPE_SOURCE, { type: \"geojson\", data: EMPTY });\n // A feature's own `color` property (simplestyle-ish) wins over the mark color.\n const color = [\"coalesce\", [\"get\", \"color\"], c.mark] as maplibregl.ExpressionSpecification;\n map.addLayer({\n id: FILL_LAYER, type: \"fill\", source: SHAPE_SOURCE,\n filter: [\"==\", [\"geometry-type\"], \"Polygon\"],\n paint: { \"fill-color\": color, \"fill-opacity\": 0.25 },\n });\n map.addLayer({\n id: LINE_LAYER, type: \"line\", source: SHAPE_SOURCE,\n filter: [\"any\", [\"==\", [\"geometry-type\"], \"Polygon\"], [\"==\", [\"geometry-type\"], \"LineString\"]],\n paint: { \"line-color\": color, \"line-width\": 2 },\n });\n map.addLayer({\n id: POINT_LAYER, type: \"circle\", source: SHAPE_SOURCE,\n // A named point is a label (next layer), drawn as text only.\n filter: [\"all\", [\"==\", [\"geometry-type\"], \"Point\"], [\"!\", [\"has\", \"name\"]]],\n paint: { \"circle-radius\": 5, \"circle-color\": color, \"circle-stroke-width\": 1, \"circle-stroke-color\": c.surface },\n });\n map.addLayer({\n id: LABEL_LAYER, type: \"symbol\", source: SHAPE_SOURCE,\n filter: [\"all\", [\"==\", [\"geometry-type\"], \"Point\"], [\"has\", \"name\"]],\n layout: {\n \"text-field\": [\"get\", \"name\"],\n \"text-size\": 12,\n \"text-font\": [\"Noto Sans Regular\"],\n \"text-anchor\": \"left\",\n \"text-offset\": [0.6, 0],\n },\n paint: {\n \"text-color\": [\"coalesce\", [\"get\", \"color\"], c.text],\n \"text-halo-color\": c.surface,\n \"text-halo-width\": 1.5,\n },\n });\n map.addSource(PLACES_SOURCE, { type: \"geojson\", data: EMPTY });\n map.addLayer({\n id: PLACES_LAYER, type: \"circle\", source: PLACES_SOURCE,\n paint: { \"circle-radius\": 6, \"circle-color\": c.mark, \"circle-stroke-width\": 2, \"circle-stroke-color\": c.surface },\n });\n }, [ready]);\n\n const place = current.place;\n\n // The current slide's shape, or the overview's dots. Re-supplied after each style load, since a\n // swap empties the source.\n useEffect(() => {\n const map = mapRef.current;\n if (!map || !ready) return;\n const source = map.getSource(SHAPE_SOURCE) as GeoJSONSource | undefined;\n source?.setData((place?.geometry as GeoJSON | undefined) ?? EMPTY);\n const dots = map.getSource(PLACES_SOURCE) as GeoJSONSource | undefined;\n dots?.setData(placeDots(current.overview));\n }, [ready, current.key, place, current.overview]);\n\n // Camera: once per slide (see moveCamera). Keyed to the slide, not to `ready`, so a\n // map-mode switch never yanks the view back from wherever the viewer\n // has since panned.\n useEffect(() => {\n const map = mapRef.current;\n if (!map) return;\n placeMarker(markerRef.current, current);\n // No place and no overview: the camera stays put.\n moveCamera(map, current, 900);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [current.key]);\n\n // Image overlays: one image source + raster layer each, under the shape\n // layers, lowest `order` first so the highest ends up on top. Keyed by\n // the overlays themselves, so consecutive slides at the same place keep\n // theirs instead of reloading them.\n const overlayKey = JSON.stringify(place?.overlays ?? []);\n useEffect(() => {\n const map = mapRef.current;\n const overlays = place?.overlays ?? [];\n if (!map || !ready || overlays.length === 0) return;\n let cancelled = false;\n const added: string[] = [];\n (async () => {\n const sorted = overlays\n .map((overlay, i) => ({ overlay, i }))\n .sort((a, b) => (a.overlay.order ?? a.i) - (b.overlay.order ?? b.i));\n for (const { overlay, i } of sorted) {\n const url = await resolveMedia(overlay.media);\n if (cancelled) return;\n if (!url) continue;\n const id = `${OVERLAY_PREFIX}${i}`;\n if (map.getSource(id)) continue;\n map.addSource(id, { type: \"image\", url, coordinates: overlay.corners });\n map.addLayer({ id, type: \"raster\", source: id }, FILL_LAYER);\n map.setPaintProperty(id, \"raster-opacity\", overlay.opacity ?? 1);\n overlayDatesRef.current.set(id, overlay.date);\n map.setLayoutProperty(id, \"visibility\", dateCoversYear(overlay.date, ohmYearRef.current) ? \"visible\" : \"none\");\n added.push(id);\n }\n })();\n return () => {\n cancelled = true;\n for (const id of added) {\n overlayDatesRef.current.delete(id);\n if (map.getLayer(id)) map.removeLayer(id);\n if (map.getSource(id)) map.removeSource(id);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [overlayKey, ready]);\n\n // A new map year alone: re-check each overlay's era.\n useEffect(() => {\n const map = mapRef.current;\n if (!map || !ready) return;\n for (const [id, date] of overlayDatesRef.current) {\n if (!map.getLayer(id)) continue;\n map.setLayoutProperty(id, \"visibility\", dateCoversYear(date, ohmYear) ? \"visible\" : \"none\");\n }\n }, [ohmYear, ready]);\n\n return <div ref={setContainerEl} style={{ position: \"absolute\", inset: 0 }} />;\n}\n", "// Basemaps for the story map: OpenFreeMap (light), CARTO Dark Matter (dark)\n// and OpenHistoricalMap (historical, filtered to a year), plus the\n// crossfade used when switching between them. Gramps Connect's own map\n// view uses these too (its visuals/mapStyles.ts re-exports them), so the\n// app and the story player can't drift into two versions of the same three\n// map modes.\nimport type { Map as MapLibreMap } from \"maplibre-gl\";\n// A CJS module with no browser global to patch onto (it only self-attaches\n// to `window.maplibregl`, which our namespace import never creates) -- the\n// named export below is what its `module.exports` branch actually produces,\n// and it's what both callers use directly rather than the `map.filterByDate`\n// method the plugin's own README shows for a script-tag install.\nimport { filterByDate } from \"@openhistoricalmap/maplibre-gl-dates\";\n\n/** The same OpenFreeMap \"liberty\" style gramps-web uses for its light\n * basemap, so the two clients' maps look like the same product in light\n * mode. Free, key-less, and hosted -- which does mean a map (unlike every\n * other view in this app) needs the network even though its *data* is\n * local. */\nexport const STYLE_LIGHT = \"https://tiles.openfreemap.org/styles/liberty\";\n/** OpenFreeMap's own \"dark\" style renders every layer, including water, in\n * the same near-zero-saturation gray (confirmed against their own demo at\n * openfreemap.org) -- there's no hue anywhere to tell land from water at a\n * glance. CARTO's Dark Matter style is the free, key-less alternative with\n * an actual (if muted) blue-gray palette -- water and major roads read as\n * distinct from background and from each other. */\nexport const STYLE_DARK = \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\";\n\n/** OHM's own vector style -- one cartography regardless of light/dark,\n * matching gramps-web's choice. */\nexport const OHM_STYLE = \"https://www.openhistoricalmap.org/map-styles/main/main.json\";\n\n/** The three selectable modes wherever a historical map is shown. \"auto\" and\n * \"historical\" both render OHM tiles filtered to a year -- they differ only\n * in where that year comes from (context vs. a user-dragged slider), which\n * is why both map components take a single `ohmYear: number | null` rather\n * than the mode itself: \"standard\" and an \"auto\" with nothing to derive a\n * year from look identical to the map, and are meant to. */\nexport type MapMode = \"auto\" | \"standard\" | \"historical\";\n\n/** A stable key for \"would setStyle() actually change anything\" -- swapping\n * light/dark never matters once OHM is showing (its cartography doesn't\n * have a dark variant), so a slider drag that changes `ohmYear`'s value\n * without leaving historical/auto mode must not trip a style reload. */\nexport function mapStyleKey(dark: boolean, ohmYear: number | null): string {\n return ohmYear != null ? \"ohm\" : dark ? \"dark\" : \"light\";\n}\n\nexport function mapStyleUrl(dark: boolean, ohmYear: number | null): string {\n return ohmYear != null ? OHM_STYLE : dark ? STYLE_DARK : STYLE_LIGHT;\n}\n\n/** Swaps a map's style as a true crossfade -- the old and new basemaps\n * dissolving into each other, not one fading out before the other fades in.\n * A single WebGL map can only ever show one style at a time, so this fakes\n * it: a snapshot of the *old* style (a plain `<img>`, frozen) is overlaid\n * over the live map at full opacity, the live map underneath is swapped to\n * the new style immediately and left to load, and once it's actually\n * finished drawing (`idle`, not `style.load` -- that fires before a single\n * tile of the new style has painted) the snapshot dissolves away, revealing\n * the new style continuously as it goes rather than snapping to it. This\n * also means the old map stays on screen, fully intact, for however long\n * the new style's tiles take to fetch -- no blank/loading gap either way,\n * unlike fading through transparent would give.\n *\n * Requires `preserveDrawingBuffer: true` on the map (see both callers'\n * constructors) -- without it, `toDataURL()` on the canvas can come back\n * blank depending on exactly when the browser clears the drawing buffer\n * after compositing. Falls back to an instant, unfaded swap if the snapshot\n * can't be taken at all (a tainted canvas, or a browser that disallows it) --\n * a plain setStyle() rather than no swap.\n *\n * `beforeSwap` runs at the same moment as setStyle(), so a caller's own\n * `setReady(false)` -- which gates its layer re-add effect -- flips exactly\n * when setStyle() actually discards those layers. */\nexport function crossfadeStyleSwap(map: MapLibreMap, styleUrl: string, beforeSwap: () => void, duration = 300): void {\n const container = map.getContainer();\n // The container is `position: absolute` but neither caller gives it its\n // own `z-index` -- which means it *doesn't* establish a stacking context\n // of its own, and the overlay's z-index below would then be compared not\n // just against the container's own children but against the container's\n // own siblings elsewhere on the page (StoryView.tsx's content panel,\n // MapView.tsx's PlaceCard popup, both positioned with their own z-index).\n // Found live: the overlay ended up painting over the story's content\n // panel instead of staying confined beneath it. Setting a z-index here\n // (any value; 0 keeps this box in the same paint bucket it was already\n // in) traps the overlay inside this container instead.\n if (!container.style.zIndex) container.style.zIndex = \"0\";\n let snapshotUrl: string | null = null;\n try {\n snapshotUrl = map.getCanvas().toDataURL(\"image/png\");\n } catch {\n // Tainted canvas or an unsupported browser -- no crossfade, just swap.\n }\n\n beforeSwap();\n map.setStyle(styleUrl);\n if (!snapshotUrl) return;\n\n const overlay = document.createElement(\"img\");\n overlay.src = snapshotUrl;\n overlay.alt = \"\";\n Object.assign(overlay.style, {\n position: \"absolute\", inset: \"0\", width: \"100%\", height: \"100%\",\n zIndex: \"5\", pointerEvents: \"none\", transition: `opacity ${duration}ms ease`,\n });\n container.appendChild(overlay);\n map.once(\"idle\", () => {\n // rAF, not set on the same tick as append -- a transition needs the\n // browser to have painted the starting opacity (1, the element's\n // default) at least once before a change to it will animate rather\n // than just jumping straight to the end state.\n requestAnimationFrame(() => { overlay.style.opacity = \"0\"; });\n window.setTimeout(() => overlay.remove(), duration + 50);\n });\n}\n\n/** Filters an OHM style's own vector layers to a year. A no-op on any layer\n * without a `source-layer` -- which is every layer either map component\n * adds itself (GeoJSON place markers, clusters, the story's pin) -- and\n * idempotent to call again on every year change: the plugin rewrites a\n * `let`-bound variable in place rather than re-wrapping each layer's filter,\n * so a slider drag never needs a style reload to take effect. */\nexport function applyOhmYear(map: MapLibreMap, year: number): void {\n filterByDate(map, String(Math.round(year)));\n}\n", "// The player's look, as plain values a host can override (a host app\n// passes its own theme's colors, so the story matches it). Its words are\n// in strings.ts.\n\nexport interface PlayerColors {\n /** Page and strip background. */\n surface: string;\n text: string;\n muted: string;\n border: string;\n /** The pin, timeline dots and default shape color. */\n mark: string;\n /** The slide panel's solid color, fading in over the map. */\n panel: string;\n}\n\n/** The marks' blue clears 3:1 against both surfaces. */\nexport function defaultColors(dark: boolean): PlayerColors {\n return dark\n ? { surface: \"#242424\", text: \"#c9c9c9\", muted: \"#828282\", border: \"#424242\", mark: \"#3987e5\", panel: \"rgb(20,20,20)\" }\n : { surface: \"#ffffff\", text: \"#000000\", muted: \"#868e96\", border: \"#dee2e6\", mark: \"#2a78d6\", panel: \"rgb(255,255,255)\" };\n}\n\n/** A map pin (round top, point at the bottom) in a 24x24 box with its tip\n * at (12, 24): the map marker and the current timeline dot share it, and\n * every use aligns that tip to the point it marks. */\nexport const PIN_PATH_D = \"M12 0C7.03 0 3 4.03 3 9c0 6.25 9 15 9 15s9-8.75 9-15c0-4.97-4.03-9-9-9z\";\n", "// The Auto / Standard / Historical map picker, in plain HTML. A host app\n// with its own UI kit can supply its own control instead (StoryPlayer's\n// renderMapModeControl), taking the same props.\nimport type { PlayerStrings } from \"./strings\";\nimport type { PlayerColors } from \"./theme\";\n\n/** \"standard\" is today's map; \"historical\" a map of the year the viewer\n * picks; \"auto\" a map of each slide's own year as the story moves. */\nexport type MapMode = \"auto\" | \"standard\" | \"historical\";\n\nexport interface MapModeControlProps {\n mode: MapMode;\n onModeChange: (mode: MapMode) => void;\n /** What Auto resolves to on this slide, or null with no date to use. */\n autoYear: number | null;\n /** The viewer's own pick, live while mode is \"historical\". */\n year: number;\n onYearChange: (year: number) => void;\n yearBounds: [number, number];\n}\n\nexport function MapModeControl({ mode, onModeChange, autoYear, year, onYearChange, yearBounds, strings, colors }:\n MapModeControlProps & { strings: PlayerStrings; colors: PlayerColors }) {\n const modes: [MapMode, string][] = [[\"auto\", strings.auto], [\"standard\", strings.standard], [\"historical\", strings.historical]];\n const font = { fontSize: 12, fontFamily: \"inherit\" };\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, whiteSpace: \"nowrap\" }}>\n <div role=\"radiogroup\" aria-label={strings.mapMode} style={{ display: \"flex\", border: `1px solid ${colors.border}`, borderRadius: 6, overflow: \"hidden\", background: colors.surface }}>\n {modes.map(([value, label]) => (\n <button\n key={value}\n type=\"button\"\n role=\"radio\"\n aria-checked={mode === value}\n onClick={() => onModeChange(value)}\n style={{\n ...font, padding: \"4px 10px\", border: \"none\", cursor: \"pointer\",\n background: mode === value ? colors.mark : \"transparent\",\n color: mode === value ? \"#ffffff\" : colors.text,\n }}\n >\n {label}\n </button>\n ))}\n </div>\n {mode === \"auto\" && (\n <span style={{ ...font, color: colors.muted }}>\n {autoYear != null ? strings.historicalMapYear(autoYear) : strings.noDateStandardMap}\n </span>\n )}\n {mode === \"historical\" && (\n <span style={{ display: \"flex\", alignItems: \"center\", gap: 6 }}>\n <input\n type=\"range\"\n min={yearBounds[0]}\n max={yearBounds[1]}\n step={1}\n value={year}\n onChange={(e) => onYearChange(Number(e.currentTarget.value))}\n aria-label={strings.historicalYear}\n style={{ width: 150, accentColor: colors.mark }}\n />\n <input\n type=\"number\"\n min={yearBounds[0]}\n max={yearBounds[1]}\n step={1}\n value={year}\n onChange={(e) => {\n const value = Number(e.currentTarget.value);\n if (Number.isFinite(value) && e.currentTarget.value !== \"\") onYearChange(Math.round(value));\n }}\n aria-label={strings.historicalYear}\n style={{ ...font, width: 70, padding: \"3px 4px\", border: `1px solid ${colors.border}`, borderRadius: 4, background: colors.surface, color: colors.text }}\n />\n </span>\n )}\n </div>\n );\n}\n", "// Slide text markup (spec/1.0/README.md section 4): **bold**, *italic*,\n// line breaks, and blank lines between paragraphs -- nothing else. Parsed\n// into plain data rather than HTML, so a viewer can never be tricked into\n// rendering markup a story file smuggles in.\n\nexport interface TextSpan {\n text: string;\n bold?: boolean;\n italic?: boolean;\n}\n\n/** Paragraphs of lines of spans. */\nexport type ParsedText = TextSpan[][][];\n\n// Bold before italic, so a bold span's own ** pair isn't read as two empty\n// italic spans. Neither crosses a line break.\nconst MARKERS = /\\*\\*([^\\n]+?)\\*\\*|\\*([^\\n]+?)\\*/g;\n\nfunction parseLine(line: string): TextSpan[] {\n const spans: TextSpan[] = [];\n let last = 0;\n for (const match of line.matchAll(MARKERS)) {\n const index = match.index ?? 0;\n if (index > last) spans.push({ text: line.slice(last, index) });\n if (match[1] !== undefined) spans.push({ text: match[1], bold: true });\n else spans.push({ text: match[2], italic: true });\n last = index + match[0].length;\n }\n if (last < line.length) spans.push({ text: line.slice(last) });\n return spans;\n}\n\nexport function parseStoryText(text: string): ParsedText {\n return text\n .replace(/\\r\\n?/g, \"\\n\")\n .split(/\\n[ \\t]*\\n+/)\n .map((paragraph) => paragraph.replace(/^\\n+|\\n+$/g, \"\"))\n .filter((paragraph) => paragraph.length > 0)\n .map((paragraph) => paragraph.split(\"\\n\").map(parseLine));\n}\n", "// The player's own words -- the few on its buttons and map control, never\n// the story's -- in a handful of languages, chosen by the story's language\n// and then the browser's. A host with its own translations passes\n// StoryPlayer's `strings` instead.\n\nexport interface PlayerStrings {\n previous: string;\n next: string;\n close: string;\n mapMode: string;\n auto: string;\n standard: string;\n historical: string;\n /** The Auto mode's readout. */\n historicalMapYear: (year: number) => string;\n noDateStandardMap: string;\n historicalYear: string;\n /** A photo's thumbnail button, on a slide with several. */\n photoOf: (n: number, total: number) => string;\n}\n\nexport const ENGLISH: PlayerStrings = {\n previous: \"Previous\",\n next: \"Next\",\n close: \"Close\",\n mapMode: \"Map\",\n auto: \"Auto\",\n standard: \"Standard\",\n historical: \"Historical\",\n historicalMapYear: (year) => `Historical map, ${year}`,\n noDateStandardMap: \"No date here \u2014 standard map\",\n historicalYear: \"Historical map year\",\n photoOf: (n, total) => `Photo ${n} of ${total}`,\n};\n\nconst TABLE: Record<string, PlayerStrings> = {\n en: ENGLISH,\n de: {\n previous: \"Zur\u00FCck\",\n next: \"Weiter\",\n close: \"Schlie\u00DFen\",\n mapMode: \"Karte\",\n auto: \"Automatisch\",\n standard: \"Standard\",\n historical: \"Historisch\",\n historicalMapYear: (year) => `Historische Karte, ${year}`,\n noDateStandardMap: \"Kein Datum hier \u2014 Standardkarte\",\n historicalYear: \"Jahr der historischen Karte\",\n photoOf: (n, total) => `Foto ${n} von ${total}`,\n },\n es: {\n previous: \"Anterior\",\n next: \"Siguiente\",\n close: \"Cerrar\",\n mapMode: \"Mapa\",\n auto: \"Autom\u00E1tico\",\n standard: \"Est\u00E1ndar\",\n historical: \"Hist\u00F3rico\",\n historicalMapYear: (year) => `Mapa hist\u00F3rico, ${year}`,\n noDateStandardMap: \"Sin fecha aqu\u00ED \u2014 mapa est\u00E1ndar\",\n historicalYear: \"A\u00F1o del mapa hist\u00F3rico\",\n photoOf: (n, total) => `Foto ${n} de ${total}`,\n },\n fr: {\n previous: \"Pr\u00E9c\u00E9dent\",\n next: \"Suivant\",\n close: \"Fermer\",\n mapMode: \"Carte\",\n auto: \"Auto\",\n standard: \"Standard\",\n historical: \"Historique\",\n historicalMapYear: (year) => `Carte historique, ${year}`,\n noDateStandardMap: \"Pas de date ici \u2014 carte standard\",\n historicalYear: \"Ann\u00E9e de la carte historique\",\n photoOf: (n, total) => `Photo ${n} sur ${total}`,\n },\n nl: {\n previous: \"Vorige\",\n next: \"Volgende\",\n close: \"Sluiten\",\n mapMode: \"Kaart\",\n auto: \"Automatisch\",\n standard: \"Standaard\",\n historical: \"Historisch\",\n historicalMapYear: (year) => `Historische kaart, ${year}`,\n noDateStandardMap: \"Geen datum hier \u2014 standaardkaart\",\n historicalYear: \"Jaar van de historische kaart\",\n photoOf: (n, total) => `Foto ${n} van ${total}`,\n },\n};\n\n/** The best table for a list of BCP 47 tags, most preferred first:\n * \"de-CH\" matches \"de\"; English when nothing does. */\nexport function stringsFor(...languages: (string | undefined)[]): PlayerStrings {\n for (const tag of languages) {\n const base = tag?.toLowerCase().split(/[-_]/)[0];\n if (base && TABLE[base]) return TABLE[base];\n }\n return ENGLISH;\n}\n\n/** Languages with a built-in table. */\nexport const PLAYER_LANGUAGES = Object.keys(TABLE);\n", "// \"A line with years on it, and a dot\": where the current slide sits among\n// the story's dated slides, with click-to-jump per dot. SVG, since a story\n// has at most a few dozen dated slides and SVG rescales for free.\nimport { useEffect, useRef, useState } from \"react\";\nimport { axisTicks, storyDomain, tickLabel, xForYear } from \"./timelineLayout\";\nimport { PIN_PATH_D, type PlayerColors } from \"./theme\";\n\nexport const TIMELINE_HEIGHT = 70;\nconst MARGIN = 28;\n/** Where each pin's tip touches the axis. */\nconst AXIS_Y = 42;\nconst LABEL_Y = 62;\nconst CURRENT_SCALE = 0.95;\n\nexport interface TimelinePoint {\n index: number;\n year: number;\n}\n\nexport function TimelineStrip({ points, currentIndex, onSelect, colors }: {\n points: TimelinePoint[];\n currentIndex: number;\n onSelect: (index: number) => void;\n colors: PlayerColors;\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const observer = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n const plotWidth = Math.max(0, width - MARGIN * 2);\n const domain = storyDomain(points.map((p) => p.year));\n const ticks = plotWidth > 0 ? axisTicks(domain, plotWidth) : [];\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"absolute\", left: 0, right: 0, bottom: 0, height: TIMELINE_HEIGHT, background: colors.surface,\n // Above the slide panel, whose fading background would otherwise\n // wash out any dot beneath it.\n zIndex: 2,\n }}\n >\n {width > 0 && (\n <svg width={width} height={TIMELINE_HEIGHT} style={{ display: \"block\", overflow: \"visible\" }}>\n <line x1={MARGIN} y1={AXIS_Y} x2={width - MARGIN} y2={AXIS_Y} stroke={colors.border} strokeWidth={1} />\n {ticks.map((tick) => (\n <text key={tick.x} x={MARGIN + tick.x} y={LABEL_Y} fontSize={15} fill={colors.muted} textAnchor=\"middle\">\n {tick.label}\n </text>\n ))}\n {points.map((point) => {\n const cx = MARGIN + xForYear(point.year, domain, plotWidth);\n const current = point.index === currentIndex;\n return current ? (\n <path\n key={point.index}\n d={PIN_PATH_D}\n transform={`translate(${cx - 12 * CURRENT_SCALE} ${AXIS_Y - 24 * CURRENT_SCALE}) scale(${CURRENT_SCALE})`}\n fill={colors.mark}\n stroke={colors.text}\n strokeWidth={1.5}\n style={{ cursor: \"pointer\" }}\n onClick={() => onSelect(point.index)}\n >\n <title>{tickLabel(point.year)}</title>\n </path>\n ) : (\n <circle\n key={point.index}\n cx={cx}\n cy={AXIS_Y}\n r={3.5}\n fill={colors.mark}\n stroke={colors.surface}\n strokeWidth={1.5}\n style={{ cursor: \"pointer\" }}\n onClick={() => onSelect(point.index)}\n >\n <title>{tickLabel(point.year)}</title>\n </circle>\n );\n })}\n </svg>\n )}\n </div>\n );\n}\n", "// Layout math for the timeline strip along the bottom of a story -- pure\n// numbers, no DOM.\n\n/** The year range the strip covers, with a little padding, so a lone dot\n * (or two dots in the same year) still sits on a real span. */\nexport function storyDomain(years: number[]): [number, number] {\n const min = Math.min(...years);\n const max = Math.max(...years);\n if (max - min < 1) return [min - 5, min + 5];\n const padding = (max - min) * 0.05;\n return [min - padding, max + padding];\n}\n\n/** Where a year lands across a `width`-px strip for `domain`. */\nexport function xForYear(year: number, domain: [number, number], width: number): number {\n const [start, end] = domain;\n const span = end - start;\n if (!(span > 0)) return width / 2;\n return ((year - start) / span) * width;\n}\n\nexport interface AxisTick {\n x: number;\n label: string;\n}\n\n/** Candidate tick spacings in years, coarse to fine. */\nconst TICK_STEPS = [1000, 500, 250, 100, 50, 25, 10, 5, 2, 1];\n/** Aim for roughly this many labelled ticks. */\nconst TARGET_TICKS = 8;\n\nexport function axisTicks(domain: [number, number], width: number): AxisTick[] {\n const [start, end] = domain;\n const span = end - start;\n if (!(span > 0) || width <= 0) return [];\n const step = TICK_STEPS.find((candidate) => span / candidate >= TARGET_TICKS) ?? 1;\n const ticks: AxisTick[] = [];\n for (let year = Math.ceil(start / step) * step; year < end; year += step) {\n ticks.push({ x: ((year - start) / span) * width, label: tickLabel(year) });\n }\n return ticks;\n}\n\n/** Negative years are BC: genealogical calendars have no year 0. */\nexport function tickLabel(year: number): string {\n const whole = Math.floor(year);\n return whole < 0 ? `${-whole} BC` : String(whole);\n}\n", "// The Gramps Connect Story Viewer's mark, linking to the viewer: for\n// StoryPlayer's `brand` slot on standalone pages (the viewer site, a page\n// a program writes around a story), so whoever is watching can find where\n// it came from.\nimport type { CSSProperties } from \"react\";\n\nexport const VIEWER_NAME = \"Gramps Connect Story Viewer\";\nexport const VIEWER_URL = \"https://dsblank.github.io/gramps-connect-stories/\";\n\n/** The Gramps Connect icon (app/public/favicon.svg): a three-box pedigree\n * on a blue tile, which holds up on light and dark headers alike. */\nexport function ViewerIcon({ size = 24 }: { size?: number }) {\n return (\n <svg viewBox=\"0 0 64 64\" width={size} height={size} aria-hidden=\"true\" focusable=\"false\">\n <rect width=\"64\" height=\"64\" rx=\"15\" fill=\"#1866ba\" />\n <g transform=\"translate(9.6 9.6) scale(0.7)\">\n <path d=\"M25 32h7M32 15v34M32 15h7M32 49h7\" fill=\"none\" stroke=\"#fff\" strokeWidth=\"5\" strokeLinecap=\"round\" />\n <g fill=\"#fff\">\n <rect x=\"1\" y=\"24\" width=\"24\" height=\"16\" rx=\"4\" />\n <rect x=\"39\" y=\"7\" width=\"24\" height=\"16\" rx=\"4\" />\n <rect x=\"39\" y=\"41\" width=\"24\" height=\"16\" rx=\"4\" />\n </g>\n </g>\n </svg>\n );\n}\n\nexport interface ViewerBrandProps {\n /** Where the link goes; the viewer site by default. */\n href?: string;\n /** \"_blank\" (the default) opens it beside the story, not in its place. */\n target?: string;\n size?: number;\n}\n\n/** The icon, as a link to the viewer. */\nexport function ViewerBrand({ href = VIEWER_URL, target = \"_blank\", size = 24 }: ViewerBrandProps) {\n const style: CSSProperties = { display: \"flex\", lineHeight: 0, borderRadius: 6 };\n return (\n <a\n href={href}\n target={target}\n rel={target === \"_blank\" ? \"noopener\" : undefined}\n title={VIEWER_NAME}\n aria-label={VIEWER_NAME}\n style={style}\n >\n <ViewerIcon size={size} />\n </a>\n );\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// Presents a story: one slide at a time, with a full-bleed map behind the\n// slides when any of them has a place (modeled on a Knight Lab StoryMapJS\n// slide), or a plain centered card when none does, plus a timeline strip\n// when two or more slides are dated. Fills its container; the host decides\n// whether that's a fullscreen modal, a page, or a box on a blog.\nimport { useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode, type TouchEvent } from \"react\";\nimport type { MediaItem, Slide, StoryDoc } from \"../types\";\nimport { timelineYear } from \"./dates\";\nimport { placeCenter, storyPlaces } from \"./geo\";\nimport { MapBackground, type MapSlide } from \"./MapBackground\";\nimport { MapModeControl, type MapMode, type MapModeControlProps } from \"./MapModeControl\";\nimport { parseStoryText } from \"./text\";\nimport { stringsFor, type PlayerStrings } from \"./strings\";\nimport { defaultColors, type PlayerColors } from \"./theme\";\nimport { TIMELINE_HEIGHT, TimelineStrip, type TimelinePoint } from \"./TimelineStrip\";\n\n/** How much of the width the slide panel takes over a map; the map\n * centers each point in what's left. */\nconst PANEL_FRACTION = 0.62;\nconst BAR_HEIGHT = 56;\n\nexport interface StoryPlayerProps {\n story: StoryDoc;\n /** A URL the browser can load for a media key: a blob: URL for a file\n * out of a zip, or anything else the host serves. The host owns these\n * URLs (and revoking them); the player may ask for the same key again.\n * Null when it can't be had: the slide shows without it. */\n resolveMedia: (key: string, item: MediaItem) => Promise<string | null>;\n dark?: boolean;\n colors?: Partial<PlayerColors>;\n /** The player's own words. By default, the built-in table for the\n * story's language, else the browser's, else English (strings.ts). */\n strings?: Partial<PlayerStrings>;\n /** Shows a close button that calls this. */\n onClose?: () => void;\n /** Arrow keys move between slides: true listens on the whole window (a\n * fullscreen presentation), \"focus\" only while the player has focus (one\n * of several on a page), false not at all. Default true. */\n keyboard?: boolean | \"focus\";\n /** Called with the new index whenever the slide changes. */\n onSlideChange?: (index: number) => void;\n /** Replaces the built-in map-mode control, e.g. with one from the host's\n * own UI kit. */\n renderMapModeControl?: (props: MapModeControlProps) => ReactNode;\n /** Shown before the title, e.g. ViewerBrand on a standalone page. */\n brand?: ReactNode;\n /** The host's own buttons, at the right of the title bar (before the\n * close button), e.g. the viewer site's Edit. */\n actions?: ReactNode;\n initialSlide?: number;\n}\n\n/** The story's basemap hint (spec section 6) as the control's starting mode:\n * \"historical\" -- a map of each slide's era -- is what the control calls\n * Auto; \"auto\" (viewer's choice) starts on the standard map. */\nfunction initialMapMode(story: StoryDoc): MapMode {\n return story.presentation?.basemap === \"historical\" ? \"auto\" : \"standard\";\n}\n\nfunction SlideText({ text, colors }: { text: string; colors: PlayerColors }) {\n return (\n <>\n {parseStoryText(text).map((lines, p) => (\n <p key={p} dir=\"auto\" style={{ margin: \"0 0 0.6em\", color: colors.text, lineHeight: 1.55 }}>\n {lines.map((spans, l) => (\n <span key={l}>\n {l > 0 && <br />}\n {spans.map((span, s) => {\n if (span.bold) return <b key={s}>{span.text}</b>;\n if (span.italic) return <i key={s}>{span.text}</i>;\n return <span key={s}>{span.text}</span>;\n })}\n </span>\n ))}\n </p>\n ))}\n </>\n );\n}\n\nfunction SlidePhoto({ mediaKey, item, caption, credit, resolveMedia, colors }: {\n mediaKey: string;\n item: MediaItem;\n caption?: string;\n credit?: string;\n resolveMedia: StoryPlayerProps[\"resolveMedia\"];\n colors: PlayerColors;\n}) {\n const [src, setSrc] = useState<string | null>(null);\n useEffect(() => {\n setSrc(null);\n let cancelled = false;\n resolveMedia(mediaKey, item).then((url) => { if (!cancelled) setSrc(url); }, () => {});\n return () => {\n cancelled = true;\n };\n }, [mediaKey, item, resolveMedia]);\n if (!src) return null;\n return (\n <figure style={{ margin: 0, display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 4 }}>\n <img\n src={src}\n alt={item.alt ?? \"\"}\n width={item.width}\n height={item.height}\n style={{ maxHeight: \"42vh\", maxWidth: \"100%\", width: \"auto\", height: \"auto\", objectFit: \"contain\", borderRadius: 4 }}\n />\n {(caption || credit) && (\n <figcaption style={{ fontSize: 13, color: colors.muted }}>\n {[caption, credit && `\u00A9 ${credit}`].filter(Boolean).join(\" \u00B7 \")}\n </figcaption>\n )}\n </figure>\n );\n}\n\n/** A small button showing one of a slide's photos. */\nfunction PhotoThumb({ mediaKey, item, label, selected, onSelect, resolveMedia, colors }: {\n mediaKey: string;\n item: MediaItem;\n label: string;\n selected: boolean;\n onSelect: () => void;\n resolveMedia: StoryPlayerProps[\"resolveMedia\"];\n colors: PlayerColors;\n}) {\n const [src, setSrc] = useState<string | null>(null);\n useEffect(() => {\n let cancelled = false;\n resolveMedia(mediaKey, item).then((url) => { if (!cancelled) setSrc(url); }, () => {});\n return () => { cancelled = true; };\n }, [mediaKey, item, resolveMedia]);\n return (\n <button\n type=\"button\" aria-label={label} title={label} aria-pressed={selected} onClick={onSelect}\n style={{\n width: 44, height: 44, padding: 0, borderRadius: 4, overflow: \"hidden\", cursor: \"pointer\",\n border: `2px solid ${selected ? colors.mark : \"transparent\"}`, background: colors.border,\n opacity: selected ? 1 : 0.7,\n }}\n >\n {src && <img src={src} alt=\"\" style={{ width: \"100%\", height: \"100%\", objectFit: \"cover\", display: \"block\" }} />}\n </button>\n );\n}\n\nfunction SlideContent({ slide, story, resolveMedia, colors, strings }: {\n slide: Slide | undefined;\n story: StoryDoc;\n resolveMedia: StoryPlayerProps[\"resolveMedia\"];\n colors: PlayerColors;\n strings: PlayerStrings;\n}) {\n // Every photo whose media exists; with several, thumbnails pick which\n // one is shown. Keyed by slide in the caller, so each slide starts on\n // its first photo.\n const photos = (slide?.media ?? []).filter((m) => Object.prototype.hasOwnProperty.call(story.media, m.ref));\n const [shown, setShown] = useState(0);\n const photo = photos[Math.min(shown, photos.length - 1)];\n const eyebrow = [slide?.date?.text, slide?.place?.name].filter(Boolean).join(\" \u00B7 \");\n return (\n <>\n {photo && (\n <SlidePhoto\n mediaKey={photo.ref}\n item={story.media[photo.ref]}\n caption={photo.caption}\n credit={photo.credit}\n resolveMedia={resolveMedia}\n colors={colors}\n />\n )}\n {photos.length > 1 && (\n <div style={{ display: \"flex\", gap: 6, flexWrap: \"wrap\", justifyContent: \"center\" }}>\n {photos.map((m, i) => (\n <PhotoThumb\n key={`${i}:${m.ref}`}\n mediaKey={m.ref}\n item={story.media[m.ref]}\n label={strings.photoOf(i + 1, photos.length)}\n selected={photo === m}\n onSelect={() => setShown(i)}\n resolveMedia={resolveMedia}\n colors={colors}\n />\n ))}\n </div>\n )}\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4, alignItems: \"center\", maxWidth: 640, textAlign: \"center\" }}>\n {eyebrow && <div dir=\"auto\" style={{ fontSize: 14, color: colors.muted }}>{eyebrow}</div>}\n {slide?.title && <div dir=\"auto\" style={{ fontSize: 20, fontWeight: 700, color: colors.text }}>{slide.title}</div>}\n {slide?.text && <div><SlideText text={slide.text} colors={colors} /></div>}\n </div>\n </>\n );\n}\n\nfunction RoundButton({ label, onClick, disabled, size, children, style }: {\n label: string;\n onClick: () => void;\n disabled?: boolean;\n size: number;\n children: ReactNode;\n style?: CSSProperties;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n title={label}\n onClick={onClick}\n disabled={disabled}\n style={{\n width: size, height: size, borderRadius: \"50%\", border: \"none\",\n background: \"#25262b\", color: \"#ffffff\", fontSize: size >= 48 ? 22 : 18,\n cursor: disabled ? \"default\" : \"pointer\", opacity: disabled ? 0.35 : 0.8,\n display: \"flex\", alignItems: \"center\", justifyContent: \"center\", padding: 0, fontFamily: \"inherit\",\n ...style,\n }}\n >\n {children}\n </button>\n );\n}\n\nfunction NavArrows({ index, total, onPrev, onNext, variant, strings, colors }: {\n index: number;\n total: number;\n onPrev: () => void;\n onNext: () => void;\n /** \"edge\": large buttons at the screen edges, dark so they stay legible\n * over any map. \"inline\": small buttons and an \"n / N\" counter. */\n variant: \"inline\" | \"edge\";\n strings: PlayerStrings;\n colors: PlayerColors;\n}) {\n if (variant === \"edge\") {\n const edge: CSSProperties = { position: \"absolute\", top: \"50%\", transform: \"translateY(-50%)\", zIndex: 2 };\n return (\n <>\n <RoundButton label={strings.previous} size={56} disabled={index === 0} onClick={onPrev} style={{ ...edge, left: 24 }}>\u2190</RoundButton>\n <RoundButton label={strings.next} size={56} disabled={index === total - 1} onClick={onNext} style={{ ...edge, right: 24 }}>\u2192</RoundButton>\n </>\n );\n }\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", justifyContent: \"center\", gap: 16, marginTop: 16 }}>\n <RoundButton label={strings.previous} size={34} disabled={index === 0} onClick={onPrev}>\u2190</RoundButton>\n <span style={{ fontSize: 14, color: colors.muted }}>{index + 1} / {total}</span>\n <RoundButton label={strings.next} size={34} disabled={index === total - 1} onClick={onNext}>\u2192</RoundButton>\n </div>\n );\n}\n\nexport function StoryPlayer({\n story, resolveMedia, dark = false, colors: colorOverrides, strings: stringOverrides, onClose,\n keyboard = true, onSlideChange, renderMapModeControl, brand, actions, initialSlide = 0,\n}: StoryPlayerProps) {\n const colors = useMemo(() => ({ ...defaultColors(dark), ...colorOverrides }), [dark, colorOverrides]);\n const strings = useMemo(\n () => ({\n ...stringsFor(story.language, typeof navigator === \"undefined\" ? undefined : navigator.language),\n ...stringOverrides,\n }),\n [story.language, stringOverrides],\n );\n const rootRef = useRef<HTMLDivElement>(null);\n const slides = story.slides;\n const [index, setIndex] = useState(initialSlide);\n const slide = slides[index];\n\n useEffect(() => {\n setIndex(Math.min(Math.max(initialSlide, 0), Math.max(slides.length - 1, 0)));\n }, [story, initialSlide, slides.length]);\n\n const datedSlides = useMemo(() => {\n const points: TimelinePoint[] = [];\n slides.forEach((s, i) => {\n const year = timelineYear(s.date);\n if (year !== null) points.push({ index: i, year });\n });\n return points;\n }, [slides]);\n const showTimeline = datedSlides.length >= 2;\n\n // Map modes: \"auto\" follows the current slide's year as the story moves.\n const [mapMode, setMapMode] = useState<MapMode>(() => initialMapMode(story));\n const [historicalYear, setHistoricalYear] = useState(() => new Date().getFullYear());\n const currentYear = timelineYear(slide?.date);\n const autoYear = currentYear !== null ? Math.floor(currentYear) : null;\n const yearBounds = useMemo<[number, number]>(() => {\n if (datedSlides.length === 0) {\n const thisYear = new Date().getFullYear();\n return [thisYear - 200, thisYear];\n }\n const years = datedSlides.map((d) => d.year);\n return [Math.floor(Math.min(...years)), Math.ceil(Math.max(...years))];\n }, [datedSlides]);\n // Entering Historical starts the slider on this slide's year.\n const wasHistoricalRef = useRef(false);\n useEffect(() => {\n if (mapMode === \"historical\" && !wasHistoricalRef.current) setHistoricalYear(autoYear ?? yearBounds[1]);\n wasHistoricalRef.current = mapMode === \"historical\";\n }, [mapMode, autoYear, yearBounds]);\n const ohmYear = mapMode === \"standard\" ? null : mapMode === \"auto\" ? autoYear : historicalYear;\n\n const goPrev = () => setIndex((i) => Math.max(i - 1, 0));\n const goNext = () => setIndex((i) => Math.min(i + 1, slides.length - 1));\n\n const onSlideChangeRef = useRef(onSlideChange);\n onSlideChangeRef.current = onSlideChange;\n useEffect(() => {\n onSlideChangeRef.current?.(index);\n }, [index]);\n\n useEffect(() => {\n if (!keyboard) return;\n const target: HTMLElement | Window | null = keyboard === \"focus\" ? rootRef.current : window;\n if (!target) return;\n function onKey(e: Event) {\n const key = (e as KeyboardEvent).key;\n const from = e.target as HTMLElement | null;\n // Not while typing a year into the map control.\n if (from && (from.tagName === \"INPUT\" || from.tagName === \"TEXTAREA\" || from.isContentEditable)) return;\n if (key === \"ArrowRight\") setIndex((i) => Math.min(i + 1, slides.length - 1));\n if (key === \"ArrowLeft\") setIndex((i) => Math.max(i - 1, 0));\n }\n target.addEventListener(\"keydown\", onKey);\n return () => target.removeEventListener(\"keydown\", onKey);\n }, [keyboard, slides.length]);\n\n // A horizontal swipe on the slide (not the map, which a drag pans)\n // moves between slides.\n const swipeStart = useRef<{ x: number; y: number } | null>(null);\n const swipeHandlers = {\n onTouchStart: (e: TouchEvent) => {\n const t = e.touches[0];\n swipeStart.current = e.touches.length === 1 ? { x: t.clientX, y: t.clientY } : null;\n },\n onTouchEnd: (e: TouchEvent) => {\n const start = swipeStart.current;\n swipeStart.current = null;\n const t = e.changedTouches[0];\n if (!start || !t) return;\n const dx = t.clientX - start.x;\n const dy = t.clientY - start.y;\n if (Math.abs(dx) < 50 || Math.abs(dx) < Math.abs(dy) * 1.5) return;\n if (dx < 0) goNext();\n else goPrev();\n },\n };\n\n // Stable per slide, so a re-render for an unrelated reason (a map-mode\n // click) never looks like a new slide to the map's camera.\n const places = useMemo(() => storyPlaces(slides.map((s) => s.place)), [slides]);\n const mapSlide = useMemo<MapSlide>(() => ({\n key: slide?.id ?? String(index),\n place: slide?.place,\n // The opening card, with no place of its own, shows where the story goes.\n overview: index === 0 && !placeCenter(slide?.place) ? places : null,\n }), [slide, index, places]);\n const firstCenter = useMemo(() => {\n if (story.presentation?.basemap === \"none\") return null;\n for (const s of slides) {\n const center = placeCenter(s.place);\n if (center) return center;\n }\n return null;\n }, [story, slides]);\n\n const modeControlProps: MapModeControlProps = {\n mode: mapMode, onModeChange: setMapMode, autoYear, year: historicalYear, onYearChange: setHistoricalYear, yearBounds,\n };\n const mediaResolver = useMemo(() => (key: string) => {\n const item = Object.prototype.hasOwnProperty.call(story.media, key) ? story.media[key] : undefined;\n return item ? resolveMedia(key, item) : Promise.resolve(null);\n }, [story, resolveMedia]);\n\n const content = (\n <SlideContent key={slide?.id ?? index} slide={slide} story={story} resolveMedia={resolveMedia} colors={colors} strings={strings} />\n );\n const timeline = showTimeline && (\n <TimelineStrip points={datedSlides} currentIndex={index} onSelect={setIndex} colors={colors} />\n );\n\n return (\n <div\n ref={rootRef}\n lang={story.language}\n tabIndex={keyboard === \"focus\" ? 0 : undefined}\n style={{\n position: \"relative\", width: \"100%\", height: \"100%\", overflow: \"hidden\",\n background: colors.surface, color: colors.text,\n }}\n >\n {/* Title bar: translucent, so it reads over the map too. */}\n <div\n style={{\n position: \"absolute\", top: 0, left: 0, right: 0, height: BAR_HEIGHT, zIndex: 3,\n background: dark ? \"rgba(20,20,20,0.55)\" : \"rgba(255,255,255,0.55)\",\n }}\n />\n <div\n dir=\"auto\"\n style={{\n position: \"absolute\", top: 0, left: 20, height: BAR_HEIGHT, maxWidth: \"26%\", zIndex: 3,\n display: \"flex\", alignItems: \"center\", gap: 10, fontWeight: 600,\n }}\n >\n {brand && <span style={{ flex: \"none\", display: \"flex\" }}>{brand}</span>}\n <span style={{ whiteSpace: \"nowrap\", overflow: \"hidden\", textOverflow: \"ellipsis\" }}>{story.title}</span>\n </div>\n {/* Centered on the whole bar, as a toolbar for the presentation; the\n full-width wrapper lets clicks through to anything under it. */}\n {firstCenter && (\n <div\n style={{\n position: \"absolute\", top: 0, left: 0, right: 0, height: BAR_HEIGHT, zIndex: 3,\n display: \"flex\", alignItems: \"center\", justifyContent: \"center\", pointerEvents: \"none\",\n }}\n >\n <div style={{ pointerEvents: \"auto\" }}>\n {renderMapModeControl\n ? renderMapModeControl(modeControlProps)\n : <MapModeControl {...modeControlProps} strings={strings} colors={colors} />}\n </div>\n </div>\n )}\n {actions && (\n <div\n style={{\n position: \"absolute\", top: 0, right: onClose ? 60 : 12, height: BAR_HEIGHT, zIndex: 3,\n display: \"flex\", alignItems: \"center\", gap: 8,\n }}\n >\n {actions}\n </div>\n )}\n {onClose && (\n <RoundButton\n label={strings.close} size={36} onClick={onClose}\n style={{ position: \"absolute\", top: 10, right: 12, zIndex: 3 }}\n >\n \u00D7\n </RoundButton>\n )}\n\n {firstCenter ? (\n <div style={{ position: \"absolute\", inset: 0 }}>\n <MapBackground\n initialCenter={firstCenter}\n current={mapSlide}\n dark={dark}\n colors={colors}\n panelFraction={PANEL_FRACTION}\n ohmYear={ohmYear}\n resolveMedia={mediaResolver}\n />\n <div\n style={{\n position: \"absolute\", top: 0, bottom: 0, right: 0, width: `${PANEL_FRACTION * 100}%`, zIndex: 1,\n padding: `${BAR_HEIGHT}px 32px ${showTimeline ? TIMELINE_HEIGHT : 32}px`, boxSizing: \"border-box\",\n display: \"flex\", flexDirection: \"column\", justifyContent: \"center\", alignItems: \"center\", gap: 16,\n overflowY: \"auto\",\n // Transparent where the panel starts, mid-screen, and solid\n // well before the centered content, so text never sits on map.\n background: `linear-gradient(to right, transparent 0%, ${colors.panel} 22%)`,\n }}\n {...swipeHandlers}\n >\n {content}\n </div>\n <NavArrows index={index} total={slides.length} onPrev={goPrev} onNext={goNext} variant=\"edge\" strings={strings} colors={colors} />\n {timeline}\n </div>\n ) : (\n <div\n style={{\n position: \"absolute\", inset: 0, overflowY: \"auto\", boxSizing: \"border-box\",\n padding: `${BAR_HEIGHT + 16}px 24px ${showTimeline ? TIMELINE_HEIGHT + 16 : 24}px`,\n display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", gap: 16,\n }}\n {...swipeHandlers}\n >\n {content}\n <NavArrows index={index} total={slides.length} onPrev={goPrev} onNext={goNext} variant=\"inline\" strings={strings} colors={colors} />\n {timeline}\n </div>\n )}\n </div>\n );\n}\n", "// What the player needs from a story's dates: which years an EDTF value\n// covers (to show a historical map overlay only in its own era), and a\n// position on the timeline finer than a whole year when the date allows.\nimport type { StoryDate } from \"../types\";\n\nexport interface YearRange {\n /** Undefined for an open start (\"../1850\"). */\n min?: number;\n /** Undefined for an open end (\"1850/..\"). */\n max?: number;\n}\n\nconst DATE = /^(-?\\d{4})(?:-(\\d{2}|XX))?(?:-(\\d{2}|XX))?[~?%]?$/;\n\nfunction yearOf(part: string): number | null {\n const match = DATE.exec(part.trim());\n return match ? Number(match[1]) : null;\n}\n\n/** The years an EDTF date covers, or null when it can't be read (which\n * callers treat the same as having no date). Handles single dates\n * (\"1850\", \"1850-03-12\", \"1850~\"), intervals (\"1850/1860\", \"../1850\",\n * \"1850/..\") and one-of sets (\"[1850..1860]\"). */\nexport function edtfYearRange(edtf: string | undefined): YearRange | null {\n if (!edtf) return null;\n const value = edtf.trim();\n const set = /^\\[(.+)\\.\\.(.+)\\]$/.exec(value);\n if (set) {\n const min = yearOf(set[1]);\n const max = yearOf(set[2]);\n return min !== null && max !== null ? { min: Math.min(min, max), max: Math.max(min, max) } : null;\n }\n if (value.includes(\"/\")) {\n const [startText, endText] = value.split(\"/\");\n const openStart = startText === \"..\" || startText === \"\";\n const openEnd = endText === \"..\" || endText === \"\";\n const start = openStart ? undefined : yearOf(startText);\n const end = openEnd ? undefined : yearOf(endText);\n if (start === null || end === null) return null;\n if (start === undefined && end === undefined) return null;\n if (start !== undefined && end !== undefined) return { min: Math.min(start, end), max: Math.max(start, end) };\n return { min: start, max: end };\n }\n const year = yearOf(value);\n return year === null ? null : { min: year, max: year };\n}\n\n/** Whether something dated `date` belongs on a map of `year`. No date, an\n * unreadable one, or no year to compare against (a standard, non-historical\n * map) all mean visible: an undated overlay is never hidden. */\nexport function dateCoversYear(date: StoryDate | undefined, year: number | null): boolean {\n if (!date || year === null) return true;\n const range = edtfYearRange(date.edtf);\n if (!range) return true;\n if (range.min !== undefined && year < range.min) return false;\n if (range.max !== undefined && year > range.max) return false;\n return true;\n}\n\n/** A slide's position on the timeline, as a fractional year: its\n * `sortYear`, nudged within the year by the EDTF month and day when they\n * agree with it -- so two events in the same year don't land on the exact\n * same spot. Null for a slide that isn't on the timeline. */\nexport function timelineYear(date: StoryDate | undefined): number | null {\n if (!date) return null;\n const match = date.edtf ? DATE.exec(date.edtf.trim()) : null;\n const sortYear = date.sortYear ?? (match ? Number(match[1]) : undefined);\n if (sortYear === undefined) return null;\n if (!match || Number(match[1]) !== sortYear) return sortYear;\n const month = match[2] && match[2] !== \"XX\" ? Number(match[2]) : 0;\n const day = match[3] && match[3] !== \"XX\" ? Number(match[3]) : 0;\n if (!month) return sortYear;\n return sortYear + (month - 1) / 12 + (day ? (day - 1) / 365 : 0);\n}\n", "// Where a slide is on the map: its point, or the box around its shape and\n// overlays -- pure geometry, no map library.\nimport type { LonLat, Place } from \"../types\";\n\nexport type Bounds = [west: number, south: number, east: number, north: number];\n\nfunction visit(coords: unknown, bounds: Bounds): void {\n if (!Array.isArray(coords)) return;\n if (coords.length >= 2 && typeof coords[0] === \"number\" && typeof coords[1] === \"number\") {\n const [lon, lat] = coords as number[];\n bounds[0] = Math.min(bounds[0], lon);\n bounds[1] = Math.min(bounds[1], lat);\n bounds[2] = Math.max(bounds[2], lon);\n bounds[3] = Math.max(bounds[3], lat);\n return;\n }\n for (const child of coords) visit(child, bounds);\n}\n\n/** Every coordinate in a GeoJSON object, whatever its nesting. */\nfunction visitGeoJson(node: unknown, bounds: Bounds): void {\n if (!node || typeof node !== \"object\") return;\n const obj = node as { type?: string; coordinates?: unknown; geometry?: unknown; geometries?: unknown[]; features?: unknown[] };\n if (obj.coordinates !== undefined) visit(obj.coordinates, bounds);\n if (obj.geometry) visitGeoJson(obj.geometry, bounds);\n for (const g of obj.geometries ?? []) visitGeoJson(g, bounds);\n for (const f of obj.features ?? []) visitGeoJson(f, bounds);\n}\n\n/** The box around a place's shape and overlays, or null when it has\n * neither (or they hold no coordinates). */\nexport function placeBounds(place: Place | undefined): Bounds | null {\n if (!place) return null;\n const bounds: Bounds = [Infinity, Infinity, -Infinity, -Infinity];\n visitGeoJson(place.geometry, bounds);\n for (const overlay of place.overlays ?? []) visit(overlay.corners as LonLat[], bounds);\n return Number.isFinite(bounds[0]) ? bounds : null;\n}\n\n/** Where to put the pin: the place's own point, else the middle of its\n * shape and overlays. Null for a slide with no position at all. */\nexport function placeCenter(place: Place | undefined): LonLat | null {\n if (place?.lat !== undefined && place.lon !== undefined) return [place.lon, place.lat];\n const bounds = placeBounds(place);\n return bounds ? [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2] : null;\n}\n\nexport interface StoryPlaces {\n points: LonLat[];\n bounds: Bounds;\n}\n\n/** Every place in the story at once, for its opening view: one point per\n * distinct place, and the box around all of them, shapes and overlays\n * included. Null when no slide has a position. */\nexport function storyPlaces(places: (Place | undefined)[]): StoryPlaces | null {\n const bounds: Bounds = [Infinity, Infinity, -Infinity, -Infinity];\n const points = new Map<string, LonLat>();\n for (const place of places) {\n const center = placeCenter(place);\n if (!center) continue;\n points.set(`${center[0].toFixed(5)},${center[1].toFixed(5)}`, center);\n visit(center, bounds);\n const box = placeBounds(place);\n if (box) visit([[box[0], box[1]], [box[2], box[3]]], bounds);\n }\n return points.size ? { points: [...points.values()], bounds } : null;\n}\n", "// The full-bleed map behind a located story's slides: flies to the current\n// slide's place (or frames its shape and overlays), draws that shape and\n// those overlays, and holds still -- marker dimmed -- on a slide with no\n// place. Its own small maplibre instance; the host page loads no map of\n// its own for this.\nimport { useEffect, useRef, useState } from \"react\";\nimport * as maplibregl from \"maplibre-gl\";\n// Positions the marker and styles the attribution control; without it the\n// pin sits in normal flow at the top left instead of on its coordinate.\nimport \"maplibre-gl/dist/maplibre-gl.css\";\nimport type { GeoJSONSource, Map as MapLibreMap, Marker as MapLibreMarker } from \"maplibre-gl\";\nimport type { FeatureCollection, GeoJSON } from \"geojson\";\nimport type { Overlay, Place } from \"../types\";\nimport { dateCoversYear } from \"./dates\";\nimport { placeBounds, placeCenter, type StoryPlaces } from \"./geo\";\nimport { applyOhmYear, crossfadeStyleSwap, mapStyleKey, mapStyleUrl } from \"./mapStyles\";\nimport { PIN_PATH_D, type PlayerColors } from \"./theme\";\n\nconst SHAPE_SOURCE = \"story-shape\";\nconst FILL_LAYER = \"story-shape-fill\";\nconst LINE_LAYER = \"story-shape-line\";\nconst POINT_LAYER = \"story-shape-points\";\nconst LABEL_LAYER = \"story-shape-labels\";\nconst OVERLAY_PREFIX = \"story-overlay-\";\nconst PLACES_SOURCE = \"story-places\";\nconst PLACES_LAYER = \"story-places-dots\";\n// Room around the outermost dots of the opening view, inside the part of\n// the map the slide panel leaves free.\nconst OVERVIEW_PADDING = 60;\nconst EMPTY: FeatureCollection = { type: \"FeatureCollection\", features: [] };\n\n/** The current slide, as far as the map cares. `key` is the slide id:\n * what tells \"a new slide\" from \"the same slide, re-rendered\". */\nexport interface MapSlide {\n key: string;\n place: Place | undefined;\n /** Every place in the story, on an opening slide with no place of its\n * own: framed together, a dot each, instead of the pin. */\n overview: StoryPlaces | null;\n}\n\n/** Frame the story's places for an overview; else the place's shape or\n * overlays (usually a field, a route or an old map, far tighter than a\n * town-level zoom); else fly to its point. */\nfunction moveCamera(map: MapLibreMap, slide: MapSlide, duration: number) {\n if (slide.overview) {\n const [west, south, east, north] = slide.overview.bounds;\n map.fitBounds([[west, south], [east, north]], { maxZoom: 9, padding: OVERVIEW_PADDING, duration });\n return;\n }\n const place = slide.place;\n const bounds = placeBounds(place);\n if (bounds) {\n map.fitBounds([[bounds[0], bounds[1]], [bounds[2], bounds[3]]], { maxZoom: 17, duration });\n return;\n }\n const center = placeCenter(place);\n if (center) map.flyTo({ center, zoom: 9, essential: true, duration });\n}\n\n/** The pin on the place; hidden for an overview (the dots stand in for\n * it), else held where it was, dimmed, for a slide with no place. */\nfunction placeMarker(marker: MapLibreMarker | null, slide: MapSlide) {\n if (!marker) return;\n const center = placeCenter(slide.place);\n if (center) marker.setLngLat(center);\n // Through the marker, not its element's style: maplibre rewrites that\n // from the marker's own opacity every frame.\n marker.setOpacity(slide.overview ? 0 : center ? 1 : 0.4);\n}\n\nconst placeDots = (overview: StoryPlaces | null): FeatureCollection => ({\n type: \"FeatureCollection\",\n features: (overview?.points ?? []).map((coordinates) => ({\n type: \"Feature\", properties: {}, geometry: { type: \"Point\", coordinates },\n })),\n});\n\nexport function MapBackground({ initialCenter, current, dark, colors, panelFraction, ohmYear, resolveMedia }: {\n initialCenter: [number, number];\n current: MapSlide;\n dark: boolean;\n colors: PlayerColors;\n /** How much of the right side the slide panel covers: a point is\n * centered in the remaining left part, not the whole width. */\n panelFraction: number;\n /** Non-null switches to OpenHistoricalMap filtered to this year. */\n ohmYear: number | null;\n resolveMedia: (key: string) => Promise<string | null>;\n}) {\n // A callback ref (via state), not useRef: a host that mounts the player\n // inside a modal may not have it in the DOM on the first render, and a\n // plain ref would leave the create-effect below nothing to re-run on.\n const [containerEl, setContainerEl] = useState<HTMLDivElement | null>(null);\n const mapRef = useRef<MapLibreMap | null>(null);\n const markerRef = useRef<MapLibreMarker | null>(null);\n // True after each \"style.load\" (initial load, and again after every\n // style swap, which wipes the layers this component adds).\n const [ready, setReady] = useState(false);\n\n const darkRef = useRef(dark);\n darkRef.current = dark;\n const ohmYearRef = useRef(ohmYear);\n ohmYearRef.current = ohmYear;\n const colorsRef = useRef(colors);\n colorsRef.current = colors;\n // overlay layer id -> its date, for re-checking visibility on a year change.\n const overlayDatesRef = useRef<Map<string, Overlay[\"date\"]>>(new Map());\n // What the map was built or last swapped with, so a re-render with the\n // same effective style never triggers a reload.\n const appliedStyleKeyRef = useRef(mapStyleKey(dark, ohmYear));\n const currentRef = useRef(current);\n currentRef.current = current;\n\n useEffect(() => {\n if (!containerEl) return;\n setReady(false);\n appliedStyleKeyRef.current = mapStyleKey(darkRef.current, ohmYearRef.current);\n const map = new maplibregl.Map({\n container: containerEl,\n style: mapStyleUrl(darkRef.current, ohmYearRef.current),\n zoom: 5,\n attributionControl: { compact: true },\n // So crossfadeStyleSwap's snapshot has the last frame in it.\n canvasContextAttributes: { preserveDrawingBuffer: true },\n });\n mapRef.current = map;\n map.on(\"style.load\", () => setReady(true));\n // Center points in the part of the map the slide panel doesn't cover.\n map.setPadding({ top: 0, bottom: 0, left: 0, right: containerEl.clientWidth * panelFraction });\n // Straight to the opening slide's place, unanimated. The camera effect\n // below ran for it before this map existed (the container arrives a\n // render late), so a story opened partway through starts there too.\n const start = currentRef.current;\n map.jumpTo({ center: placeCenter(start.place) ?? initialCenter });\n moveCamera(map, start, 0);\n map.on(\"load\", () => {\n const c = colorsRef.current;\n const el = document.createElement(\"div\");\n el.style.width = \"26px\";\n el.style.height = \"26px\";\n el.style.transition = \"opacity 300ms\";\n el.innerHTML = `<svg viewBox=\"0 0 24 24\" width=\"26\" height=\"26\">`\n + `<path d=\"${PIN_PATH_D}\" fill=\"${c.mark}\" stroke=\"${c.surface}\" stroke-width=\"1.5\" />`\n + `</svg>`;\n // The pin's tip, not its middle, sits on the coordinate.\n markerRef.current = new maplibregl.Marker({ element: el, anchor: \"bottom\" }).setLngLat(initialCenter).addTo(map);\n placeMarker(markerRef.current, currentRef.current);\n });\n return () => {\n markerRef.current?.remove();\n markerRef.current = null;\n map.remove();\n mapRef.current = null;\n };\n // initialCenter is only the first frame; moving is the effect below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [containerEl]);\n\n useEffect(() => {\n const map = mapRef.current;\n const key = mapStyleKey(dark, ohmYear);\n if (!map || appliedStyleKeyRef.current === key) return;\n appliedStyleKeyRef.current = key;\n crossfadeStyleSwap(map, mapStyleUrl(dark, ohmYear), () => setReady(false));\n }, [dark, ohmYear]);\n\n useEffect(() => {\n const map = mapRef.current;\n if (!map || !ready || ohmYear == null) return;\n applyOhmYear(map, ohmYear);\n }, [ready, ohmYear]);\n\n // The shape layers, (re-)added after every style load, under the marker.\n useEffect(() => {\n const map = mapRef.current;\n if (!map || !ready || map.getSource(SHAPE_SOURCE)) return;\n const c = colorsRef.current;\n map.addSource(SHAPE_SOURCE, { type: \"geojson\", data: EMPTY });\n // A feature's own `color` property (simplestyle-ish) wins over the mark color.\n const color = [\"coalesce\", [\"get\", \"color\"], c.mark] as maplibregl.ExpressionSpecification;\n map.addLayer({\n id: FILL_LAYER, type: \"fill\", source: SHAPE_SOURCE,\n filter: [\"==\", [\"geometry-type\"], \"Polygon\"],\n // And its own `opacity` (what the map editor sets on an area) over\n // a light default.\n paint: { \"fill-color\": color, \"fill-opacity\": [\"coalesce\", [\"to-number\", [\"get\", \"opacity\"], 0.25], 0.25] },\n });\n map.addLayer({\n id: LINE_LAYER, type: \"line\", source: SHAPE_SOURCE,\n filter: [\"any\", [\"==\", [\"geometry-type\"], \"Polygon\"], [\"==\", [\"geometry-type\"], \"LineString\"]],\n paint: { \"line-color\": color, \"line-width\": 2 },\n });\n map.addLayer({\n id: POINT_LAYER, type: \"circle\", source: SHAPE_SOURCE,\n // A named point is a label (next layer), drawn as text only.\n filter: [\"all\", [\"==\", [\"geometry-type\"], \"Point\"], [\"!\", [\"has\", \"name\"]]],\n paint: { \"circle-radius\": 5, \"circle-color\": color, \"circle-stroke-width\": 1, \"circle-stroke-color\": c.surface },\n });\n map.addLayer({\n id: LABEL_LAYER, type: \"symbol\", source: SHAPE_SOURCE,\n filter: [\"all\", [\"==\", [\"geometry-type\"], \"Point\"], [\"has\", \"name\"]],\n layout: {\n \"text-field\": [\"get\", \"name\"],\n \"text-size\": 12,\n \"text-font\": [\"Noto Sans Regular\"],\n \"text-anchor\": \"left\",\n \"text-offset\": [0.6, 0],\n },\n paint: {\n \"text-color\": [\"coalesce\", [\"get\", \"color\"], c.text],\n \"text-halo-color\": c.surface,\n \"text-halo-width\": 1.5,\n },\n });\n map.addSource(PLACES_SOURCE, { type: \"geojson\", data: EMPTY });\n map.addLayer({\n id: PLACES_LAYER, type: \"circle\", source: PLACES_SOURCE,\n paint: { \"circle-radius\": 6, \"circle-color\": c.mark, \"circle-stroke-width\": 2, \"circle-stroke-color\": c.surface },\n });\n }, [ready]);\n\n const place = current.place;\n\n // The current slide's shape, or the overview's dots. Re-supplied after each style load, since a\n // swap empties the source.\n useEffect(() => {\n const map = mapRef.current;\n if (!map || !ready) return;\n const source = map.getSource(SHAPE_SOURCE) as GeoJSONSource | undefined;\n source?.setData((place?.geometry as GeoJSON | undefined) ?? EMPTY);\n const dots = map.getSource(PLACES_SOURCE) as GeoJSONSource | undefined;\n dots?.setData(placeDots(current.overview));\n }, [ready, current.key, place, current.overview]);\n\n // Camera: once per slide (see moveCamera). Keyed to the slide, not to `ready`, so a\n // map-mode switch never yanks the view back from wherever the viewer\n // has since panned.\n useEffect(() => {\n const map = mapRef.current;\n if (!map) return;\n placeMarker(markerRef.current, current);\n // No place and no overview: the camera stays put.\n moveCamera(map, current, 900);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [current.key]);\n\n // Image overlays: one image source + raster layer each, under the shape\n // layers, lowest `order` first so the highest ends up on top. Keyed by\n // the overlays themselves, so consecutive slides at the same place keep\n // theirs instead of reloading them.\n const overlayKey = JSON.stringify(place?.overlays ?? []);\n useEffect(() => {\n const map = mapRef.current;\n const overlays = place?.overlays ?? [];\n if (!map || !ready || overlays.length === 0) return;\n let cancelled = false;\n const added: string[] = [];\n (async () => {\n const sorted = overlays\n .map((overlay, i) => ({ overlay, i }))\n .sort((a, b) => (a.overlay.order ?? a.i) - (b.overlay.order ?? b.i));\n for (const { overlay, i } of sorted) {\n const url = await resolveMedia(overlay.media);\n if (cancelled) return;\n if (!url) continue;\n const id = `${OVERLAY_PREFIX}${i}`;\n if (map.getSource(id)) continue;\n map.addSource(id, { type: \"image\", url, coordinates: overlay.corners });\n map.addLayer({ id, type: \"raster\", source: id }, FILL_LAYER);\n map.setPaintProperty(id, \"raster-opacity\", overlay.opacity ?? 1);\n overlayDatesRef.current.set(id, overlay.date);\n map.setLayoutProperty(id, \"visibility\", dateCoversYear(overlay.date, ohmYearRef.current) ? \"visible\" : \"none\");\n added.push(id);\n }\n })();\n return () => {\n cancelled = true;\n for (const id of added) {\n overlayDatesRef.current.delete(id);\n if (map.getLayer(id)) map.removeLayer(id);\n if (map.getSource(id)) map.removeSource(id);\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [overlayKey, ready]);\n\n // A new map year alone: re-check each overlay's era.\n useEffect(() => {\n const map = mapRef.current;\n if (!map || !ready) return;\n for (const [id, date] of overlayDatesRef.current) {\n if (!map.getLayer(id)) continue;\n map.setLayoutProperty(id, \"visibility\", dateCoversYear(date, ohmYear) ? \"visible\" : \"none\");\n }\n }, [ohmYear, ready]);\n\n return <div ref={setContainerEl} style={{ position: \"absolute\", inset: 0 }} />;\n}\n", "// Basemaps for the story map: OpenFreeMap (light), CARTO Dark Matter (dark)\n// and OpenHistoricalMap (historical, filtered to a year), plus the\n// crossfade used when switching between them. Gramps Connect's own map\n// view uses these too (its visuals/mapStyles.ts re-exports them), so the\n// app and the story player can't drift into two versions of the same three\n// map modes.\nimport type { Map as MapLibreMap } from \"maplibre-gl\";\n// A CJS module with no browser global to patch onto (it only self-attaches\n// to `window.maplibregl`, which our namespace import never creates) -- the\n// named export below is what its `module.exports` branch actually produces,\n// and it's what both callers use directly rather than the `map.filterByDate`\n// method the plugin's own README shows for a script-tag install.\nimport { filterByDate } from \"@openhistoricalmap/maplibre-gl-dates\";\n\n/** The same OpenFreeMap \"liberty\" style gramps-web uses for its light\n * basemap, so the two clients' maps look like the same product in light\n * mode. Free, key-less, and hosted -- which does mean a map (unlike every\n * other view in this app) needs the network even though its *data* is\n * local. */\nexport const STYLE_LIGHT = \"https://tiles.openfreemap.org/styles/liberty\";\n/** OpenFreeMap's own \"dark\" style renders every layer, including water, in\n * the same near-zero-saturation gray (confirmed against their own demo at\n * openfreemap.org) -- there's no hue anywhere to tell land from water at a\n * glance. CARTO's Dark Matter style is the free, key-less alternative with\n * an actual (if muted) blue-gray palette -- water and major roads read as\n * distinct from background and from each other. */\nexport const STYLE_DARK = \"https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json\";\n\n/** OHM's own vector style -- one cartography regardless of light/dark,\n * matching gramps-web's choice. */\nexport const OHM_STYLE = \"https://www.openhistoricalmap.org/map-styles/main/main.json\";\n\n/** The three selectable modes wherever a historical map is shown. \"auto\" and\n * \"historical\" both render OHM tiles filtered to a year -- they differ only\n * in where that year comes from (context vs. a user-dragged slider), which\n * is why both map components take a single `ohmYear: number | null` rather\n * than the mode itself: \"standard\" and an \"auto\" with nothing to derive a\n * year from look identical to the map, and are meant to. */\nexport type MapMode = \"auto\" | \"standard\" | \"historical\";\n\n/** A stable key for \"would setStyle() actually change anything\" -- swapping\n * light/dark never matters once OHM is showing (its cartography doesn't\n * have a dark variant), so a slider drag that changes `ohmYear`'s value\n * without leaving historical/auto mode must not trip a style reload. */\nexport function mapStyleKey(dark: boolean, ohmYear: number | null): string {\n return ohmYear != null ? \"ohm\" : dark ? \"dark\" : \"light\";\n}\n\nexport function mapStyleUrl(dark: boolean, ohmYear: number | null): string {\n return ohmYear != null ? OHM_STYLE : dark ? STYLE_DARK : STYLE_LIGHT;\n}\n\n/** Swaps a map's style as a true crossfade -- the old and new basemaps\n * dissolving into each other, not one fading out before the other fades in.\n * A single WebGL map can only ever show one style at a time, so this fakes\n * it: a snapshot of the *old* style (a plain `<img>`, frozen) is overlaid\n * over the live map at full opacity, the live map underneath is swapped to\n * the new style immediately and left to load, and once it's actually\n * finished drawing (`idle`, not `style.load` -- that fires before a single\n * tile of the new style has painted) the snapshot dissolves away, revealing\n * the new style continuously as it goes rather than snapping to it. This\n * also means the old map stays on screen, fully intact, for however long\n * the new style's tiles take to fetch -- no blank/loading gap either way,\n * unlike fading through transparent would give.\n *\n * Requires `preserveDrawingBuffer: true` on the map (see both callers'\n * constructors) -- without it, `toDataURL()` on the canvas can come back\n * blank depending on exactly when the browser clears the drawing buffer\n * after compositing. Falls back to an instant, unfaded swap if the snapshot\n * can't be taken at all (a tainted canvas, or a browser that disallows it) --\n * a plain setStyle() rather than no swap.\n *\n * `beforeSwap` runs at the same moment as setStyle(), so a caller's own\n * `setReady(false)` -- which gates its layer re-add effect -- flips exactly\n * when setStyle() actually discards those layers. */\nexport function crossfadeStyleSwap(map: MapLibreMap, styleUrl: string, beforeSwap: () => void, duration = 300): void {\n const container = map.getContainer();\n // The container is `position: absolute` but neither caller gives it its\n // own `z-index` -- which means it *doesn't* establish a stacking context\n // of its own, and the overlay's z-index below would then be compared not\n // just against the container's own children but against the container's\n // own siblings elsewhere on the page (StoryView.tsx's content panel,\n // MapView.tsx's PlaceCard popup, both positioned with their own z-index).\n // Found live: the overlay ended up painting over the story's content\n // panel instead of staying confined beneath it. Setting a z-index here\n // (any value; 0 keeps this box in the same paint bucket it was already\n // in) traps the overlay inside this container instead.\n if (!container.style.zIndex) container.style.zIndex = \"0\";\n let snapshotUrl: string | null = null;\n try {\n snapshotUrl = map.getCanvas().toDataURL(\"image/png\");\n } catch {\n // Tainted canvas or an unsupported browser -- no crossfade, just swap.\n }\n\n beforeSwap();\n map.setStyle(styleUrl);\n if (!snapshotUrl) return;\n\n const overlay = document.createElement(\"img\");\n overlay.src = snapshotUrl;\n overlay.alt = \"\";\n Object.assign(overlay.style, {\n position: \"absolute\", inset: \"0\", width: \"100%\", height: \"100%\",\n zIndex: \"5\", pointerEvents: \"none\", transition: `opacity ${duration}ms ease`,\n });\n container.appendChild(overlay);\n map.once(\"idle\", () => {\n // rAF, not set on the same tick as append -- a transition needs the\n // browser to have painted the starting opacity (1, the element's\n // default) at least once before a change to it will animate rather\n // than just jumping straight to the end state.\n requestAnimationFrame(() => { overlay.style.opacity = \"0\"; });\n window.setTimeout(() => overlay.remove(), duration + 50);\n });\n}\n\n/** Filters an OHM style's own vector layers to a year. A no-op on any layer\n * without a `source-layer` -- which is every layer either map component\n * adds itself (GeoJSON place markers, clusters, the story's pin) -- and\n * idempotent to call again on every year change: the plugin rewrites a\n * `let`-bound variable in place rather than re-wrapping each layer's filter,\n * so a slider drag never needs a style reload to take effect. */\nexport function applyOhmYear(map: MapLibreMap, year: number): void {\n filterByDate(map, String(Math.round(year)));\n}\n", "// The player's look, as plain values a host can override (a host app\n// passes its own theme's colors, so the story matches it). Its words are\n// in strings.ts.\n\nexport interface PlayerColors {\n /** Page and strip background. */\n surface: string;\n text: string;\n muted: string;\n border: string;\n /** The pin, timeline dots and default shape color. */\n mark: string;\n /** The slide panel's solid color, fading in over the map. */\n panel: string;\n}\n\n/** The marks' blue clears 3:1 against both surfaces. */\nexport function defaultColors(dark: boolean): PlayerColors {\n return dark\n ? { surface: \"#242424\", text: \"#c9c9c9\", muted: \"#828282\", border: \"#424242\", mark: \"#3987e5\", panel: \"rgb(20,20,20)\" }\n : { surface: \"#ffffff\", text: \"#000000\", muted: \"#868e96\", border: \"#dee2e6\", mark: \"#2a78d6\", panel: \"rgb(255,255,255)\" };\n}\n\n/** A map pin (round top, point at the bottom) in a 24x24 box with its tip\n * at (12, 24): the map marker and the current timeline dot share it, and\n * every use aligns that tip to the point it marks. */\nexport const PIN_PATH_D = \"M12 0C7.03 0 3 4.03 3 9c0 6.25 9 15 9 15s9-8.75 9-15c0-4.97-4.03-9-9-9z\";\n", "// The Auto / Standard / Historical map picker, in plain HTML. A host app\n// with its own UI kit can supply its own control instead (StoryPlayer's\n// renderMapModeControl), taking the same props.\nimport type { PlayerStrings } from \"./strings\";\nimport type { PlayerColors } from \"./theme\";\n\n/** \"standard\" is today's map; \"historical\" a map of the year the viewer\n * picks; \"auto\" a map of each slide's own year as the story moves. */\nexport type MapMode = \"auto\" | \"standard\" | \"historical\";\n\nexport interface MapModeControlProps {\n mode: MapMode;\n onModeChange: (mode: MapMode) => void;\n /** What Auto resolves to on this slide, or null with no date to use. */\n autoYear: number | null;\n /** The viewer's own pick, live while mode is \"historical\". */\n year: number;\n onYearChange: (year: number) => void;\n yearBounds: [number, number];\n}\n\nexport function MapModeControl({ mode, onModeChange, autoYear, year, onYearChange, yearBounds, strings, colors }:\n MapModeControlProps & { strings: PlayerStrings; colors: PlayerColors }) {\n const modes: [MapMode, string][] = [[\"auto\", strings.auto], [\"standard\", strings.standard], [\"historical\", strings.historical]];\n const font = { fontSize: 12, fontFamily: \"inherit\" };\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, whiteSpace: \"nowrap\" }}>\n <div role=\"radiogroup\" aria-label={strings.mapMode} style={{ display: \"flex\", border: `1px solid ${colors.border}`, borderRadius: 6, overflow: \"hidden\", background: colors.surface }}>\n {modes.map(([value, label]) => (\n <button\n key={value}\n type=\"button\"\n role=\"radio\"\n aria-checked={mode === value}\n onClick={() => onModeChange(value)}\n style={{\n ...font, padding: \"4px 10px\", border: \"none\", cursor: \"pointer\",\n background: mode === value ? colors.mark : \"transparent\",\n color: mode === value ? \"#ffffff\" : colors.text,\n }}\n >\n {label}\n </button>\n ))}\n </div>\n {mode === \"auto\" && (\n <span style={{ ...font, color: colors.muted }}>\n {autoYear != null ? strings.historicalMapYear(autoYear) : strings.noDateStandardMap}\n </span>\n )}\n {mode === \"historical\" && (\n <span style={{ display: \"flex\", alignItems: \"center\", gap: 6 }}>\n <input\n type=\"range\"\n min={yearBounds[0]}\n max={yearBounds[1]}\n step={1}\n value={year}\n onChange={(e) => onYearChange(Number(e.currentTarget.value))}\n aria-label={strings.historicalYear}\n style={{ width: 150, accentColor: colors.mark }}\n />\n <input\n type=\"number\"\n min={yearBounds[0]}\n max={yearBounds[1]}\n step={1}\n value={year}\n onChange={(e) => {\n const value = Number(e.currentTarget.value);\n if (Number.isFinite(value) && e.currentTarget.value !== \"\") onYearChange(Math.round(value));\n }}\n aria-label={strings.historicalYear}\n style={{ ...font, width: 70, padding: \"3px 4px\", border: `1px solid ${colors.border}`, borderRadius: 4, background: colors.surface, color: colors.text }}\n />\n </span>\n )}\n </div>\n );\n}\n", "// Slide text markup (spec/1.0/README.md section 4): **bold**, *italic*,\n// line breaks, and blank lines between paragraphs -- nothing else. Parsed\n// into plain data rather than HTML, so a viewer can never be tricked into\n// rendering markup a story file smuggles in.\n\nexport interface TextSpan {\n text: string;\n bold?: boolean;\n italic?: boolean;\n}\n\n/** Paragraphs of lines of spans. */\nexport type ParsedText = TextSpan[][][];\n\n// Bold before italic, so a bold span's own ** pair isn't read as two empty\n// italic spans. Neither crosses a line break.\nconst MARKERS = /\\*\\*([^\\n]+?)\\*\\*|\\*([^\\n]+?)\\*/g;\n\nfunction parseLine(line: string): TextSpan[] {\n const spans: TextSpan[] = [];\n let last = 0;\n for (const match of line.matchAll(MARKERS)) {\n const index = match.index ?? 0;\n if (index > last) spans.push({ text: line.slice(last, index) });\n if (match[1] !== undefined) spans.push({ text: match[1], bold: true });\n else spans.push({ text: match[2], italic: true });\n last = index + match[0].length;\n }\n if (last < line.length) spans.push({ text: line.slice(last) });\n return spans;\n}\n\nexport function parseStoryText(text: string): ParsedText {\n return text\n .replace(/\\r\\n?/g, \"\\n\")\n .split(/\\n[ \\t]*\\n+/)\n .map((paragraph) => paragraph.replace(/^\\n+|\\n+$/g, \"\"))\n .filter((paragraph) => paragraph.length > 0)\n .map((paragraph) => paragraph.split(\"\\n\").map(parseLine));\n}\n", "// The player's own words -- the few on its buttons and map control, never\n// the story's -- in a handful of languages, chosen by the story's language\n// and then the browser's. A host with its own translations passes\n// StoryPlayer's `strings` instead.\n\nexport interface PlayerStrings {\n previous: string;\n next: string;\n close: string;\n mapMode: string;\n auto: string;\n standard: string;\n historical: string;\n /** The Auto mode's readout. */\n historicalMapYear: (year: number) => string;\n noDateStandardMap: string;\n historicalYear: string;\n /** A photo's thumbnail button, on a slide with several. */\n photoOf: (n: number, total: number) => string;\n}\n\nexport const ENGLISH: PlayerStrings = {\n previous: \"Previous\",\n next: \"Next\",\n close: \"Close\",\n mapMode: \"Map\",\n auto: \"Auto\",\n standard: \"Standard\",\n historical: \"Historical\",\n historicalMapYear: (year) => `Historical map, ${year}`,\n noDateStandardMap: \"No date here \u2014 standard map\",\n historicalYear: \"Historical map year\",\n photoOf: (n, total) => `Photo ${n} of ${total}`,\n};\n\nconst TABLE: Record<string, PlayerStrings> = {\n en: ENGLISH,\n de: {\n previous: \"Zur\u00FCck\",\n next: \"Weiter\",\n close: \"Schlie\u00DFen\",\n mapMode: \"Karte\",\n auto: \"Automatisch\",\n standard: \"Standard\",\n historical: \"Historisch\",\n historicalMapYear: (year) => `Historische Karte, ${year}`,\n noDateStandardMap: \"Kein Datum hier \u2014 Standardkarte\",\n historicalYear: \"Jahr der historischen Karte\",\n photoOf: (n, total) => `Foto ${n} von ${total}`,\n },\n es: {\n previous: \"Anterior\",\n next: \"Siguiente\",\n close: \"Cerrar\",\n mapMode: \"Mapa\",\n auto: \"Autom\u00E1tico\",\n standard: \"Est\u00E1ndar\",\n historical: \"Hist\u00F3rico\",\n historicalMapYear: (year) => `Mapa hist\u00F3rico, ${year}`,\n noDateStandardMap: \"Sin fecha aqu\u00ED \u2014 mapa est\u00E1ndar\",\n historicalYear: \"A\u00F1o del mapa hist\u00F3rico\",\n photoOf: (n, total) => `Foto ${n} de ${total}`,\n },\n fr: {\n previous: \"Pr\u00E9c\u00E9dent\",\n next: \"Suivant\",\n close: \"Fermer\",\n mapMode: \"Carte\",\n auto: \"Auto\",\n standard: \"Standard\",\n historical: \"Historique\",\n historicalMapYear: (year) => `Carte historique, ${year}`,\n noDateStandardMap: \"Pas de date ici \u2014 carte standard\",\n historicalYear: \"Ann\u00E9e de la carte historique\",\n photoOf: (n, total) => `Photo ${n} sur ${total}`,\n },\n nl: {\n previous: \"Vorige\",\n next: \"Volgende\",\n close: \"Sluiten\",\n mapMode: \"Kaart\",\n auto: \"Automatisch\",\n standard: \"Standaard\",\n historical: \"Historisch\",\n historicalMapYear: (year) => `Historische kaart, ${year}`,\n noDateStandardMap: \"Geen datum hier \u2014 standaardkaart\",\n historicalYear: \"Jaar van de historische kaart\",\n photoOf: (n, total) => `Foto ${n} van ${total}`,\n },\n};\n\n/** The best table for a list of BCP 47 tags, most preferred first:\n * \"de-CH\" matches \"de\"; English when nothing does. */\nexport function stringsFor(...languages: (string | undefined)[]): PlayerStrings {\n for (const tag of languages) {\n const base = tag?.toLowerCase().split(/[-_]/)[0];\n if (base && TABLE[base]) return TABLE[base];\n }\n return ENGLISH;\n}\n\n/** Languages with a built-in table. */\nexport const PLAYER_LANGUAGES = Object.keys(TABLE);\n", "// \"A line with years on it, and a dot\": where the current slide sits among\n// the story's dated slides, with click-to-jump per dot. SVG, since a story\n// has at most a few dozen dated slides and SVG rescales for free.\nimport { useEffect, useRef, useState } from \"react\";\nimport { axisTicks, storyDomain, tickLabel, xForYear } from \"./timelineLayout\";\nimport { PIN_PATH_D, type PlayerColors } from \"./theme\";\n\nexport const TIMELINE_HEIGHT = 70;\nconst MARGIN = 28;\n/** Where each pin's tip touches the axis. */\nconst AXIS_Y = 42;\nconst LABEL_Y = 62;\nconst CURRENT_SCALE = 0.95;\n\nexport interface TimelinePoint {\n index: number;\n year: number;\n}\n\nexport function TimelineStrip({ points, currentIndex, onSelect, colors }: {\n points: TimelinePoint[];\n currentIndex: number;\n onSelect: (index: number) => void;\n colors: PlayerColors;\n}) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState(0);\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n const observer = new ResizeObserver(([entry]) => setWidth(entry.contentRect.width));\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n const plotWidth = Math.max(0, width - MARGIN * 2);\n const domain = storyDomain(points.map((p) => p.year));\n const ticks = plotWidth > 0 ? axisTicks(domain, plotWidth) : [];\n\n return (\n <div\n ref={containerRef}\n style={{\n position: \"absolute\", left: 0, right: 0, bottom: 0, height: TIMELINE_HEIGHT, background: colors.surface,\n // Above the slide panel, whose fading background would otherwise\n // wash out any dot beneath it.\n zIndex: 2,\n }}\n >\n {width > 0 && (\n <svg width={width} height={TIMELINE_HEIGHT} style={{ display: \"block\", overflow: \"visible\" }}>\n <line x1={MARGIN} y1={AXIS_Y} x2={width - MARGIN} y2={AXIS_Y} stroke={colors.border} strokeWidth={1} />\n {ticks.map((tick) => (\n <text key={tick.x} x={MARGIN + tick.x} y={LABEL_Y} fontSize={15} fill={colors.muted} textAnchor=\"middle\">\n {tick.label}\n </text>\n ))}\n {points.map((point) => {\n const cx = MARGIN + xForYear(point.year, domain, plotWidth);\n const current = point.index === currentIndex;\n return current ? (\n <path\n key={point.index}\n d={PIN_PATH_D}\n transform={`translate(${cx - 12 * CURRENT_SCALE} ${AXIS_Y - 24 * CURRENT_SCALE}) scale(${CURRENT_SCALE})`}\n fill={colors.mark}\n stroke={colors.text}\n strokeWidth={1.5}\n style={{ cursor: \"pointer\" }}\n onClick={() => onSelect(point.index)}\n >\n <title>{tickLabel(point.year)}</title>\n </path>\n ) : (\n <circle\n key={point.index}\n cx={cx}\n cy={AXIS_Y}\n r={3.5}\n fill={colors.mark}\n stroke={colors.surface}\n strokeWidth={1.5}\n style={{ cursor: \"pointer\" }}\n onClick={() => onSelect(point.index)}\n >\n <title>{tickLabel(point.year)}</title>\n </circle>\n );\n })}\n </svg>\n )}\n </div>\n );\n}\n", "// Layout math for the timeline strip along the bottom of a story -- pure\n// numbers, no DOM.\n\n/** The year range the strip covers, with a little padding, so a lone dot\n * (or two dots in the same year) still sits on a real span. */\nexport function storyDomain(years: number[]): [number, number] {\n const min = Math.min(...years);\n const max = Math.max(...years);\n if (max - min < 1) return [min - 5, min + 5];\n const padding = (max - min) * 0.05;\n return [min - padding, max + padding];\n}\n\n/** Where a year lands across a `width`-px strip for `domain`. */\nexport function xForYear(year: number, domain: [number, number], width: number): number {\n const [start, end] = domain;\n const span = end - start;\n if (!(span > 0)) return width / 2;\n return ((year - start) / span) * width;\n}\n\nexport interface AxisTick {\n x: number;\n label: string;\n}\n\n/** Candidate tick spacings in years, coarse to fine. */\nconst TICK_STEPS = [1000, 500, 250, 100, 50, 25, 10, 5, 2, 1];\n/** Aim for roughly this many labelled ticks. */\nconst TARGET_TICKS = 8;\n\nexport function axisTicks(domain: [number, number], width: number): AxisTick[] {\n const [start, end] = domain;\n const span = end - start;\n if (!(span > 0) || width <= 0) return [];\n const step = TICK_STEPS.find((candidate) => span / candidate >= TARGET_TICKS) ?? 1;\n const ticks: AxisTick[] = [];\n for (let year = Math.ceil(start / step) * step; year < end; year += step) {\n ticks.push({ x: ((year - start) / span) * width, label: tickLabel(year) });\n }\n return ticks;\n}\n\n/** Negative years are BC: genealogical calendars have no year 0. */\nexport function tickLabel(year: number): string {\n const whole = Math.floor(year);\n return whole < 0 ? `${-whole} BC` : String(whole);\n}\n", "// The Gramps Connect Story Viewer's mark, linking to the viewer: for\n// StoryPlayer's `brand` slot on standalone pages (the viewer site, a page\n// a program writes around a story), so whoever is watching can find where\n// it came from.\nimport type { CSSProperties } from \"react\";\n\nexport const VIEWER_NAME = \"Gramps Connect Story Viewer\";\nexport const VIEWER_URL = \"https://dsblank.github.io/gramps-connect-stories/\";\n\n/** The Gramps Connect icon (app/public/favicon.svg): a three-box pedigree\n * on a blue tile, which holds up on light and dark headers alike. */\nexport function ViewerIcon({ size = 24 }: { size?: number }) {\n return (\n <svg viewBox=\"0 0 64 64\" width={size} height={size} aria-hidden=\"true\" focusable=\"false\">\n <rect width=\"64\" height=\"64\" rx=\"15\" fill=\"#1866ba\" />\n <g transform=\"translate(9.6 9.6) scale(0.7)\">\n <path d=\"M25 32h7M32 15v34M32 15h7M32 49h7\" fill=\"none\" stroke=\"#fff\" strokeWidth=\"5\" strokeLinecap=\"round\" />\n <g fill=\"#fff\">\n <rect x=\"1\" y=\"24\" width=\"24\" height=\"16\" rx=\"4\" />\n <rect x=\"39\" y=\"7\" width=\"24\" height=\"16\" rx=\"4\" />\n <rect x=\"39\" y=\"41\" width=\"24\" height=\"16\" rx=\"4\" />\n </g>\n </g>\n </svg>\n );\n}\n\nexport interface ViewerBrandProps {\n /** Where the link goes; the viewer site by default. */\n href?: string;\n /** \"_blank\" (the default) opens it beside the story, not in its place. */\n target?: string;\n size?: number;\n}\n\n/** The icon, as a link to the viewer. */\nexport function ViewerBrand({ href = VIEWER_URL, target = \"_blank\", size = 24 }: ViewerBrandProps) {\n const style: CSSProperties = { display: \"flex\", lineHeight: 0, borderRadius: 6 };\n return (\n <a\n href={href}\n target={target}\n rel={target === \"_blank\" ? \"noopener\" : undefined}\n title={VIEWER_NAME}\n aria-label={VIEWER_NAME}\n style={style}\n >\n <ViewerIcon size={size} />\n </a>\n );\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["useEffect", "useRef", "useState", "jsx", "useEffect", "useRef", "useState", "jsx", "jsxs", "useRef", "useState", "useEffect", "jsx", "jsxs", "useState", "useEffect", "useRef", "jsx", "jsxs"]
7
7
  }
@@ -0,0 +1,89 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Feature } from "geojson";
3
+ import { type OverlayCorners } from "../mapEdit";
4
+ import { type MapEditorStrings } from "./mapEditorStrings";
5
+ /** An image overlay as the host gives and gets it: `image` is the host's
6
+ * own name for the picture (a media handle, a story media key), turned
7
+ * into something displayable by `resolveImage`. */
8
+ export interface MapEditorOverlay {
9
+ image: string;
10
+ /** Top-left, top-right, bottom-right, bottom-left, as [lng, lat]. */
11
+ corners: OverlayCorners;
12
+ name?: string;
13
+ /** 0-1; undefined is fully opaque. */
14
+ opacity?: number;
15
+ /** Stacking rank; higher draws on top. */
16
+ order?: number;
17
+ }
18
+ /** What's in the editor, for the host to save. */
19
+ export interface MapEditorResult {
20
+ features: Feature[];
21
+ overlays: MapEditorOverlay[];
22
+ }
23
+ export interface MapEditorStatus {
24
+ /** Anything drawn or placed (shapes or images). */
25
+ hasDrawn: boolean;
26
+ /** Anything to save at all: drawn, placed, or locked. */
27
+ hasContent: boolean;
28
+ }
29
+ export interface MapEditorHandle {
30
+ /** Everything as it is now: shapes put back together (see map-edit's
31
+ * assembleForSaving) and the image overlays. */
32
+ getResult: () => MapEditorResult;
33
+ /** The box around everything drawn and placed, [west, south, east,
34
+ * north], or null with nothing there yet. */
35
+ drawnBounds: () => [number, number, number, number] | null;
36
+ /** Places an image in the middle of the current view, at its own aspect
37
+ * ratio, and selects it. False if that image is already one of the
38
+ * overlays. Throws if it can't be loaded. */
39
+ addImage: (image: string) => Promise<boolean>;
40
+ /** Adds shapes (an imported map file's, see parseMapFile) after what's
41
+ * there, and frames them. */
42
+ addFeatures: (features: Feature[]) => void;
43
+ }
44
+ export interface MapEditorProps {
45
+ /** What to edit: null while the host is still loading it (the map shows,
46
+ * and fills in once this arrives). Read once; later changes are
47
+ * ignored. */
48
+ initial: {
49
+ features: Feature[];
50
+ overlays: MapEditorOverlay[];
51
+ } | null;
52
+ /** Where to look when what's loaded has nothing to frame. */
53
+ initialView?: {
54
+ center: [number, number];
55
+ zoom: number;
56
+ };
57
+ /** A displayable URL for an overlay's image. */
58
+ resolveImage: (image: string) => Promise<string>;
59
+ /** Called with a URL from resolveImage once the editor is done with it
60
+ * (e.g. URL.revokeObjectURL). */
61
+ releaseImage?: (url: string) => void;
62
+ /** Shows an Image button; the host picks one and calls addImage. */
63
+ onAddImage?: () => void;
64
+ onStatus?: (status: MapEditorStatus) => void;
65
+ /** A map that wouldn't load, or a terra-draw failure. */
66
+ onError?: (message: string) => void;
67
+ dark?: boolean;
68
+ /** A new shape's color until another is picked. */
69
+ defaultColor?: string;
70
+ /** For point labels drawn on the map. */
71
+ labelColors?: {
72
+ text: string;
73
+ surface: string;
74
+ };
75
+ /** Room to leave above the tool panel for the host's own bars. */
76
+ toolbarTop?: number;
77
+ /** Whether an image overlay has a name to edit (default true). */
78
+ overlayNames?: boolean;
79
+ /** More fields for the selected image overlay, below its own. */
80
+ renderOverlayOptions?: (image: string) => ReactNode;
81
+ strings?: Partial<MapEditorStrings>;
82
+ }
83
+ export declare const MapEditor: import("react").ForwardRefExoticComponent<MapEditorProps & import("react").RefAttributes<MapEditorHandle>>;
84
+ /** How to use the map editor, for the host to show where it likes (Gramps
85
+ * Connect: an info button's modal). */
86
+ export declare function MapEditorHelp({ strings: stringOverrides, images }: {
87
+ strings?: Partial<MapEditorStrings>;
88
+ images?: boolean;
89
+ }): import("react").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import type { Place } from "../types";
2
+ import type { Bounds } from "../player/geo";
3
+ import type { PlaceSearch } from "./placeSearch";
4
+ import type { EditorStrings } from "./strings";
5
+ export type PlaceUpdate = (place: Place | undefined) => Place | undefined;
6
+ export interface PlaceFieldProps {
7
+ place: Place | undefined;
8
+ /** The story's other places, to reuse. */
9
+ choices: Place[];
10
+ /** Where the map looks while this place has no point. */
11
+ around?: Bounds | null;
12
+ /** The story's language: search results are named in it. */
13
+ language?: string;
14
+ /** Null hides the search. */
15
+ search: PlaceSearch | null;
16
+ dark?: boolean;
17
+ strings: EditorStrings;
18
+ /** A change, as a function of the place as it is by then. */
19
+ onChange: (update: PlaceUpdate, key: string) => void;
20
+ /** Opens the shapes-and-old-maps editor; without it, those are only
21
+ * kept. */
22
+ onEditMap?: () => void;
23
+ }
24
+ export declare function PlaceField({ place, choices, around, language, search, dark, strings, onChange, onEditMap }: PlaceFieldProps): import("react").JSX.Element;