diff --git a/src/components/Moments/AwardsMoment/AwardsMoment.styles.ts b/src/components/Moments/AwardsMoment/AwardsMoment.styles.ts index a21218f..2a4011e 100644 --- a/src/components/Moments/AwardsMoment/AwardsMoment.styles.ts +++ b/src/components/Moments/AwardsMoment/AwardsMoment.styles.ts @@ -1,6 +1,20 @@ const styles = { container: { backgroundImage: 'url(https://stories-api-public.s3.amazonaws.com/award-moment-background.jpg)', + backgroundPosition: 'center', + backgroundSize: 'cover', + minHeight: 600, + + '@media (max-width: 768px)': { + minHeight: 420, + paddingBottom: 16, + paddingTop: 16, + }, + + '@media (max-width: 320px)': { + paddingBottom: 4, + paddingTop: 4, + }, }, divider: { background: 'rgba(255, 255, 255, 0.45)', diff --git a/src/components/UI/AwardCertificate/AwardCertificate.styles.ts b/src/components/UI/AwardCertificate/AwardCertificate.styles.ts index fd87fc1..e1ddabb 100644 --- a/src/components/UI/AwardCertificate/AwardCertificate.styles.ts +++ b/src/components/UI/AwardCertificate/AwardCertificate.styles.ts @@ -12,11 +12,17 @@ const styles = { flexDirection: 'column', height: '250px', justifyContent: 'center', + maxWidth: '100%', + minHeight: 250, position: 'relative', textAlign: 'center', - width: '400px', + width: { xs: '100%', sm: 360, md: 400 }, } as SxProps, + content: { + maxWidth: '90%', + }, + corner: { topLeft: (color: string) => ({ borderRight: `${cornerSize}px solid transparent`, @@ -61,15 +67,15 @@ const styles = { mb: 1, }), recipient: { - fontSize: '1.5rem', + fontSize: { xs: '1.2rem', sm: '1.5rem' }, fontWeight: 700, }, subtitle: { - fontSize: '1.2rem', + fontSize: { xs: '1rem', sm: '1.2rem' }, fontWeight: 500, }, description: { - fontSize: '1rem', + fontSize: { xs: '0.9rem', sm: '1rem' }, mt: 0.5, }, title: { diff --git a/src/components/UI/AwardCertificate/AwardCertificate.tsx b/src/components/UI/AwardCertificate/AwardCertificate.tsx index 9b08817..959a441 100644 --- a/src/components/UI/AwardCertificate/AwardCertificate.tsx +++ b/src/components/UI/AwardCertificate/AwardCertificate.tsx @@ -37,24 +37,26 @@ const AwardCertificate = observer(({ award, moment, sx }: AwardCertificateProps) - {title ? {title} : null} + + {title ? {title} : null} - {subtitle ? {subtitle} : null} + {subtitle ? {subtitle} : null} - {description ? {description} : null} + {description ? {description} : null} - {recipient ? {recipient} : null} + {recipient ? {recipient} : null} - {conferredBy ? ( - - Conferred by - {' '} + {conferredBy ? ( + + Conferred by + {' '} - {conferredBy.title || conferredBy.label} - - ) : null} + {conferredBy.title || conferredBy.label} + + ) : null} - {year ? {year} : null} + {year ? {year} : null} + ); }); diff --git a/src/components/UI/Cards/Cards.styles.ts b/src/components/UI/Cards/Cards.styles.ts index 30e3f35..81f137e 100644 --- a/src/components/UI/Cards/Cards.styles.ts +++ b/src/components/UI/Cards/Cards.styles.ts @@ -11,20 +11,27 @@ const styles = { width: '100%', '& .cards-carousel-layout-swiper-slide': { - maxWidth: '80%', - width: 'auto', + width: '100%', + maxWidth: { + xs: '100%', + md: '80%', + }, '& .swiper-slide-shadow': { display: 'none', }, }, + '& .swiper-pagination': { + position: 'relative', + marginTop: '16px', + }, }, }, gridLayoutRoot: { m: 0, }, item: { - flexShrink: 0, + width: '100%', }, orbitLayoutRoot: { alignItems: 'center', diff --git a/src/components/UI/Cards/CardsCarouselLayout.tsx b/src/components/UI/Cards/CardsCarouselLayout.tsx index 9ad3441..5a58454 100644 --- a/src/components/UI/Cards/CardsCarouselLayout.tsx +++ b/src/components/UI/Cards/CardsCarouselLayout.tsx @@ -1,3 +1,6 @@ +import 'swiper/css'; +import 'swiper/css/pagination'; + import Box from '@mui/material/Box'; import { observer } from 'mobx-react-lite'; import type { Swiper as SwiperClass } from 'swiper'; @@ -26,26 +29,24 @@ const CardsCarouselLayout = observer(({ delay: 4000, // ms between auto swipes disableOnInteraction: false, // keeps autoplay after user swipes }) : undefined; - - const slidesPerView = layoutOptions?.slides_per_view ?? 'auto'; - const slideGap = layoutOptions?.slide_gap; + const slidesPerView = layoutOptions?.slides_per_view ?? 3; + const slideGap = layoutOptions?.slide_gap ?? 16; return ( diff --git a/src/components/UI/Cards/CardsOrbitLayout.styles.ts b/src/components/UI/Cards/CardsOrbitLayout.styles.ts index 50c1086..0389d0a 100644 --- a/src/components/UI/Cards/CardsOrbitLayout.styles.ts +++ b/src/components/UI/Cards/CardsOrbitLayout.styles.ts @@ -1,7 +1,7 @@ const styles = { orbitLayoutRoot: { height: '600px', - overflow: 'visible', + padding: '0 64px', perspective: '1200px', position: 'relative', width: '100%', @@ -15,28 +15,29 @@ const styles = { transformOrigin: 'center center', transformStyle: 'preserve-3d', transition: 'transform 0.8s ease-in-out', + width: 0, + height: 0, }), orbitItem: ( angle: number, radius: number, - scale: number, ) => ({ alignItems: 'center', display: 'flex', height: '100%', justifyContent: 'center', - left: 0, + left: '50%', position: 'absolute', - top: 0, + top: '50%', transform: ` + translate(-50%, -50%) rotateY(${angle}deg) translateZ(${radius}px) - scale(${scale}) + scale(1) `, transformOrigin: 'center center', transition: 'transform 0.8s ease-in-out', - width: '100%', }), navLeft: { diff --git a/src/components/UI/Cards/CardsOrbitLayout.tsx b/src/components/UI/Cards/CardsOrbitLayout.tsx index 0813f55..963bf24 100644 --- a/src/components/UI/Cards/CardsOrbitLayout.tsx +++ b/src/components/UI/Cards/CardsOrbitLayout.tsx @@ -9,45 +9,45 @@ import type { CardsLayoutProps } from './Cards.types'; import CardsItem from './CardsItem'; import styles from './CardsOrbitLayout.styles'; -const radius = 500; - const CardsOrbitLayout = observer(({ children, - itemSx, keyFn, onChange, }: CardsLayoutProps) => { + const radius = 500; const [activeIndex, setActiveIndex] = useState(0); const count = children.length; const step = 360 / count; - const [rotation, setRotation] = useState(0); const handleNextCard = () => { const newIndex = (activeIndex + 1) % count; setActiveIndex(newIndex); - setRotation((prev) => prev - step); onChange?.(newIndex); }; const handlePrevCard = () => { const newIndex = (activeIndex - 1 + count) % count; setActiveIndex(newIndex); - setRotation((prev) => prev + step); onChange?.(newIndex); }; return ( - + {children.map((child, index) => { - const angle = (360 / count) * index; - const relativeAngle = ((index - activeIndex + count) % count) * (360 / count); - const normalizedAngle = ((relativeAngle + 180) % 360) - 180; - const scale = 1 - 0.5 * (Math.abs(normalizedAngle) / 180); + const angle = index * step; return ( - - + + {child} diff --git a/src/components/UI/Cards/CardsZigZagLayout.styles.ts b/src/components/UI/Cards/CardsZigZagLayout.styles.ts index eca312c..b48a7d7 100644 --- a/src/components/UI/Cards/CardsZigZagLayout.styles.ts +++ b/src/components/UI/Cards/CardsZigZagLayout.styles.ts @@ -51,8 +51,12 @@ const styles = { }, zigZagGrid: { - marginTop: '-25px', + flexDirection: { xs: 'column', md: 'row' }, + maxWidth: '1200px', + marginLeft: 'auto', + marginRight: 'auto', position: 'relative', + textAlign: { xs: 'center', md: 'inherit' }, zIndex: 1, }, @@ -60,13 +64,15 @@ const styles = { alignItems: 'center', display: 'flex', justifyContent: 'center', - maxWidth: 400, + marginBottom: { xs: 2, md: 0 }, + maxWidth: { xs: '100%', md: 400 }, transformStyle: 'preserve-3d', transition: 'transform 0.4s ease, box-shadow 0.4s ease', }, zigZagCaption: { container: { + overflowWrap: 'break-word', textAlign: 'center', width: '100%', marginBottom: '12px', @@ -104,6 +110,7 @@ const styles = { '&:hover': { backgroundColor: 'primary.dark', }, + wordBreak: 'break-word', }, }, }; diff --git a/src/components/UI/Cards/CardsZigZagLayout.tsx b/src/components/UI/Cards/CardsZigZagLayout.tsx index da7ede7..2026edf 100644 --- a/src/components/UI/Cards/CardsZigZagLayout.tsx +++ b/src/components/UI/Cards/CardsZigZagLayout.tsx @@ -1,3 +1,4 @@ +import { useMediaQuery, useTheme } from '@mui/material'; import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid2'; import React, { isValidElement } from 'react'; @@ -12,6 +13,8 @@ export default function CardsZigZagLayout({ keyFn, sx, }: CardsLayoutProps) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); const cards = React.Children.toArray(children); type CaptionBlock = @@ -82,31 +85,32 @@ export default function CardsZigZagLayout({ - {isEven ? ( - <> - {renderCaption} - - - {card} - - - - ) : ( - <> - - - {card} - - + + + {card} + + - {renderCaption} - - )} + + {renderCaption} + ); diff --git a/src/components/UI/IdBadge/IdBadge.styles.ts b/src/components/UI/IdBadge/IdBadge.styles.ts index 1f1a4bb..e7815f8 100644 --- a/src/components/UI/IdBadge/IdBadge.styles.ts +++ b/src/components/UI/IdBadge/IdBadge.styles.ts @@ -15,13 +15,15 @@ const styles = { margin: '0 auto', overflow: 'hidden', position: 'relative', - width: 240, transition: 'transform 0.6s ease, box-shadow 0.6s ease', + width: 240, + '&:hover': { transform: 'translateY(-10px) scale(1.03) rotateX(2deg)', + '& .badge-dates': { - transform: 'translateY(0)', opacity: 1, + transform: 'translateY(0)', }, }, @@ -45,6 +47,12 @@ const styles = { width: '100%', }, + '&:hover .info-panel': { + opacity: 1, + pointerEvents: 'auto', + transform: 'translate(0, 0) scale(1)', + }, + '& .badge-body': { bottom: 15, position: 'absolute', @@ -74,18 +82,63 @@ const styles = { }, }, + badgeInfo: { + alignItems: 'flex-start', + backgroundColor: '#1976d2', + borderBottomLeftRadius: 60, + cursor: 'pointer', + display: 'flex', + height: 56, + justifyContent: 'flex-end', + padding: 2, + position: 'absolute', + right: 0, + transition: 'all 0.3s ease', + top: 0, + width: 56, + zIndex: 4, + + '& .info-icon': { + color: '#fff', + fontSize: 20, + }, + + '&:hover': { + height: '33%', + width: '33%', + }, + }, + topGraphic: { left: '50%', position: 'absolute', top: -50, transform: 'translateX(-50%)', zIndex: 1, + '& img': { display: 'block', - width: '100%', height: 'auto', + width: '100%', }, }, + + infoPanel: { + borderRadius: 8, + boxShadow: '0 6px 18px rgba(0,0,0,0.18)', + display: 'flex', + flexDirection: 'column', + inset: 0, + opacity: 0, + overflowY: 'auto', + padding: 2, + pointerEvents: 'none', + position: 'absolute', + transform: 'scale(0.5)', + transformOrigin: 'top right', + transition: 'transform 0.32s cubic-bezier(.2,.9,.2,1), opacity 0.28s ease', + zIndex: 5, + }, }; export default styles; diff --git a/src/components/UI/IdBadge/IdBadge.tsx b/src/components/UI/IdBadge/IdBadge.tsx index 911dffa..4bddc1c 100644 --- a/src/components/UI/IdBadge/IdBadge.tsx +++ b/src/components/UI/IdBadge/IdBadge.tsx @@ -1,7 +1,9 @@ +import InfoIcon from '@mui/icons-material/Info'; import Box from '@mui/material/Box'; import type { ButtonContentBlock } from '../../../types'; import { Content } from '../Content'; +import IdBadgeInfoPanel from '../IdBadgeInfoPanel/IdBadgeInfoPanel'; import styles from './IdBadge.styles'; import type { IdBadgeProps } from './IdBadge.types'; @@ -12,6 +14,7 @@ export default function IdBadge({ idBadge }: IdBadgeProps) { content, information, } = idBadge; + const buttonBlocks = information?.blocks.filter( (b): b is ButtonContentBlock => b.type === 'BUTTON' && 'button' in b, ); @@ -26,47 +29,53 @@ export default function IdBadge({ idBadge }: IdBadgeProps) { - - {backgroundImage?.url ? ( - - ) : null} + {backgroundImage?.url ? ( + + ) : null} - - {logo?.url ? ( - - {logo.alt - - ) : null} + + + - {content ? ( - - - - ) : null} + + + - {buttonBlocks && buttonBlocks.length > 0 ? ( - - {buttonBlocks.map((block) => { - const href = 'collection_id' in block.button - ? `/collections/${block.button.collection_id}` - : '#'; - return ( - - {block.button.label} - - ); - })} - - ) : null} - + + {logo?.url ? ( + + {logo.alt + + ) : null} + + {content ? ( + + + + ) : null} + + {buttonBlocks && buttonBlocks.length > 0 ? ( + + {buttonBlocks.map((block) => { + const href = 'collection_id' in block.button + ? `/collections/${block.button.collection_id}` + : '#'; + return ( + + {block.button.label} + + ); + })} + + ) : null} diff --git a/src/components/UI/IdBadgeInfoPanel/IdBadgeInfoPanel.tsx b/src/components/UI/IdBadgeInfoPanel/IdBadgeInfoPanel.tsx new file mode 100644 index 0000000..b1604df --- /dev/null +++ b/src/components/UI/IdBadgeInfoPanel/IdBadgeInfoPanel.tsx @@ -0,0 +1,20 @@ +import Box from '@mui/material/Box'; + +import type { Content as ContentType } from '../../../types'; +import { Content } from '../Content'; +import styles from '../IdBadge/IdBadge.styles'; + +interface IdBadgeInfoPanelProps { + readonly caption?: ContentType; +} +export default function IdBadgeInfoPanel({ caption }: IdBadgeInfoPanelProps = {}) { + if (!caption) return null; + + return ( + + + + + + ); +} diff --git a/src/hooks/useStoriesAPITheme.ts b/src/hooks/useStoriesAPITheme.ts index 5bdcb43..43f3b19 100644 --- a/src/hooks/useStoriesAPITheme.ts +++ b/src/hooks/useStoriesAPITheme.ts @@ -14,6 +14,9 @@ const useStoriesAPITheme = (themeOptions?: ThemeOptions) => { const muiTheme = mergedThemeOptions ? storiesAPI.theme.overrideTheme(mergedThemeOptions) : theme; const breakpointIsMobile = useMediaQuery(muiTheme.breakpoints.down('sm')); const isMobile = storiesAPI.theme.getIsMobile(breakpointIsMobile); + + storiesAPI.theme.isMobile = isMobile; + return { isDesktop: !isMobile, isMobile, diff --git a/src/state/momentStore.ts b/src/state/momentStore.ts index 5df3b77..4fa9642 100644 --- a/src/state/momentStore.ts +++ b/src/state/momentStore.ts @@ -246,6 +246,10 @@ export default class MomentStore { this.moments.onEdit(); } + protected get root() { + return this.moments.root; + } + reset() { this.moment = deepCopy(this.initialMoment); } diff --git a/src/state/moments/awardsMomentStore.ts b/src/state/moments/awardsMomentStore.ts index 2a57b77..d924075 100644 --- a/src/state/moments/awardsMomentStore.ts +++ b/src/state/moments/awardsMomentStore.ts @@ -38,12 +38,38 @@ export default class AwardsMomentStore extends CardsBaseMomentStore