Mountain: pause on click + figure 3x bigger

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Athena 2026-04-30 23:37:58 +02:00
parent db7d7530f4
commit badba0a9f7

View file

@ -59,6 +59,7 @@ const CARD_H = 128;
export default function MountainClimb() { export default function MountainClimb() {
const [step, setStep] = useState(-1); const [step, setStep] = useState(-1);
const [paused, setPaused] = useState(false);
const sectionRef = useRef<HTMLDivElement>(null); const sectionRef = useRef<HTMLDivElement>(null);
const started = useRef(false); const started = useRef(false);
@ -79,12 +80,12 @@ export default function MountainClimb() {
return () => obs.disconnect(); return () => obs.disconnect();
}, []); }, []);
// Advance one step every 1.6 s // Advance one step every 1.6 s — paused when user clicked
useEffect(() => { useEffect(() => {
if (step < 0 || step >= STEPS.length - 1) return; if (step < 0 || step >= STEPS.length - 1 || paused) return;
const t = setTimeout(() => setStep((s) => s + 1), 1600); const t = setTimeout(() => setStep((s) => s + 1), 1600);
return () => clearTimeout(t); return () => clearTimeout(t);
}, [step]); }, [step, paused]);
const atSummit = step === STEPS.length - 1; const atSummit = step === STEPS.length - 1;
const wp = step >= 0 ? WP[step] : WP[0]; const wp = step >= 0 ? WP[step] : WP[0];
@ -92,8 +93,8 @@ export default function MountainClimb() {
// Card x: centred on person, clamped inside viewBox // Card x: centred on person, clamped inside viewBox
const rawCX = wp.x - CARD_W / 2; const rawCX = wp.x - CARD_W / 2;
const cardX = Math.min(Math.max(rawCX, 8), VW - CARD_W - 8); const cardX = Math.min(Math.max(rawCX, 8), VW - CARD_W - 8);
// Card y: above figure head, clamped to top of viewBox // Card y: above figure head (figure is 3× scaled → head at ~87 SVG units above foot)
const cardY = Math.max(wp.y - CARD_H - 58, 4); const cardY = Math.max(wp.y - CARD_H - 110, 4);
const replay = () => { const replay = () => {
started.current = true; started.current = true;
@ -115,7 +116,12 @@ export default function MountainClimb() {
</div> </div>
{/* ── Mountain animation (md+) ── */} {/* ── Mountain animation (md+) ── */}
<div className="hidden md:block"> <div
className="hidden md:block"
onClick={() => !atSummit && setPaused((p) => !p)}
style={{ cursor: atSummit ? "default" : "pointer" }}
title={paused ? "Klicken um fortzusetzen" : "Klicken zum Pausieren"}
>
<svg <svg
viewBox={`0 0 ${VW} ${VH}`} viewBox={`0 0 ${VW} ${VH}`}
className="w-full" className="w-full"
@ -152,27 +158,23 @@ export default function MountainClimb() {
transform={`translate(${wp.x}, ${wp.y}) rotate(${wp.lean})`} transform={`translate(${wp.x}, ${wp.y}) rotate(${wp.lean})`}
style={{ transition: "transform 0.75s cubic-bezier(0.4,0.2,0.2,1)" }} style={{ transition: "transform 0.75s cubic-bezier(0.4,0.2,0.2,1)" }}
> >
{/* Inner group — receives bounce animation at summit */} {/* Inner group — 3× scale + bounce at summit */}
<g <g
transform="scale(3)"
className={atSummit ? "mc-bounce" : ""} className={atSummit ? "mc-bounce" : ""}
style={{ transformBox: "fill-box", transformOrigin: "50% 100%" }} style={{ transformBox: "fill-box", transformOrigin: "50% 100%" }}
> >
{/* Head */} {/* strokeWidth 0.5 = visually 1.5 after scale(3) */}
<circle cx="0" cy="-29" r="7.5" fill="none" stroke="#4f46e5" strokeWidth="1.5" /> <circle cx="0" cy="-29" r="7.5" fill="none" stroke="#4f46e5" strokeWidth="0.5" />
{/* Body */} <line x1="0" y1="-21" x2="0" y2="-3" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
<line x1="0" y1="-21" x2="0" y2="-3" stroke="#4f46e5" strokeWidth="1.5" strokeLinecap="round" /> <line x1="0" y1="-16" x2="-10" y2="-8" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
{/* Left arm (down, holding mountain) */} <line x1="0" y1="-16" x2="10" y2="-24" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
<line x1="0" y1="-16" x2="-10" y2="-8" stroke="#4f46e5" strokeWidth="1.5" strokeLinecap="round" /> <line x1="0" y1="-3" x2="-7" y2="10" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
{/* Right arm (raised) */} <line x1="0" y1="-3" x2="7" y2="10" stroke="#4f46e5" strokeWidth="0.5" strokeLinecap="round" />
<line x1="0" y1="-16" x2="10" y2="-24" stroke="#4f46e5" strokeWidth="1.5" strokeLinecap="round" />
{/* Legs */}
<line x1="0" y1="-3" x2="-7" y2="10" stroke="#4f46e5" strokeWidth="1.5" strokeLinecap="round" />
<line x1="0" y1="-3" x2="7" y2="10" stroke="#4f46e5" strokeWidth="1.5" strokeLinecap="round" />
{/* Flag — appears at summit, attached to raised right arm */}
{atSummit && ( {atSummit && (
<g className="mc-fade-in" transform="translate(9, -25)"> <g className="mc-fade-in" transform="translate(9, -25)">
<line x1="0" y1="0" x2="0" y2="-22" stroke="#6366f1" strokeWidth="1.5" strokeLinecap="round" /> <line x1="0" y1="0" x2="0" y2="-22" stroke="#6366f1" strokeWidth="0.5" strokeLinecap="round" />
<polygon points="0,-22 18,-15 0,-8" fill="#6366f1" /> <polygon points="0,-22 18,-15 0,-8" fill="#6366f1" />
</g> </g>
)} )}
@ -188,7 +190,7 @@ export default function MountainClimb() {
x1={cardX + CARD_W / 2} x1={cardX + CARD_W / 2}
y1={cardY + CARD_H} y1={cardY + CARD_H}
x2={wp.x} x2={wp.x}
y2={wp.y - 37} y2={wp.y - 90}
stroke="#c7d2fe" stroke="#c7d2fe"
strokeWidth="1" strokeWidth="1"
strokeDasharray="4 3" strokeDasharray="4 3"
@ -236,6 +238,13 @@ export default function MountainClimb() {
)} )}
</svg> </svg>
{/* Pause hint */}
{step >= 0 && !atSummit && (
<p className="text-center text-xs text-slate-400 mt-2 select-none">
{paused ? "▶ Klicken zum Fortsetzen" : "⏸ Klicken zum Pausieren"}
</p>
)}
{/* Replay button */} {/* Replay button */}
{atSummit && ( {atSummit && (
<div className="text-center mt-4"> <div className="text-center mt-4">