diff --git a/resources/js/guest/pages/HomePage.tsx b/resources/js/guest/pages/HomePage.tsx index 6bbc06f..27d38f4 100644 --- a/resources/js/guest/pages/HomePage.tsx +++ b/resources/js/guest/pages/HomePage.tsx @@ -12,7 +12,7 @@ import GalleryPreview from '../components/GalleryPreview'; import { useGuestIdentity } from '../context/GuestIdentityContext'; import { useEventData } from '../hooks/useEventData'; import { useGuestTaskProgress } from '../hooks/useGuestTaskProgress'; -import { Sparkles, UploadCloud, X, RefreshCw, Timer } from 'lucide-react'; +import { ChevronDown, Sparkles, UploadCloud, X, RefreshCw, Timer } from 'lucide-react'; import { useTranslation, type TranslateFn } from '../i18n/useTranslation'; import { useEventBranding } from '../context/EventBrandingContext'; import type { EventBranding } from '../types/event-branding'; @@ -482,6 +482,49 @@ function MissionActionCard({ const shellRadius = `${radius + 10}px`; const normalizeText = (value: string | undefined | null) => (value ?? '').trim().toLowerCase().replace(/\s+/g, ' '); + const [expandedTaskId, setExpandedTaskId] = React.useState(null); + const lastSlideIndexRef = React.useRef(initialIndex); + const titleRefs = React.useRef(new Map()); + const [expandableTitles, setExpandableTitles] = React.useState>({}); + + const measureTitleOverflow = React.useCallback(() => { + setExpandableTitles((prev) => { + let hasChange = false; + const next = { ...prev }; + + titleRefs.current.forEach((element, id) => { + if (!element || element.dataset.collapsed !== 'true') { + return; + } + const isOverflowing = element.scrollHeight > element.clientHeight + 1; + if (next[id] !== isOverflowing) { + next[id] = isOverflowing; + hasChange = true; + } + }); + + return hasChange ? next : prev; + }); + }, []); + + React.useLayoutEffect(() => { + if (typeof window === 'undefined') { + return; + } + const raf = window.requestAnimationFrame(measureTitleOverflow); + return () => window.cancelAnimationFrame(raf); + }, [measureTitleOverflow, cards, headingFont]); + + React.useEffect(() => { + if (typeof window === 'undefined') { + return; + } + const handleResize = () => { + window.requestAnimationFrame(measureTitleOverflow); + }; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [measureTitleOverflow]); const renderCardContent = (card: MissionPreview | null) => { const theme = getEmotionTheme(card?.emotion ?? null); @@ -489,6 +532,15 @@ function MissionActionCard({ const durationMinutes = card?.duration ?? 3; const titleFont = headingFont ? { fontFamily: headingFont } : undefined; const gradientBackground = card ? theme.gradientBackground : `linear-gradient(135deg, ${primary}, ${secondary})`; + const isExpanded = card ? expandedTaskId === card.id : false; + const isExpandable = Boolean(card && expandableTitles[card.id]); + const titleClamp = isExpanded ? '' : 'line-clamp-2 sm:line-clamp-3'; + const titleClasses = `text-xl font-semibold leading-snug text-slate-900 sm:text-2xl break-words py-1 min-h-[3.75rem] sm:min-h-[4.5rem] ${titleClamp}`; + const titleId = card ? `task-title-${card.id}` : undefined; + const toggleExpanded = () => { + if (!card) return; + setExpandedTaskId((prev) => (prev === card.id ? null : card.id)); + }; return (
{card ? ( -

- {card.title} -

+ isExpandable ? ( + + ) : ( +

{ + if (card) { + titleRefs.current.set(card.id, node); + } + }} + data-collapsed={!isExpanded} + className={titleClasses} + style={{ ...titleFont, textShadow: '0 6px 18px rgba(15,23,42,0.28)' }} + > + {card.title} +

+ ) ) : loading ? (
@@ -622,8 +710,8 @@ function MissionActionCard({ effect="cards" modules={[EffectCards]} cardsEffect={{ - perSlideRotate: 2, - perSlideOffset: 4, + perSlideRotate: 1, + perSlideOffset: 1, slideShadows: false, }} slidesPerView={1} @@ -639,6 +727,10 @@ function MissionActionCard({ }} onSlideChange={(instance) => { const realIndex = typeof instance.realIndex === 'number' ? instance.realIndex : instance.activeIndex ?? 0; + if (realIndex !== lastSlideIndexRef.current) { + setExpandedTaskId(null); + } + lastSlideIndexRef.current = realIndex; onIndexChange(realIndex, slides.length); }} className="!pb-2"