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 (
-
-
-
-
+
+
+
+
+ {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 (
<>
-
-
-
-
-
+
- {isMobile && (
-
-
+ className={cn(styles.firstPage, {
+ [styles.fadeOutFirstPage]: switchPage,
+ [styles.fadeInFirstPage]: !switchPage,
+ })}
+ >
+
+
+
+
+
+
+ {isMobile && (
+
+
+
+ )}
+
+
setSwitchPage(!switchPage)}
+ />
+
+ {!isMobile && (
+
+
)}
-
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 ? (
+
+ ) : (
+
+ )}
+
>
);
}