Skip to content

Commit e7dd06e

Browse files
Show a trailer and learn line in the figure carousel
Threads an optional learn line through the figure data and renders it, next to a trailer play control, in the carousel info overlay.
1 parent 1ea9e8f commit e7dd06e

6 files changed

Lines changed: 136 additions & 8 deletions

File tree

client/src/api/figures.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface FigureBase {
1515
export interface TranslatedFigure {
1616
name: string;
1717
about: string;
18+
learn?: string; // Golden line: "You will learn to ..."
1819
id: string;
1920
}
2021

@@ -24,6 +25,7 @@ export interface TranslatedFigure {
2425
interface FigureTranslation {
2526
name?: string;
2627
about?: string;
28+
learn?: string;
2729
}
2830

2931
/**
@@ -83,6 +85,7 @@ export function getHistoricalFigures(language: string = 'en'): TranslatedFigure[
8385
return {
8486
name: translation.name || figure.baseNameEn,
8587
about: translation.about || `Information about ${figure.baseNameEn} is not available.`,
88+
learn: translation.learn,
8689
id: figure.id
8790
};
8891
});

client/src/assets/translations/ui-de.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,8 @@
547547
"about": "Über",
548548
"select": "Wählen",
549549
"wisdom": "Weisheit",
550+
"trailerPlay": "Intro abspielen",
551+
"trailerPause": "Pause",
550552
"echoOf": "Echo von",
551553
"echoOfName": "Echo von {name}",
552554
"categories": {

client/src/assets/translations/ui-en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,8 @@
545545
"about": "About",
546546
"select": "Select",
547547
"wisdom": "Wisdom",
548+
"trailerPlay": "Play intro",
549+
"trailerPause": "Pause",
548550
"echoOf": "Echo of",
549551
"echoOfName": "Echo of {name}",
550552
"categories": {

client/src/components/FigureCarousel.css

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,4 +1218,85 @@
12181218
animation:none!important;
12191219
transform: none!important;
12201220
}
1221+
}
1222+
1223+
/* ============================================================
1224+
Figure page revision — golden "you will learn" line + trailer
1225+
play control inside the info-overlay. Additive, existing tokens.
1226+
============================================================ */
1227+
1228+
/* Golden line — the "what you will learn" hook. Always visible. */
1229+
.figure-carousel .figure-learn {
1230+
margin: 7px auto 0;
1231+
max-width: 540px;
1232+
color: var(--secondary-color);
1233+
font-family: var(--font-content, 'Libre Caslon Text', Georgia, serif);
1234+
font-size: clamp(0.9375rem, 0.875rem + 0.5vw, 1.25rem);
1235+
font-weight: 500;
1236+
line-height: 1.4;
1237+
text-align: center;
1238+
text-shadow:
1239+
0 1px 4px color-mix(in srgb, var(--utility-pure-black) 70%, transparent),
1240+
0 0 12px color-mix(in srgb, var(--gold-subtle) var(--a-30), transparent);
1241+
}
1242+
1243+
/* Trailer play control — paired with the golden line, always visible. */
1244+
.figure-carousel .figure-trailer-btn {
1245+
display: flex;
1246+
width: fit-content;
1247+
align-items: center;
1248+
justify-content: center;
1249+
gap: 7px;
1250+
margin: 9px auto 2px;
1251+
min-height: 44px; /* WCAG touch target */
1252+
padding: 0 18px;
1253+
border-radius: 999px;
1254+
border: 1px solid color-mix(in srgb, var(--gold-subtle) var(--a-40), transparent);
1255+
background: color-mix(in srgb, var(--gold-subtle) var(--a-15), transparent);
1256+
color: var(--secondary-color);
1257+
font-family: var(--ui-font-family);
1258+
font-size: 0.8125rem;
1259+
font-weight: 600;
1260+
letter-spacing: 0.02em;
1261+
cursor: pointer;
1262+
-webkit-tap-highlight-color: transparent;
1263+
transition: background 0.25s ease, border-color 0.25s ease;
1264+
}
1265+
1266+
.figure-carousel .figure-trailer-btn:hover {
1267+
background: color-mix(in srgb, var(--gold-subtle) var(--a-30), transparent);
1268+
border-color: color-mix(in srgb, var(--gold-subtle) var(--a-70), transparent);
1269+
}
1270+
1271+
.figure-carousel .figure-trailer-btn.is-active {
1272+
background: color-mix(in srgb, var(--gold-subtle) var(--a-30), transparent);
1273+
border-color: var(--secondary-color);
1274+
}
1275+
1276+
.figure-carousel .figure-trailer-btn:focus-visible {
1277+
outline: 2px solid var(--secondary-color);
1278+
outline-offset: 2px;
1279+
}
1280+
1281+
.figure-carousel .figure-trailer-btn svg {
1282+
flex-shrink: 0;
1283+
}
1284+
1285+
.figure-carousel .figure-trailer-label {
1286+
white-space: nowrap;
1287+
}
1288+
1289+
/* Small mobile (iPhone SE etc.) — keep the overlay compact. */
1290+
@media (max-width: 375px) {
1291+
.figure-carousel .figure-learn {
1292+
font-size: 0.9375rem;
1293+
line-height: 1.35;
1294+
margin-top: 5px;
1295+
padding: 0 6px;
1296+
}
1297+
1298+
.figure-carousel .figure-trailer-btn {
1299+
margin: 7px auto 0;
1300+
padding: 0 15px;
1301+
}
12211302
}

client/src/components/FigureCarousel.tsx

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ import React, { useState, useEffect, useCallback, FC, MouseEvent, KeyboardEvent
22
import { useSwipeable } from 'react-swipeable';
33
import { getHistoricalFigures } from '../api/figures';
44
import { CategoryTab, ActionButton } from './Button';
5-
import { BookOpen, Check, Sparkle } from "@phosphor-icons/react";
5+
import { BookOpen, Check, Sparkle, Play, Pause } from "@phosphor-icons/react";
66
import OptimizedImage from './OptimizedImage';
77
import { useTranslation } from '../hooks/useTranslation';
88
import WisdomMapModal from './WisdomMapModal';
99
import type { Figure, Seed } from '../types/global';
1010
import { useUIStore } from '../stores/uiStore';
1111
import EchoExplainerHelp, { ECHO_EXPLAINER_HELP_ID } from './EchoExplainerHelp';
12+
import { useFigureTrailer } from '../hooks/useFigureTrailer';
1213
import './FigureCarousel.css';
1314

1415
interface FigureCarouselProps {
@@ -49,6 +50,9 @@ const FigureCarousel: FC<FigureCarouselProps> = ({
4950
}) => {
5051
const { tString, tNode, language } = useTranslation();
5152

53+
// Figure page trailer — standalone audio player (play-on-tap, never autoplay)
54+
const trailer = useFigureTrailer();
55+
5256
// Echo explainer helper — show once for users who haven't seen it
5357
const shouldShowHelp = useUIStore((state) => state.shouldShowHelp);
5458
const [showEchoHelp, setShowEchoHelp] = useState<boolean>(() => shouldShowHelp(ECHO_EXPLAINER_HELP_ID));
@@ -300,6 +304,12 @@ const FigureCarousel: FC<FigureCarouselProps> = ({
300304
return () => clearTimeout(preloadTimer);
301305
}, [currentIndex, figures, hasNavigated, preloadedImages]);
302306

307+
// Stop any trailer when the figure changes or the carousel closes — never
308+
// leave audio playing behind a swipe, a category switch, or a close.
309+
useEffect(() => {
310+
trailer.stop();
311+
}, [currentFigure?.id, isOpen, trailer.stop]);
312+
303313
const handleSelect = () => {
304314
if (onSelectFigure) onSelectFigure(currentFigure);
305315
else console.error('onSelectFigure is not defined');
@@ -460,6 +470,18 @@ const FigureCarousel: FC<FigureCarouselProps> = ({
460470
const handleInfoOverlayClick = () => {
461471
setShowFullInfo(!showFullInfo);
462472
};
473+
474+
const trailerStatus = trailer.activeId === currentFigure.id ? trailer.status : 'idle';
475+
const trailerEngaged = trailerStatus === 'loading' || trailerStatus === 'playing';
476+
477+
const handleTrailerClick = (e: MouseEvent<HTMLButtonElement>) => {
478+
e.stopPropagation(); // don't toggle the about overlay
479+
trailer.toggle(currentFigure.id, language);
480+
};
481+
// Keep Enter/Space on the trailer button from also toggling the overlay.
482+
const handleTrailerKeyDown = (e: KeyboardEvent<HTMLButtonElement>) => {
483+
if (e.key === 'Enter' || e.key === ' ') e.stopPropagation();
484+
};
463485

464486
return (
465487
<div className="main-image-section">
@@ -486,15 +508,32 @@ const FigureCarousel: FC<FigureCarouselProps> = ({
486508
aria-label={tString('carousel.toggleInfo', 'Toggle figure information')}
487509
>
488510
<h2>{getDisplayName(currentFigure)}</h2>
511+
512+
{currentFigure.learn && (
513+
<p className="figure-learn">{currentFigure.learn}</p>
514+
)}
515+
516+
<button
517+
type="button"
518+
className={`figure-trailer-btn ${trailerEngaged ? 'is-active' : ''}`}
519+
onClick={handleTrailerClick}
520+
onKeyDown={handleTrailerKeyDown}
521+
>
522+
{trailerEngaged
523+
? <Pause size={16} weight="fill" />
524+
: <Play size={16} weight="fill" />}
525+
<span className="figure-trailer-label">
526+
{trailerEngaged
527+
? tString('figures.trailerPause', 'Pause')
528+
: tString('figures.trailerPlay', 'Play intro')}
529+
</span>
530+
</button>
531+
489532
{showFullInfo && currentFigure.about && (
490533
<div className="figure-about-text">
491-
{currentFigure.about.split('\n\n').map((line, index) => {
492-
// Check if this line contains bullet points (tags)
493-
if (line.includes('•')) {
494-
return <div key={index} className="golden-tags no-justify">{line}</div>;
495-
}
496-
return <div key={index}>{line}</div>;
497-
})}
534+
{currentFigure.about.split('\n\n').map((para, index) => (
535+
<div key={index}>{para}</div>
536+
))}
498537
</div>
499538
)}
500539
</div>

client/src/types/global.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface Figure {
1212
id: string; // e.g., 'plato', 'aurelius'
1313
name: string; // Display name
1414
about?: string; // Description
15+
learn?: string; // Golden line: "You will learn to ..." (figure page revision)
1516
image?: string; // Avatar path
1617
topic?: string; // Main topics/themes
1718
metadata?: FigureMetadata;

0 commit comments

Comments
 (0)