mirror of
				https://github.com/r-freeman/portfolio.git
				synced 2025-11-03 23:31:11 +00:00 
			
		
		
		
	drop avatar from home page
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				Build And Publish / BuildAndPublish (push) Successful in 2m52s
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	Build And Publish / BuildAndPublish (push) Successful in 2m52s
				
			This commit is contained in:
		
							parent
							
								
									80dee58abe
								
							
						
					
					
						commit
						21af93b0b9
					
				
							
								
								
									
										60
									
								
								app/page.tsx
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								app/page.tsx
									
									
									
									
									
								
							@ -2,13 +2,11 @@ import React from 'react'
 | 
				
			|||||||
import {Card} from '@/components/ui/Card'
 | 
					import {Card} from '@/components/ui/Card'
 | 
				
			||||||
import {Views} from '@/components/ui/Views'
 | 
					import {Views} from '@/components/ui/Views'
 | 
				
			||||||
import {Resume} from '@/components/ui/Resume'
 | 
					import {Resume} from '@/components/ui/Resume'
 | 
				
			||||||
import {SocialLink} from '@/components/ui/SocialLink'
 | 
					 | 
				
			||||||
import {Container} from '@/components/common/Container'
 | 
					 | 
				
			||||||
import {GitHubIcon, LinkedInIcon} from '@/components/icons/SocialIcons'
 | 
					 | 
				
			||||||
import {getAllArticles} from '@/lib/getAllArticles'
 | 
					import {getAllArticles} from '@/lib/getAllArticles'
 | 
				
			||||||
import {formatDate} from '@/lib/formatDate'
 | 
					import {formatDate} from '@/lib/formatDate'
 | 
				
			||||||
import type {Article} from '@/types'
 | 
					import type {Article} from '@/types'
 | 
				
			||||||
