From a8f318ce16f41dea8de0ba48ba6f27a8d314f038 Mon Sep 17 00:00:00 2001 From: Aaron Chan <42254254+aaronchan32@users.noreply.github.com> Date: Thu, 30 May 2024 00:38:36 -0700 Subject: [PATCH] feat: revert css-only solution and dynamically determine height of overview page squash for desktop --- .../Overview/Expectations/Expectations.scss | 55 +------------- .../Overview/Expectations/Expectations.tsx | 23 +++--- .../components/Overview/Squash/Squash.scss | 71 ++++++++++++++++++ src/app/components/Overview/Squash/Squash.tsx | 39 ++++++++++ .../Squash/hooks/useHandleSquashSizing.ts | 74 +++++++++++++++++++ .../SquashGooglyEyes.scss | 25 +++++-- .../SquashGooglyEyes.tsx | 2 +- .../hooks/useEyesFollowCursor.ts | 22 ++---- 8 files changed, 224 insertions(+), 87 deletions(-) create mode 100644 src/app/components/Overview/Squash/Squash.scss create mode 100644 src/app/components/Overview/Squash/Squash.tsx create mode 100644 src/app/components/Overview/Squash/hooks/useHandleSquashSizing.ts rename src/app/components/Overview/{SquashGoogleEyes => SquashGooglyEyes}/SquashGooglyEyes.scss (71%) rename src/app/components/Overview/{SquashGoogleEyes => SquashGooglyEyes}/SquashGooglyEyes.tsx (97%) rename src/app/components/Overview/{SquashGoogleEyes => SquashGooglyEyes}/hooks/useEyesFollowCursor.ts (71%) diff --git a/src/app/components/Overview/Expectations/Expectations.scss b/src/app/components/Overview/Expectations/Expectations.scss index 6efc216..6bbf221 100644 --- a/src/app/components/Overview/Expectations/Expectations.scss +++ b/src/app/components/Overview/Expectations/Expectations.scss @@ -1,5 +1,6 @@ #overview { .expectations-container { + max-height: 550px; position: relative; background-color: $secondary-yellow; padding: 2rem; @@ -29,6 +30,7 @@ h3 { max-width: 16em; + height: fit-content; } .expectations-list { @@ -49,58 +51,5 @@ } } } - - .squash-container { - position: absolute; - width: 100%; - height: 100%; - display: grid; - justify-content: center; - align-items: flex-end; - bottom: calc(60% + 7vw); - left: 55%; - - @media (min-width: 500px) { - bottom: calc(18% + 7vw); - left: 30%; - } - - @media (min-width: 760px) { - transform: rotate(0deg); - right: 0; - left: 0; - bottom: -10%; - } - - @media (min-width: 1250px) { - bottom: 0; - } - - .squash-image-wrapper { - transform: scaleX(-1) rotate(-80deg); - position: relative; - width: 100%; - // Ideally height is about 100% subtracted by half of expectations list (~8em) and padding (2rem) - @media (min-width: 500px) { - height: calc(100% - 8em - 2rem); - transform: rotate(-90deg); - } - - @media (min-width: 760px) { - transform: rotate(0deg); - } - - @media (min-width: 1080px) { - height: calc(100% - 6.25em - 2rem); - } - } - - .squash { - object-fit: contain; - object-position: bottom; - height: 100%; - width: 100%; - } - } } } diff --git a/src/app/components/Overview/Expectations/Expectations.tsx b/src/app/components/Overview/Expectations/Expectations.tsx index 4de639f..44cb8d2 100644 --- a/src/app/components/Overview/Expectations/Expectations.tsx +++ b/src/app/components/Overview/Expectations/Expectations.tsx @@ -1,7 +1,8 @@ -import Image from 'next/image'; -import Squash from '@/assets/images/squash.webp'; +'use client'; + +import { useRef } from 'react'; import './Expectations.scss'; -import SquashGooglyEyes from '../SquashGoogleEyes/SquashGooglyEyes'; +import Squash from '@/components/Overview/Squash/Squash'; type Expectation = { id: string; @@ -11,10 +12,13 @@ type ExpectationsProps = { expectationsList: Expectation[]; }; export default function Expectations({ expectationsList }: ExpectationsProps) { + const containerRef = useRef(null); + const listRef = useRef(null); + const titleRef = useRef(null); return ( -
-

{`Here's what you can expect from UP-Grade:`}

