mirror of
https://github.com/r-freeman/portfolio.git
synced 2025-02-21 17:44:31 +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 {Views} from '@/components/ui/Views'
|
||||
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 {formatDate} from '@/lib/formatDate'
|
||||
import type {Article} from '@/types'
|
||||
import {metadata as _metadata} from '@/lib/generateMetadata'
|
||||
import {SimpleLayout} from "@/components/layouts/SimpleLayout";
|
||||
|
||||
const meta = {
|
||||
title: 'Ryan Freeman - Full-stack software engineer based in Dublin, Ireland.',
|
||||
@ -66,47 +64,25 @@ export default async function Home() {
|
||||
.map(component => component)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container className="mt-9">
|
||||
<div className="max-w-2xl">
|
||||
<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">
|
||||
{meta.heading}
|
||||
</h1>
|
||||
<p className="mt-6 text-base text-zinc-600 dark:text-zinc-400">
|
||||
{meta.description}
|
||||
</p>
|
||||
<div className="mt-6 flex gap-6">
|
||||
<SocialLink
|
||||
href="https://github.com/r-freeman"
|
||||
ariaLabel="Follow on GitHub"
|
||||
icon={GitHubIcon}
|
||||
<SimpleLayout heading={meta.heading}
|
||||
description={meta.description}
|
||||
gradient="bg-gradient-to-r from-pink-500 to-violet-500">
|
||||
<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}
|
||||
/>
|
||||
<SocialLink
|
||||
href="https://linkedin.com/in/r-freeman/"
|
||||
ariaLabel="Follow on LinkedIn"
|
||||
icon={LinkedInIcon}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
<Container className="mt-24 md:mt-28">
|
||||
<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 className="space-y-10 lg:pl-16 xl:pl-24">
|
||||
<Resume/>
|
||||
</div>
|
||||
</Container>
|
||||
</>
|
||||
</div>
|
||||
</SimpleLayout>
|
||||
)
|
||||
}
|
@ -8,7 +8,7 @@ export function Providers({children}: {
|
||||
}) {
|
||||
|
||||
return (
|
||||
<ThemeProvider attribute="class" disableTransitionOnChange>
|
||||
<ThemeProvider attribute="class" disableTransitionOnChange defaultTheme="dark">
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
)
|
||||
|
@ -1,192 +1,19 @@
|
||||
'use client'
|
||||
|
||||
import {useEffect, useRef} from 'react'
|
||||
import {usePathname} from 'next/navigation'
|
||||
import {Container} from './Container'
|
||||
import {ThemeButton} from '@/components/ui/ThemeButton'
|
||||
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() {
|
||||
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 (
|
||||
<>
|
||||
<header
|
||||
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>
|
||||
</>
|
||||
)}
|
||||
className="pointer-events-none relative z-50 flex flex-col">
|
||||
<div
|
||||
ref={headerRef}
|
||||
className="top-0 z-10 h-16 pt-6"
|
||||
style={headerPosition}
|
||||
>
|
||||
<Container
|
||||
className="top-[var(--header-top,theme(spacing.6))] w-full"
|
||||
style={{position: 'var(--header-inner-position)'}}
|
||||
>
|
||||
className="top-0 z-10 h-16 pt-6">
|
||||
<Container>
|
||||
<div className="relative flex gap-4">
|
||||
<div className="flex flex-1">
|
||||
{!isHomePage && (
|
||||
<AvatarContainer>
|
||||
<Avatar/>
|
||||
</AvatarContainer>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-1 justify-end md:justify-center">
|
||||
<MobileNavigation className="pointer-events-auto md:hidden"/>
|
||||
@ -201,7 +28,6 @@ export function Header() {
|
||||
</Container>
|
||||
</div>
|
||||
</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…
Reference in New Issue
Block a user