import {metadata as _metadata} from '@/lib/generateMetadata'
 | 
					import {metadata as _metadata} from '@/lib/generateMetadata'
 | 
				
			||||||
 | 
					import {SimpleLayout} from "@/components/layouts/SimpleLayout";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const meta = {
 | 
					const meta = {
 | 
				
			||||||
    title: 'Ryan Freeman - Full-stack software engineer based in Dublin, Ireland.',
 | 
					    title: 'Ryan Freeman - Full-stack software engineer based in Dublin, Ireland.',
 | 
				
			||||||
@ -66,47 +64,25 @@ export default async function Home() {
 | 
				
			|||||||
        .map(component => component)
 | 
					        .map(component => component)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <SimpleLayout heading={meta.heading}
 | 
				
			||||||
            <Container className="mt-9">
 | 
					                      description={meta.description}
 | 
				
			||||||
                <div className="max-w-2xl">
 | 
					                      gradient="bg-gradient-to-r from-pink-500 to-violet-500">
 | 
				
			||||||
                    <h1 className="text-4xl font-bold tracking-tight text-zinc-800 sm:text-5xl bg-clip-text dark:text-transparent bg-gradient-to-r from-pink-500 via-red-500 to-yellow-500">
 | 
					            <div className="mx-auto grid max-w-xl grid-cols-1 gap-y-20 lg:max-w-none lg:grid-cols-2">
 | 
				
			||||||
                        {meta.heading}
 | 
					                <div className="flex flex-col gap-16 mt-6">
 | 
				
			||||||
                    </h1>
 | 
					                    {articles.map(({slug, title, description, date}) => (
 | 
				
			||||||
                    <p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
 | 
					                        <Article
 | 
				
			||||||
                        {meta.description}
 | 
					                            key={slug}
 | 
				
			||||||
                    </p>
 | 
					                            title={title}
 | 
				
			||||||
                    <div className="mt-6 flex gap-6">
 | 
					                            description={description}
 | 
				
			||||||
                        <SocialLink
 | 
					                            slug={slug}
 | 
				
			||||||
                            href="https://github.com/r-freeman"
 | 
					                            date={date}
 | 
				
			||||||
                            ariaLabel="Follow on GitHub"
 | 
					 | 
				
			||||||
                            icon={GitHubIcon}
 | 
					 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
                        <SocialLink
 | 
					                    ))}
 | 
				
			||||||
                            href="https://linkedin.com/in/r-freeman/"
 | 
					 | 
				
			||||||
                            ariaLabel="Follow on LinkedIn"
 | 
					 | 
				
			||||||
                            icon={LinkedInIcon}
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </Container>
 | 
					                <div className="space-y-10 lg:pl-16 xl:pl-24">
 | 
				
			||||||
            <Container className="mt-24 md:mt-28">
 | 
					                    <Resume/>
 | 
				
			||||||
                <div className="mx-auto grid max-w-xl grid-cols-1 gap-y-20 lg:max-w-none lg:grid-cols-2">
 | 
					 | 
				
			||||||
                    <div className="flex flex-col gap-16 mt-6">
 | 
					 | 
				
			||||||
                        {articles.map(({slug, title, description, date}) => (
 | 
					 | 
				
			||||||
                            <Article
 | 
					 | 
				
			||||||
                                key={slug}
 | 
					 | 
				
			||||||
                                title={title}
 | 
					 | 
				
			||||||
                                description={description}
 | 
					 | 
				
			||||||
                                slug={slug}
 | 
					 | 
				
			||||||
                                date={date}
 | 
					 | 
				
			||||||
                            />
 | 
					 | 
				
			||||||
                        ))}
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                    <div className="space-y-10 lg:pl-16 xl:pl-24">
 | 
					 | 
				
			||||||
                        <Resume/>
 | 
					 | 
				
			||||||
                    </div>
 | 
					 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </Container>
 | 
					            </div>
 | 
				
			||||||
        </>
 | 
					        </SimpleLayout>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -8,7 +8,7 @@ export function Providers({children}: {
 | 
				
			|||||||
}) {
 | 
					}) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <ThemeProvider attribute="class" disableTransitionOnChange>
 | 
					        <ThemeProvider attribute="class" disableTransitionOnChange defaultTheme="dark">
 | 
				
			||||||
            {children}
 | 
					            {children}
 | 
				
			||||||
        </ThemeProvider>
 | 
					        </ThemeProvider>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
				
			|||||||
@ -1,192 +1,19 @@
 | 
				
			|||||||
'use client'
 | 
					'use client'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {useEffect, useRef} from 'react'
 | 
					 | 
				
			||||||
import {usePathname} from 'next/navigation'
 | 
					 | 
				
			||||||
import {Container} from './Container'
 | 
					import {Container} from './Container'
 | 
				
			||||||
import {ThemeButton} from '@/components/ui/ThemeButton'
 | 
					import {ThemeButton} from '@/components/ui/ThemeButton'
 | 
				
			||||||
import {DesktopNavigation, MobileNavigation} from '@/components/ui/Navigation'
 | 
					import {DesktopNavigation, MobileNavigation} from '@/components/ui/Navigation'
 | 
				
			||||||
import {Avatar, AvatarContainer} from '@/components/ui/Avatar'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function clamp(num: number, a: number, b: number) {
 | 
					 | 
				
			||||||
    let min = Math.min(a, b)
 | 
					 | 
				
			||||||
    let max = Math.max(a, b)
 | 
					 | 
				
			||||||
    return Math.min(Math.max(num, min), max)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Header() {
 | 
					export function Header() {
 | 
				
			||||||
    const pathname = usePathname()
 | 
					 | 
				
			||||||
    const isHomePage = pathname === '/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headerRef = useRef<HTMLDivElement>(null)
 | 
					 | 
				
			||||||
    const avatarRef = useRef<HTMLImageElement>(null)
 | 
					 | 
				
			||||||
    const isInitial = useRef(true)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const headerPosition: Object = {
 | 
					 | 
				
			||||||
        position: 'var(--header-position)'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const headerInnerPosition: Object = {
 | 
					 | 
				
			||||||
        position: 'var(--header-inner-position)'
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    useEffect(() => {
 | 
					 | 
				
			||||||
        let downDelay = avatarRef.current?.offsetTop ?? 0
 | 
					 | 
				
			||||||
        let upDelay = 64
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function setProperty(property: string, value: string) {
 | 
					 | 
				
			||||||
            document.documentElement.style.setProperty(property, value.toString())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function removeProperty(property: string) {
 | 
					 | 
				
			||||||
            document.documentElement.style.removeProperty(property)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function updateHeaderStyles() {
 | 
					 | 
				
			||||||
            const headerBoundingRect = headerRef.current?.getBoundingClientRect()
 | 
					 | 
				
			||||||
            let top = headerBoundingRect?.top!
 | 
					 | 
				
			||||||
            let height = headerBoundingRect?.height!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let scrollY = clamp(
 | 
					 | 
				
			||||||
                window.scrollY,
 | 
					 | 
				
			||||||
                0,
 | 
					 | 
				
			||||||
                document.body.scrollHeight - window.innerHeight
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (isInitial.current) {
 | 
					 | 
				
			||||||
                setProperty('--header-position', 'sticky')
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            setProperty('--content-offset', `${downDelay}px`)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (isInitial.current || scrollY < downDelay) {
 | 
					 | 
				
			||||||
                setProperty('--header-height', `${downDelay + height}px`)
 | 
					 | 
				
			||||||
                setProperty('--header-mb', `${-downDelay}px`)
 | 
					 | 
				
			||||||
            } else if (top + height < -upDelay) {
 | 
					 | 
				
			||||||
                let offset = Math.max(height, scrollY - upDelay)
 | 
					 | 
				
			||||||
                setProperty('--header-height', `${offset}px`)
 | 
					 | 
				
			||||||
                setProperty('--header-mb', `${height - offset}px`)
 | 
					 | 
				
			||||||
            } else if (top === 0) {
 | 
					 | 
				
			||||||
                setProperty('--header-height', `${scrollY + height}px`)
 | 
					 | 
				
			||||||
                setProperty('--header-mb', `${-scrollY}px`)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (top === 0 && scrollY > 0 && scrollY >= downDelay) {
 | 
					 | 
				
			||||||
                setProperty('--header-inner-position', 'fixed')
 | 
					 | 
				
			||||||
                removeProperty('--header-top')
 | 
					 | 
				
			||||||
                removeProperty('--avatar-top')
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                removeProperty('--header-inner-position')
 | 
					 | 
				
			||||||
                setProperty('--header-top', '0px')
 | 
					 | 
				
			||||||
                setProperty('--avatar-top', '0px')
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function updateAvatarStyles() {
 | 
					 | 
				
			||||||
            if (!isHomePage) {
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let fromScale = 1
 | 
					 | 
				
			||||||
            let toScale = 36 / 64
 | 
					 | 
				
			||||||
            let fromX = 0
 | 
					 | 
				
			||||||
            let toX = 2 / 16
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let scrollY = downDelay - window.scrollY
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let scale = (scrollY * (fromScale - toScale)) / downDelay + toScale
 | 
					 | 
				
			||||||
            scale = clamp(scale, fromScale, toScale)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let x = (scrollY * (fromX - toX)) / downDelay + toX
 | 
					 | 
				
			||||||
            x = clamp(x, fromX, toX)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            setProperty(
 | 
					 | 
				
			||||||
                '--avatar-image-transform',
 | 
					 | 
				
			||||||
                `translate3d(${x}rem, 0, 0) scale(${scale})`
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let borderScale = 1 / (toScale / scale)
 | 
					 | 
				
			||||||
            let borderX = (-toX + x) * borderScale
 | 
					 | 
				
			||||||
            let borderTransform = `translate3d(${borderX}rem, 0, 0) scale(${borderScale})`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            setProperty('--avatar-border-transform', borderTransform)
 | 
					 | 
				
			||||||
            setProperty('--avatar-border-opacity', (scale === toScale ? 1 : 0).toString())
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function updateStyles() {
 | 
					 | 
				
			||||||
            updateHeaderStyles()
 | 
					 | 
				
			||||||
            updateAvatarStyles()
 | 
					 | 
				
			||||||
            isInitial.current = false
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        updateStyles()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        const opts: AddEventListenerOptions & EventListenerOptions = {passive: true}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (isHomePage) {
 | 
					 | 
				
			||||||
            window.addEventListener('scroll', updateStyles, opts)
 | 
					 | 
				
			||||||
            window.addEventListener('resize', updateStyles)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return () => {
 | 
					 | 
				
			||||||
            window.removeEventListener('scroll', updateStyles, opts)
 | 
					 | 
				
			||||||
            window.removeEventListener('resize', updateStyles)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }, [isHomePage])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
            <header
 | 
					            <header
 | 
				
			||||||
                className="pointer-events-none relative z-50 flex flex-col"
 | 
					                className="pointer-events-none relative z-50 flex flex-col">
 | 
				
			||||||
                style={{
 | 
					 | 
				
			||||||
                    height: 'var(--header-height)',
 | 
					 | 
				
			||||||
                    marginBottom: 'var(--header-mb)',
 | 
					 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
                {isHomePage && (
 | 
					 | 
				
			||||||
                    <>
 | 
					 | 
				
			||||||
                        <div
 | 
					 | 
				
			||||||
                            ref={avatarRef}
 | 
					 | 
				
			||||||
                            className="order-last mt-[calc(theme(spacing.16)-theme(spacing.3))]"
 | 
					 | 
				
			||||||
                        />
 | 
					 | 
				
			||||||
                        <Container
 | 
					 | 
				
			||||||
                            className="top-0 order-last -mb-3 pt-3"
 | 
					 | 
				
			||||||
                            style={{position: 'var(--header-position)'}}
 | 
					 | 
				
			||||||
                        >
 | 
					 | 
				
			||||||
                            <div className="top-[var(--avatar-top,theme(spacing.3))] w-full"
 | 
					 | 
				
			||||||
                                 style={headerInnerPosition}>
 | 
					 | 
				
			||||||
                                <div className="relative">
 | 
					 | 
				
			||||||
                                    <AvatarContainer
 | 
					 | 
				
			||||||
                                        className="absolute left-0 top-3 origin-left transition-opacity"
 | 
					 | 
				
			||||||
                                        style={{
 | 
					 | 
				
			||||||
                                            opacity: 'var(--avatar-border-opacity, 0)',
 | 
					 | 
				
			||||||
                                            transform: 'var(--avatar-border-transform)',
 | 
					 | 
				
			||||||
                                        }}
 | 
					 | 
				
			||||||
                                    />
 | 
					 | 
				
			||||||
                                    <Avatar
 | 
					 | 
				
			||||||
                                        large
 | 
					 | 
				
			||||||
                                        className="block h-16 w-16 origin-left"
 | 
					 | 
				
			||||||
                                        style={{transform: 'var(--avatar-image-transform)'}}
 | 
					 | 
				
			||||||
                                    />
 | 
					 | 
				
			||||||
                                </div>
 | 
					 | 
				
			||||||
                            </div>
 | 
					 | 
				
			||||||
                        </Container>
 | 
					 | 
				
			||||||
                    </>
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
                <div
 | 
					                <div
 | 
				
			||||||
                    ref={headerRef}
 | 
					                    className="top-0 z-10 h-16 pt-6">
 | 
				
			||||||
                    className="top-0 z-10 h-16 pt-6"
 | 
					                    <Container>
 | 
				
			||||||
                    style={headerPosition}
 | 
					 | 
				
			||||||
                >
 | 
					 | 
				
			||||||
                    <Container
 | 
					 | 
				
			||||||
                        className="top-[var(--header-top,theme(spacing.6))] w-full"
 | 
					 | 
				
			||||||
                        style={{position: 'var(--header-inner-position)'}}
 | 
					 | 
				
			||||||
                    >
 | 
					 | 
				
			||||||
                        <div className="relative flex gap-4">
 | 
					                        <div className="relative flex gap-4">
 | 
				
			||||||
                            <div className="flex flex-1">
 | 
					                            <div className="flex flex-1">
 | 
				
			||||||
                                {!isHomePage && (
 | 
					 | 
				
			||||||
                                    <AvatarContainer>
 | 
					 | 
				
			||||||
                                        <Avatar/>
 | 
					 | 
				
			||||||
                                    </AvatarContainer>
 | 
					 | 
				
			||||||
                                )}
 | 
					 | 
				
			||||||
                            </div>
 | 
					                            </div>
 | 
				
			||||||
                            <div className="flex flex-1 justify-end md:justify-center">
 | 
					                            <div className="flex flex-1 justify-end md:justify-center">
 | 
				
			||||||
                                <MobileNavigation className="pointer-events-auto md:hidden"/>
 | 
					                                <MobileNavigation className="pointer-events-auto md:hidden"/>
 | 
				
			||||||
@ -201,7 +28,6 @@ export function Header() {
 | 
				
			|||||||
                    </Container>
 | 
					                    </Container>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </header>
 | 
					            </header>
 | 
				
			||||||
            {isHomePage && <div style={{height: 'var(--content-offset)'}}/>}
 | 
					 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,39 +0,0 @@
 | 
				
			|||||||
import {Props} from '@/types'
 | 
					 | 
				
			||||||
import clsx from 'clsx'
 | 
					 | 
				
			||||||
import Link from 'next/link'
 | 
					 | 
				
			||||||
import Image from 'next/image'
 | 
					 | 
				
			||||||
import me from '/public/images/me.jpg'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function AvatarContainer({className, ...props}: { style?: Object } & Props) {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <div
 | 
					 | 
				
			||||||
            className={clsx(
 | 
					 | 
				
			||||||
                className,
 | 
					 | 
				
			||||||
                'h-10 w-10 rounded-full bg-white/90 p-0.5 shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur dark:bg-zinc-800/90 dark:ring-white/10'
 | 
					 | 
				
			||||||
            )}
 | 
					 | 
				
			||||||
            {...props}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function Avatar({large = false, className, ...props}: { large?: boolean, style?: Object } & Props) {
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <Link
 | 
					 | 
				
			||||||
            href="/"
 | 
					 | 
				
			||||||
            aria-label="Home"
 | 
					 | 
				
			||||||
            className={clsx(className, 'pointer-events-auto')}
 | 
					 | 
				
			||||||
            {...props}
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
            <Image
 | 
					 | 
				
			||||||
                src={me}
 | 
					 | 
				
			||||||
                alt=""
 | 
					 | 
				
			||||||
                sizes={large ? '4rem' : '2.25rem'}
 | 
					 | 
				
			||||||
                className={clsx(
 | 
					 | 
				
			||||||
                    'rounded-full bg-zinc-100 object-cover dark:bg-zinc-800',
 | 
					 | 
				
			||||||
                    large ? 'h-16 w-16' : 'h-9 w-9'
 | 
					 | 
				
			||||||
                )}
 | 
					 | 
				
			||||||
                placeholder="blur"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
        </Link>
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user