-
    +
    +

    {`Here's what you can expect from UP-Grade:`}

    +
      {expectationsList.map(expectation => (
    • @@ -22,12 +26,7 @@ export default function Expectations({ expectationsList }: ExpectationsProps) {
    • ))}
    -
    -
    - Squash - -
    -
    +
    ); } diff --git a/src/app/components/Overview/Squash/Squash.scss b/src/app/components/Overview/Squash/Squash.scss new file mode 100644 index 0000000..ddef259 --- /dev/null +++ b/src/app/components/Overview/Squash/Squash.scss @@ -0,0 +1,71 @@ +.squash-container { + position: absolute; + width: 100%; + height: 100%; + display: grid; + justify-content: center; + align-items: flex-end; + bottom: calc(60% + 7vw); + left: 55%; + + @media (min-width: 500px) { + bottom: calc(18% + 7vw); + left: 40%; + } + + @media (min-width: 760px) { + transform: rotate(0deg); + right: 0; + left: 0; + bottom: -10%; + } + + @media (min-width: 1250px) { + bottom: 0; + } + + .squash-image-wrapper { + transform: scaleX(-1) rotate(-80deg); + position: relative; + width: 100%; + // Ideally height is about 100% subtracted by half of expectations list (~8em) and padding (2rem) + @media (min-width: 500px) { + width: min(90%, 520px); + transform: rotate(-90deg); + } + + @media (min-width: 760px) { + width: auto; + transform: rotate(0deg); + } + + @media (min-width: $tablet-breakpoint) { + opacity: 0; + } + + &.pop-up { + opacity: 1; + animation: PopUp 0.2s ease-in-out forwards; + } + + @keyframes PopUp { + 0% { + transform: scale(0); + } + + 80% { + transform: scale(1.2); + } + 100% { + transform: scale(1); + } + } + } + + .squash { + object-fit: contain; + object-position: bottom; + height: 100%; + width: 100%; + } +} diff --git a/src/app/components/Overview/Squash/Squash.tsx b/src/app/components/Overview/Squash/Squash.tsx new file mode 100644 index 0000000..9da7bee --- /dev/null +++ b/src/app/components/Overview/Squash/Squash.tsx @@ -0,0 +1,39 @@ +'use client'; + +import Image from 'next/image'; +import SquashGooglyEyes from '../SquashGooglyEyes/SquashGooglyEyes'; +import SquashImage from '@/assets/images/squash.webp'; +import './Squash.scss'; +import { RefObject, useRef } from 'react'; +import { useHandleSquashSizing } from './hooks/useHandleSquashSizing'; + +type SquashProps = { + expectationRefs: { + containerRef: RefObject; + listRef: RefObject; + titleRef: RefObject; + }; +}; + +export default function Squash({ expectationRefs }: SquashProps) { + const { containerRef, listRef, titleRef } = expectationRefs; + const squashContainerRef = useRef(null); + const squashWrapperRef = useRef(null); + + useHandleSquashSizing({ + containerRef, + listRef, + titleRef, + squashContainerRef, + squashWrapperRef + }); + + return ( +
    +
    + Squash + +
    +
    + ); +} diff --git a/src/app/components/Overview/Squash/hooks/useHandleSquashSizing.ts b/src/app/components/Overview/Squash/hooks/useHandleSquashSizing.ts new file mode 100644 index 0000000..bba9fe1 --- /dev/null +++ b/src/app/components/Overview/Squash/hooks/useHandleSquashSizing.ts @@ -0,0 +1,74 @@ +import { RefObject, useEffect } from 'react'; + +// Threshold for space under expectation list to determine if squash should be aligned to the left +const SPACE_THRESHOLD = 130; +// The smaller end of the squash is about 60% of its height +const SQUASH_SMALLER_END_RATIO = 0.67; // 309 / 465 + +const TABLET_BREAKPOINT = 1000; + +type UseHandleSquashSizingProps = { + containerRef: RefObject; + listRef: RefObject; + titleRef: RefObject; + squashContainerRef: RefObject; + squashWrapperRef: RefObject; +}; + +export const useHandleSquashSizing = ({ + containerRef, + listRef, + titleRef, + squashContainerRef, + squashWrapperRef +}: UseHandleSquashSizingProps) => { + useEffect(() => { + const container = containerRef.current; + const squashContainer = squashContainerRef.current; + const squashWrapper = squashWrapperRef.current; + const title = titleRef.current; + const listRect = listRef.current?.getBoundingClientRect(); + + if (!container || !title || !listRect || !squashContainer || !squashWrapper) + return; + + const handleSquashSizing = () => { + if (window.innerWidth < TABLET_BREAKPOINT) { + // Reset styles potentially set by logic below + squashContainer.style.justifyContent = ''; + squashWrapper.style.height = ''; + squashWrapper.classList.remove('pop-up'); + } else { + const containerRect = container.getBoundingClientRect(); + const containerPadding = parseInt( + getComputedStyle(container).paddingTop + ); + const spaceUnderList = containerRect.height - listRect.height; + console.log({ spaceUnderList }); + const spaceUnderTitle = + containerRect.height - title.getBoundingClientRect().height; + + // If there's too little space under list, make the squash aligned to the left + if (spaceUnderList < SPACE_THRESHOLD) { + squashContainer.style.justifyContent = 'flex-start'; + squashWrapper.style.height = `${spaceUnderTitle / 1.5}px`; + } else { + const squashHeight = + spaceUnderList + + listRect.height * (1 - SQUASH_SMALLER_END_RATIO) - + containerPadding; + squashWrapper.style.height = `${squashHeight}px`; + } + squashWrapper.classList.add('pop-up'); + } + }; + + handleSquashSizing(); + + window.addEventListener('resize', handleSquashSizing); + + return () => { + window.removeEventListener('resize', handleSquashSizing); + }; + }, [containerRef, listRef, squashContainerRef, squashWrapperRef, titleRef]); +}; diff --git a/src/app/components/Overview/SquashGoogleEyes/SquashGooglyEyes.scss b/src/app/components/Overview/SquashGooglyEyes/SquashGooglyEyes.scss similarity index 71% rename from src/app/components/Overview/SquashGoogleEyes/SquashGooglyEyes.scss rename to src/app/components/Overview/SquashGooglyEyes/SquashGooglyEyes.scss index 4404817..019c023 100644 --- a/src/app/components/Overview/SquashGoogleEyes/SquashGooglyEyes.scss +++ b/src/app/components/Overview/SquashGooglyEyes/SquashGooglyEyes.scss @@ -8,6 +8,21 @@ } .googly-eyes { + &.blink { + animation: Blink 0.18s forwards; + } + + @keyframes Blink { + 0% { + transform: scaleY(1); + } + 50% { + transform: scaleY(0.1); + } + 100% { + transform: scaleY(1); + } + } transform: scale(0.3); @media (min-width: 380px) { @@ -19,19 +34,19 @@ } @media (min-width: 760px) { - transform: scale(0.7); + transform: scale(0.65); } @media (min-width: 1250px) { - transform: scale(0.85); + transform: scale(0.8); } @media (min-width: 1440px) { - transform: scale(1); + transform: scale(0.95); } @media (min-width: 1750px) { - transform: scale(1.2); + transform: scale(1.1); } } @@ -63,7 +78,7 @@ @media (min-width: 760px) { top: auto; - bottom: 30%; + bottom: 28%; right: 9%; } } diff --git a/src/app/components/Overview/SquashGoogleEyes/SquashGooglyEyes.tsx b/src/app/components/Overview/SquashGooglyEyes/SquashGooglyEyes.tsx similarity index 97% rename from src/app/components/Overview/SquashGoogleEyes/SquashGooglyEyes.tsx rename to src/app/components/Overview/SquashGooglyEyes/SquashGooglyEyes.tsx index fb1615f..91286a8 100644 --- a/src/app/components/Overview/SquashGoogleEyes/SquashGooglyEyes.tsx +++ b/src/app/components/Overview/SquashGooglyEyes/SquashGooglyEyes.tsx @@ -21,7 +21,7 @@ export default function SquashGooglyEyes() {
    - + diff --git a/src/app/components/Overview/SquashGoogleEyes/hooks/useEyesFollowCursor.ts b/src/app/components/Overview/SquashGooglyEyes/hooks/useEyesFollowCursor.ts similarity index 71% rename from src/app/components/Overview/SquashGoogleEyes/hooks/useEyesFollowCursor.ts rename to src/app/components/Overview/SquashGooglyEyes/hooks/useEyesFollowCursor.ts index 6b110ec..74219a3 100644 --- a/src/app/components/Overview/SquashGoogleEyes/hooks/useEyesFollowCursor.ts +++ b/src/app/components/Overview/SquashGooglyEyes/hooks/useEyesFollowCursor.ts @@ -21,16 +21,6 @@ export const useEyesFollowCursor = ({ const { eye1Ref, eye2Ref } = eyeRefs; useEffect(() => { - if (window.innerWidth < 768) { - // Shift eyes to not be centered for mobile - const pupil1 = pupil1Ref.current; - const pupil2 = pupil2Ref.current; - if (!pupil1 || !pupil2) return; - pupil1.style.transform = 'translate(5%, 5%)'; - pupil2.style.transform = 'translate(-5%, -5%)'; - return; - } - const handleEyesFollowCursor = (e: MouseEvent) => { const eye1 = eye1Ref.current; const eye2 = eye2Ref.current; @@ -46,9 +36,9 @@ export const useEyesFollowCursor = ({ const eye2CenterX = eye2Rect.left + eye2Rect.width / 2; const eye2CenterY = eye2Rect.top + eye2Rect.height / 2; - const maxPupilDistance1 = EYE_RADIUS / 3.5; - const maxPupilDistance2 = EYE_RADIUS / 3.5; + const maxPupilDistance = EYE_RADIUS / 3.5; + // Make one angle negative to make eyes look in different directions const angle1 = -Math.atan2( e.clientY - eye1CenterY, e.clientX - eye1CenterX @@ -57,10 +47,10 @@ export const useEyesFollowCursor = ({ e.clientY - eye2CenterY, e.clientX - eye2CenterX ); - const pupil1X = Math.cos(angle1) * maxPupilDistance1; - const pupil1Y = Math.sin(angle1) * maxPupilDistance1; - const pupil2X = Math.cos(angle2) * maxPupilDistance2; - const pupil2Y = Math.sin(angle2) * maxPupilDistance2; + const pupil1X = Math.cos(angle1) * maxPupilDistance; + const pupil1Y = Math.sin(angle1) * maxPupilDistance; + const pupil2X = Math.cos(angle2) * maxPupilDistance; + const pupil2Y = Math.sin(angle2) * maxPupilDistance; pupil1.style.transform = `translate(${pupil1X}px, ${pupil1Y}px)`; pupil2.style.transform = `translate(${pupil2X}px, ${pupil2Y}px)`;