diff --git a/src/app/components/ApplyNow/ApplyNow.scss b/src/app/components/ApplyNow/ApplyNow.scss new file mode 100644 index 0000000..ad83257 --- /dev/null +++ b/src/app/components/ApplyNow/ApplyNow.scss @@ -0,0 +1,20 @@ +.apply-now-svg { + align-self: flex-end; + margin-inline: auto; + opacity: 0; + font-size: $p-font; + display: flex; + + .mask { + fill: none; + stroke: #fff; + stroke-dasharray: 0; + stroke-dashoffset: 0; + } + + @keyframes dash { + to { + stroke-dashoffset: 0; + } + } +} diff --git a/src/app/components/ApplyNow/ApplyNow.tsx b/src/app/components/ApplyNow/ApplyNow.tsx new file mode 100644 index 0000000..ce57c07 --- /dev/null +++ b/src/app/components/ApplyNow/ApplyNow.tsx @@ -0,0 +1,58 @@ +import { RefObject } from 'react'; +import './ApplyNow.scss'; +import ApplyNowSVG from './ApplyNowSVG'; +// Credit to: https://css-tricks.com/how-to-get-handwriting-animation-with-irregular-svg-strokes/ + +type ApplyNowTypes = { + applySVGRef: RefObject; +}; + +export default function ApplyNow({ applySVGRef }: ApplyNowTypes) { + return ( + <> + {/* prettier-ignore */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/components/ApplyNow/ApplyNowSVG.tsx b/src/app/components/ApplyNow/ApplyNowSVG.tsx new file mode 100644 index 0000000..d5ca4d2 --- /dev/null +++ b/src/app/components/ApplyNow/ApplyNowSVG.tsx @@ -0,0 +1,9 @@ +export default function ApplyNowSVG() { + return ( + + ); +} diff --git a/src/app/components/FutureUpgrader/FutureUpgrader.scss b/src/app/components/FutureUpgrader/FutureUpgrader.scss new file mode 100644 index 0000000..e2b15c8 --- /dev/null +++ b/src/app/components/FutureUpgrader/FutureUpgrader.scss @@ -0,0 +1,20 @@ +.future-upgrader-svg { + align-self: flex-end; + margin-inline: auto; + opacity: 0; + font-size: $p-font; + display: flex; + + .mask { + fill: none; + stroke: #fff; + stroke-dasharray: 0; + stroke-dashoffset: 0; + } + + @keyframes dash { + to { + stroke-dashoffset: 0; + } + } +} diff --git a/src/app/components/FutureUpgrader/FutureUpgrader.tsx b/src/app/components/FutureUpgrader/FutureUpgrader.tsx new file mode 100644 index 0000000..2492777 --- /dev/null +++ b/src/app/components/FutureUpgrader/FutureUpgrader.tsx @@ -0,0 +1,76 @@ +import { RefObject } from 'react'; +import './FutureUpgrader.scss'; +import FutureUpgraderSVG from './FutureUpgraderSVG'; +// Credit to: https://css-tricks.com/how-to-get-handwriting-animation-with-irregular-svg-strokes/ + +type FutureUpgraderTypes = { + futureSVGRef: RefObject; +}; + +export default function FutureUpgrader({ futureSVGRef }: FutureUpgraderTypes) { + return ( + <> + {/* prettier-ignore */} + + + + {/* Future */} + + + + + + + + + + + + + + + + + + + + + + {/* Upgrader */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/components/FutureUpgrader/FutureUpgraderSVG.tsx b/src/app/components/FutureUpgrader/FutureUpgraderSVG.tsx new file mode 100644 index 0000000..6747d91 --- /dev/null +++ b/src/app/components/FutureUpgrader/FutureUpgraderSVG.tsx @@ -0,0 +1,9 @@ +export default function FutureUpgraderSVG() { + return ( + + ); +} diff --git a/src/app/components/Navbar/Navbar.scss b/src/app/components/Navbar/Navbar.scss index 7a9445a..862f068 100644 --- a/src/app/components/Navbar/Navbar.scss +++ b/src/app/components/Navbar/Navbar.scss @@ -11,7 +11,6 @@ nav { height: 100dvh; display: flex; flex-direction: column; - gap: 2rem; position: fixed; z-index: 1; @@ -51,10 +50,15 @@ nav { } } + .event-logistics { + margin-bottom: 1rem; + } + ul { list-style: none; font-family: $p-font; padding-left: 0; + margin-block: 2rem; } } @@ -76,29 +80,32 @@ nav { } .nav-link-list { - display: flex; - flex-direction: row; - padding-bottom: 14px; + display: grid; + gap: 14px; max-width: 310px; - } - - .nav-link { - position: relative; - - &:before { - content: ''; - position: absolute; - bottom: -2px; - left: 0; - width: 0; - height: 2px; - background-color: #212121; - transition: width 0.25s; - } - &:is(:hover, :focus-visible):before { - width: 100%; - left: 0; + .nav-link { + display: flex; + + .nav-link-text { + position: relative; + + &:before { + content: ''; + position: absolute; + bottom: -2px; + left: 0; + width: 0; + height: 2px; + background-color: #212121; + transition: width 0.25s; + } + + &:is(:hover, :focus-visible):before { + width: 100%; + left: 0; + } + } } } @@ -158,20 +165,16 @@ nav { } .add-me-to-cart { + border: 2px solid $primary-black; border-radius: 5px; display: flex; flex-direction: row; justify-content: center; align-items: center; - } - .add-me-to-cart:hover { - background-color: #fcfcfc; - color: #212121; - } - - .add-me-to-cart:hover .svg-cart path { - stroke: #212121; - stroke-width: 2.25px; + &:hover { + background-color: #fcfcfc; + color: #212121; + } } } diff --git a/src/app/components/Navbar/Navbar.tsx b/src/app/components/Navbar/Navbar.tsx index 6374716..63bc3c6 100644 --- a/src/app/components/Navbar/Navbar.tsx +++ b/src/app/components/Navbar/Navbar.tsx @@ -6,6 +6,7 @@ import Hamburger from './Hamburger/Hamburger'; import FocusTrap from 'focus-trap-react'; import { useHandleHamburger } from './hooks/useHandleHamburger'; import { useHandleScroll } from './hooks/useHandleScroll'; +import Signature from '../Signature/Signature'; type NavbarProps = { pageRefs: { @@ -37,6 +38,7 @@ export default function Navbar({ pageRefs }: NavbarProps) { const asterisksRef1 = useRef(null); const asterisksRef2 = useRef(null); const [currPage, setCurrPage] = useState('Home'); + const [hoveringCart, setHoveringCart] = useState(false); const { toggleHamburger, isHamburgerOpen, hamburgerInnerRef } = useHandleHamburger({ navContainerRef }); @@ -58,15 +60,17 @@ export default function Navbar({ pageRefs }: NavbarProps) { >

UP-GRADE 2024

+ +

UCSD Design Co
San Diego, CA

JUNE 1ST TO AUGUST 13TH

- -

+

+ {'*'.repeat(NUM_ASTERISKS)} - -
    + +
      {NAV_LINKS.map((link, index) => (
    • -
      +

      ))}

    -

    + {'*'.repeat(NUM_ASTERISKS)} -

    +
    -
    -

    Apply Now

    -
    - +

    ADD ME TO CART

    +

    diff --git a/src/app/components/Signature/Signature.scss b/src/app/components/Signature/Signature.scss new file mode 100644 index 0000000..27af408 --- /dev/null +++ b/src/app/components/Signature/Signature.scss @@ -0,0 +1,28 @@ +.signature { + position: relative; + margin-bottom: 40px; + font-size: $p-font; + height: 60px; + display: flex; + + .signature-container { + display: flex; + align-items: flex-end; + position: absolute; + bottom: 15px; + width: 100%; + gap: 12px; + + span { + font-family: $p-font; + line-height: 1; + } + + .line { + width: 100%; + height: 1px; + background-color: #bababa; + z-index: -1; + } + } +} diff --git a/src/app/components/Signature/Signature.tsx b/src/app/components/Signature/Signature.tsx new file mode 100644 index 0000000..f80f866 --- /dev/null +++ b/src/app/components/Signature/Signature.tsx @@ -0,0 +1,69 @@ +'use client'; +import { RefObject, useEffect, useRef } from 'react'; +import ApplyNow from '../ApplyNow/ApplyNow'; +import FutureUpgrader from '../FutureUpgrader/FutureUpgrader'; +import './Signature.scss'; +import { handleHandwritingAnimation } from './util/handleHandwritingAnimation'; + +type SignatureProps = { + hoveringCart: boolean; + navContainerRef: RefObject; +}; + +const APPLY_CONFIG = { + maskPrefix: 'apply', + animationDuration: 0.06, + maskList: ['A', 'p-1', 'p-2', 'l', 'y', 'n', 'o', 'w', 'exclamation'], + heightOffset: '0%' +}; + +const FUTURE_CONFIG = { + maskPrefix: 'future', + animationDuration: 0.1, + // prettier-ignore + maskList: ['F', 'u1', 't', 'u2', 'r1', 'e1', 'U', 'p', 'dash', 'g', 'r2', 'a', 'd', 'e2', 'r3'], + heightOffset: '15%' +}; + +export default function Signature({ + hoveringCart, + navContainerRef +}: SignatureProps) { + const xElementRef = useRef(null); + const applySVGRef = useRef(null); + const futureSVGRef = useRef(null); + + useEffect(() => { + if (hoveringCart) { + console.log('APPLY NOW ANIMATION!'); + handleHandwritingAnimation({ + svgRef: applySVGRef, + navContainerRef, + xElementRef, + config: APPLY_CONFIG + }); + } else { + console.log('FUTURE UPGRADER ANIMATION!'); + handleHandwritingAnimation({ + svgRef: futureSVGRef, + navContainerRef, + xElementRef, + config: FUTURE_CONFIG + }); + } + }, [hoveringCart, navContainerRef]); + + return ( +

    +
    + X +
    +
    + {hoveringCart ? ( + + ) : ( + + )} +
    + ); +} diff --git a/src/app/components/Signature/util/handleHandwritingAnimation.ts b/src/app/components/Signature/util/handleHandwritingAnimation.ts new file mode 100644 index 0000000..578694d --- /dev/null +++ b/src/app/components/Signature/util/handleHandwritingAnimation.ts @@ -0,0 +1,52 @@ +import { RefObject } from 'react'; + +type HandleHandwritingAnimationProps = { + svgRef: RefObject; + navContainerRef: RefObject; + xElementRef: RefObject; + config: { + maskPrefix: string; + animationDuration: number; + maskList: string[]; + heightOffset: string; + }; +}; + +export const handleHandwritingAnimation = ({ + svgRef, + navContainerRef, + xElementRef, + config +}: HandleHandwritingAnimationProps) => { + const { animationDuration, maskList, maskPrefix, heightOffset } = config; + const applyContainer = svgRef.current; + const navContainer = navContainerRef.current; + const xElement = xElementRef.current; + if (!applyContainer || !navContainer || !xElement) return; + const navFullWidth = parseFloat(window.getComputedStyle(navContainer).width); + const navPadding = parseFloat( + window.getComputedStyle(navContainer).paddingLeft + ); + const navWidth = navFullWidth - navPadding * 2; + const xWidth = parseFloat(window.getComputedStyle(xElement).width); + const maxWidth = Math.min(navWidth - xWidth - 20, 230); + + svgRef.current.style.width = `${maxWidth}px`; + svgRef.current.style.transform = `translateX(${xWidth}px) translateY(${heightOffset})`; + applyContainer.style.opacity = '1'; + let currentDelay = 0; + for (let maskID of maskList) { + const maskElement = document.querySelector(`#${maskPrefix}-mask-${maskID}`); + console.log(maskElement); + if (!maskElement) return; + const maskChildren = maskElement.children; + for (var i = 0; i < maskChildren.length; i++) { + const pathChild = maskChildren[i] as SVGPathElement; + const length = pathChild.getTotalLength(); + pathChild.style.strokeDasharray = length.toString(); + pathChild.style.strokeDashoffset = length.toString(); + pathChild.style.animation = `dash ${animationDuration}s ${i * animationDuration + currentDelay}s linear forwards`; + } + currentDelay += maskChildren.length * animationDuration; + } +};