diff --git a/public/keepsimple_/assets/longevity/dna-1.mp4 b/public/keepsimple_/assets/longevity/dna-1.mp4 new file mode 100644 index 0000000..9c6184e Binary files /dev/null and b/public/keepsimple_/assets/longevity/dna-1.mp4 differ diff --git a/public/keepsimple_/assets/longevity/habits/damage-type-mobile.bg.webp b/public/keepsimple_/assets/longevity/habits/damage-type-mobile.bg.webp new file mode 100644 index 0000000..7996c89 Binary files /dev/null and b/public/keepsimple_/assets/longevity/habits/damage-type-mobile.bg.webp differ diff --git a/public/keepsimple_/assets/longevity/study/page-switcher-back.svg b/public/keepsimple_/assets/longevity/study/page-switcher-back.svg new file mode 100644 index 0000000..c0a1e30 --- /dev/null +++ b/public/keepsimple_/assets/longevity/study/page-switcher-back.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/keepsimple_/assets/longevity/study/page-switcher.svg b/public/keepsimple_/assets/longevity/study/page-switcher.svg index 810b442..a7f0c47 100644 --- a/public/keepsimple_/assets/longevity/study/page-switcher.svg +++ b/public/keepsimple_/assets/longevity/study/page-switcher.svg @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - + + - - - - + + + + - diff --git a/public/keepsimple_/assets/longevity/study/study-flip-bg.webp b/public/keepsimple_/assets/longevity/study/study-flip-bg.webp new file mode 100644 index 0000000..d50615d Binary files /dev/null and b/public/keepsimple_/assets/longevity/study/study-flip-bg.webp differ diff --git a/public/keepsimple_/assets/longevity/study/study-flip-card-bg.webp b/public/keepsimple_/assets/longevity/study/study-flip-card-bg.webp new file mode 100644 index 0000000..dbf90dd Binary files /dev/null and b/public/keepsimple_/assets/longevity/study/study-flip-card-bg.webp differ diff --git a/src/components/Modal/Modal.tsx b/src/components/Modal/Modal.tsx index d663a89..7a2d40d 100644 --- a/src/components/Modal/Modal.tsx +++ b/src/components/Modal/Modal.tsx @@ -142,7 +142,7 @@ const Modal: FC = ({
close left line = ({ hacksQuote, quoteAuthor, isHacks, + setSwitchPage, + switchPage, }) => { return (
= ({ [styles.hacksFlipCard]: isHacks, })} > + {'Page setSwitchPage && setSwitchPage(!switchPage)} + /> {isHacks ? (
void; + switchPage?: boolean; }; diff --git a/src/components/longevity/Hero/Hero.module.scss b/src/components/longevity/Hero/Hero.module.scss index ced343e..4eecf8b 100644 --- a/src/components/longevity/Hero/Hero.module.scss +++ b/src/components/longevity/Hero/Hero.module.scss @@ -24,11 +24,12 @@ margin-top: 55px; h1 { - font-size: 36px; + width: 290px; + font-size: 24px !important; } h2 { - font-size: 16px; + font-size: 16px !important; } } } diff --git a/src/components/longevity/Hero/Hero.tsx b/src/components/longevity/Hero/Hero.tsx index 052a1de..0a1a97b 100644 --- a/src/components/longevity/Hero/Hero.tsx +++ b/src/components/longevity/Hero/Hero.tsx @@ -11,6 +11,7 @@ const Hero: FC = ({}) => { Tag={'h2'} showRightIcon={false} showLeftIcon={false} + className={styles.author} /> ); diff --git a/src/components/longevity/LongevitySubSection/LongevitySubSection.tsx b/src/components/longevity/LongevitySubSection/LongevitySubSection.tsx index 9666bf1..f06243e 100644 --- a/src/components/longevity/LongevitySubSection/LongevitySubSection.tsx +++ b/src/components/longevity/LongevitySubSection/LongevitySubSection.tsx @@ -1,11 +1,14 @@ -import { FC } from 'react'; +import { FC, useState } from 'react'; import Image from 'next/image'; import cn from 'classnames'; import { Tooltip as ReactTooltip } from 'react-tooltip'; import WhyDoThisTooltip from '@components/longevity/WhyDoThisTooltip'; +import Modal from '@components/Modal'; import Heading from '@components/Heading'; +import { useIsWidthLessThan } from '@hooks/useScreenSize'; + import longevityData from '@data/longevity'; import { LongevitySubSectionProps } from './LongevitySubSection.types'; @@ -23,61 +26,83 @@ const LongevitySubSection: FC = ({ isHacks, }) => { const { habitTooltipTitle } = longevityData[locale]; - + const isMobile = useIsWidthLessThan(956); + const [openMobileModal, setOpenMobileModal] = useState(false); return ( -
-
- {title} - +
+
+ {title} + + {whatDamages && ( + { + if (isMobile) { + setOpenMobileModal(true); + } + }} + > + {habitTooltipTitle} + + )} + {date &&
{date}
} + {whatDamages && !isMobile && ( + + + + )} +
+
- {whatDamages && ( - - {habitTooltipTitle} - - )} - {date &&
{date}
} - {whatDamages && ( - - - - )} -
-
- {date &&
{date}
} + > + {date &&
{date}
} - {description ? ( -
- ) : ( -
{children}
- )} -
-
+ {description ? ( +
+ ) : ( +
{children}
+ )} +
+
+ {isMobile && openMobileModal && ( + setOpenMobileModal(false)} + > + + + )} + ); }; diff --git a/src/components/longevity/MobileNavigation/MobileNavigation.module.scss b/src/components/longevity/MobileNavigation/MobileNavigation.module.scss index 5d8dc55..730f39a 100644 --- a/src/components/longevity/MobileNavigation/MobileNavigation.module.scss +++ b/src/components/longevity/MobileNavigation/MobileNavigation.module.scss @@ -8,7 +8,7 @@ background-image: url('/keepsimple_/assets/longevity/nav-hover-bg.png'); color: #ffffff; border: unset; - padding: 13px 16px; + padding: 15px 16px; width: 100%; background-repeat: no-repeat; background-size: cover; @@ -197,7 +197,6 @@ } .nextPageBtn { - //background-image: url('/keepsimple_/assets/longevity/navbar-borders.svg'); background-size: cover; border: 1px solid #000; margin: 20px 16px; diff --git a/src/components/longevity/StudySection/StudySection.module.scss b/src/components/longevity/StudySection/StudySection.module.scss index 81dd983..deefd77 100644 --- a/src/components/longevity/StudySection/StudySection.module.scss +++ b/src/components/longevity/StudySection/StudySection.module.scss @@ -2,9 +2,39 @@ .studySection { max-width: 948px; +} + +.cardContainer { position: relative; } +.firstPage { + position: relative; + z-index: 13; +} + +.flipCardWrapper { + position: absolute; + inset: 0; + z-index: 0; +} + +.fadeOutFirstPage { + animation: fadeOutAdnScaleDown 0.6s forwards; +} + +.fadeInFirstPage { + animation: showFlipCardAndScaleUp 0.6s forwards; +} + +.showFlipCard { + animation: showFlipCardAndScaleUp 0.6s forwards; +} + +.hideFlipCard { + animation: fadeOutAdnScaleDown 0.6s forwards; +} + .headline { height: 27px; position: relative; @@ -74,6 +104,7 @@ p { line-height: 1.5; margin: 0; + padding-right: 15px; a { color: #000000d9; @@ -84,9 +115,10 @@ .pageSwitcher { position: relative; - bottom: 60px; - right: 0; - left: 94%; + bottom: 70px; + right: 5px; + left: 93%; + z-index: 5; cursor: pointer; } @@ -137,7 +169,35 @@ display: none; } } + .modal { + div { + background-repeat: repeat-y !important; + background-position: right !important; + } + } .hacksModalBody { display: flex !important; } } + +@keyframes fadeOutAdnScaleDown { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.95); + } +} + +@keyframes showFlipCardAndScaleUp { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} diff --git a/src/components/longevity/StudySection/StudySection.tsx b/src/components/longevity/StudySection/StudySection.tsx index a5e6a08..448df90 100644 --- a/src/components/longevity/StudySection/StudySection.tsx +++ b/src/components/longevity/StudySection/StudySection.tsx @@ -35,69 +35,87 @@ const StudySection: FC = ({ return ( <>
-
- {title} - -
-
+
- {isMobile && ( -
- + className={cn(styles.firstPage, { + [styles.fadeOutFirstPage]: switchPage, + [styles.fadeInFirstPage]: !switchPage, + })} + > +
+ {title} + +
+
+
+ {isMobile && ( +
+ +
+ )} +
+ {'Page setSwitchPage(!switchPage)} + /> +
+ {!isMobile && ( +
+
)}
- {'Page setSwitchPage(!switchPage)} - /> - {!isMobile && ( - - )}
{isMobile && openModal && ( = ({ : '/keepsimple_/assets/longevity/study/flipped-card-bg.png' } bodyClassName={isHacks ? styles.hacksModalBody : styles.modalBody} + className={styles.modal} > (null); - const probeVideoRef = useRef(null); - const [targetHeight, setTargetHeight] = useState(0); - const [renderedCount, setRenderedCount] = useState(1); const [isLongevityProtocolPage, setIsLongevityProtocolPage] = useState(false); const isMobile = useIsWidthLessThan(956); - const recalc = useCallback(() => { - if (!isLongevityProtocolPage) return; - - const section = sectionRef.current; - const probe = probeVideoRef.current; - if (!section || !probe) return; - - const h = Math.ceil(section.getBoundingClientRect().height); - const one = Math.ceil(probe.getBoundingClientRect().height); - - if (h <= 0 || one <= 0) return; - - const needed = Math.max(1, Math.ceil(h / one) + 1); - - setTargetHeight(prev => (prev === h ? prev : h)); - setRenderedCount(prev => (needed > prev ? needed : prev)); - }, [isLongevityProtocolPage]); - useEffect(() => { if (router.pathname.startsWith('/tools/longevity-protocol')) { setIsLongevityProtocolPage(true); @@ -51,92 +24,165 @@ export default function Layout({ children }: { children: React.ReactNode }) { } }, [router.pathname]); - useEffect(() => { - if (!isLongevityProtocolPage) { - setTargetHeight(0); - setRenderedCount(1); - } else { - requestAnimationFrame(recalc); - } - }, [isLongevityProtocolPage]); - + const videoRef = useRef(null); + const canvasRef = useRef(null); + const videoLayerRef = useRef(null); useEffect(() => { if (!isLongevityProtocolPage) return; - const handleDone = () => requestAnimationFrame(recalc); - router.events.on('routeChangeComplete', handleDone); + const layer = videoLayerRef.current; + const canvas = canvasRef.current; + const video = videoRef.current; + if (!layer || !canvas || !video) return; - return () => { - router.events.off('routeChangeComplete', handleDone); + const ctx = canvas.getContext('2d', { alpha: true }); + if (!ctx) return; + + ctx.imageSmoothingEnabled = true; + // @ts-ignore + if (ctx.imageSmoothingQuality) ctx.imageSmoothingQuality = 'high'; + + let raf = 0; + let stopped = false; + let visible = true; + + const dpr = Math.min(2, window.devicePixelRatio || 1); + + const getSizes = () => { + const h = Math.max(1, Math.ceil(layer.getBoundingClientRect().height)); + const w = Math.max(1, Math.ceil(canvas.getBoundingClientRect().width)); + return { w, h }; }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [router.events, isLongevityProtocolPage]); - useLayoutEffect(() => { - if (!isLongevityProtocolPage) return; + const resize = () => { + const { w, h } = getSizes(); - recalc(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isLongevityProtocolPage]); + canvas.style.width = `${w}px`; + canvas.style.height = `${h}px`; - useEffect(() => { - if (!isLongevityProtocolPage) return; - const section = sectionRef.current; - if (!section) return; + canvas.width = Math.max(1, Math.round(w * dpr)); + canvas.height = Math.max(1, Math.round(h * dpr)); - let raf = 0; - const ro = new ResizeObserver(() => { - cancelAnimationFrame(raf); - raf = requestAnimationFrame(recalc); - }); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + }; + + const draw = () => { + if (stopped) return; + + if (!visible) { + raf = requestAnimationFrame(draw); + return; + } + + const { w, h } = getSizes(); + + const vw = video.videoWidth; + const vh = video.videoHeight; + + if (!vw || !vh || video.paused || video.ended) { + raf = requestAnimationFrame(draw); + return; + } - ro.observe(section); - window.addEventListener('resize', recalc); + const scale = w / vw; + + const tileH = Math.max(1, Math.round(vh * scale)); + + ctx.clearRect(0, 0, w, h); + + for (let y = 0; y < h + tileH; y += tileH) { + ctx.drawImage(video, 0, y, w, tileH); + } + + raf = requestAnimationFrame(draw); + }; + + const ro = new ResizeObserver(() => resize()); + ro.observe(layer); + + const io = new IntersectionObserver( + entries => { + visible = entries.some(e => e.isIntersecting); + }, + { threshold: 0.01 }, + ); + io.observe(layer); + + const onMeta = () => { + resize(); + video.play().catch(() => {}); + }; + video.addEventListener('loadedmetadata', onMeta); + + const onVis = () => { + if (document.visibilityState === 'visible') { + resize(); + video.play().catch(() => {}); + } + }; + document.addEventListener('visibilitychange', onVis); + + const onRouteDone = () => requestAnimationFrame(resize); + router.events.on('routeChangeComplete', onRouteDone); + + resize(); + raf = requestAnimationFrame(draw); return () => { - ro.disconnect(); - window.removeEventListener('resize', recalc); + stopped = true; cancelAnimationFrame(raf); + ro.disconnect(); + io.disconnect(); + video.removeEventListener('loadedmetadata', onMeta); + document.removeEventListener('visibilitychange', onVis); + router.events.off('routeChangeComplete', onRouteDone); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isLongevityProtocolPage]); + }, [isLongevityProtocolPage, router.events]); return ( <>
- {isLongevityProtocolPage ? ( - <> - -
- {isMobile ? : } -
-
- {Array.from({ length: renderedCount }).map((_, i) => ( -
-
{children}
-
-
- - ) : ( -
{children}
- )} + {isLongevityProtocolPage && } +
+ {isLongevityProtocolPage ? ( + isMobile ? ( + + ) : ( + + ) + ) : null} + {isLongevityProtocolPage ? ( +
+
+ +
+ +
{children}
+
+ ) : ( +
{children}
+ )} +
); }