Skip to content

Commit

Permalink
feat(hero): add reverse typing anim and slight refactor for better state
Browse files Browse the repository at this point in the history
  • Loading branch information
lewismorgan committed May 15, 2024
1 parent 9fb53da commit afa4d2a
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 73 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ This is my main site. It's a work in progress.
- [X] Move Git calls to api folder
- [ ] Interactivity
- [X] Hello Internet. types across the screen
- [ ] glowsticks
- [ ] Lightsaber sound plays
- [X] glowsticks
- [X] Profile image turns into a character based on if it is dark or light mode
- [ ] space-lizards
- [ ] Lizard sound plays
- [ ] Profile image turns into a lizard sci-fi head, make something with midjourney or chatgpt
- [X] code
- [X] Hello_Internet reverses direction.
- [ ] Update the readme with a whole section on how I created and built this site
- [ ] Show my resume
- [ ] Contact form
Expand Down
4 changes: 1 addition & 3 deletions src/app/api/git/repos/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,5 @@ import { getRepositories } from '~/server'
export async function GET() {
const data = await getRepositories()

return Response.json({ data })
return Response.json({ ...data })
}

export const revalidate = 60 * 60 // 1 hour
8 changes: 7 additions & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ export default function RootLayout({
}: {
children: React.ReactNode
}) {
// hydration warning suppression is only 1 level deep per react docs, this is to prevent the theme being put in html class errors
// https://github.com/pacocoursey/next-themes?tab=readme-ov-file#with-app
return (
<html lang="en" className={`${source_code_pro.variable} ${exo_2.variable}`}>
<html
lang="en"
className={`${source_code_pro.variable} ${exo_2.variable}`}
suppressHydrationWarning
>
<body className="flex min-h-screen min-w-full flex-col font-sans ">
<ThemeProvider attribute="class" defaultTheme="dark">
<div className="fixed bottom-20 right-2 h-fit w-fit md:right-10 md:top-10">
Expand Down
2 changes: 2 additions & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,5 @@ export default async function HomePage() {
</main>
)
}

export const revalidate = 3600 // revalidate at most every hour
48 changes: 32 additions & 16 deletions src/components/client/typing-animation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,48 @@

import { useEffect, useState } from 'react'

export const TypingAnimation = ({ finalText }: { finalText: string }) => {
const [index, setIndex] = useState(0)
const [period, setPeriod] = useState(false)
export const TypingAnimation = ({
finalText,
reverse = false,
onCompleted,
}: {
finalText: string
reverse: boolean
onCompleted?: (reversed: boolean) => void
}) => {
const [index, setIndex] = useState(reverse ? finalText.length : 0)

useEffect(() => {
const interval = setInterval(() => {
if (index < finalText.length) {
setIndex(index + 1)
} else if (index === finalText.length) {
// do it this way so the period is inserted consistently with the rest of the text
setPeriod(true)
if (!reverse) {
if (index < finalText.length) {
setIndex(index + 1)
} else {
clearInterval(interval)
onCompleted?.(reverse)
}
} else {
clearInterval(interval)
if (index === finalText.length) {
setIndex(index - 1)
} else if (index != 0) {
setIndex(index - 1)
} else {
clearInterval(interval)
onCompleted?.(reverse)
}
}
}, 250)

return () => clearInterval(interval)
}, [finalText.length, index])
}, [finalText.length, index, reverse, onCompleted])

const currentText = finalText.slice(0, index)

const shouldFade = index === finalText.length || index === 0

return (
<>
<h1 className="text-left">{currentText}</h1>
{period && (
<span className="inline-flex animate-pulse delay-1000">.</span>
)}
</>
<h1 className={`font-light ${shouldFade ? 'text-muted-foreground' : ''}`}>
{currentText}
</h1>
)
}
49 changes: 46 additions & 3 deletions src/components/hero.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,66 @@
'use client'

import { useEffect, useState } from 'react'

import { useTheme } from 'next-themes'

import { TypingAnimation } from './client/typing-animation'
import { Spiel, type SpielProps } from './spiel'

export type ForceSide = 'light' | 'dark' | 'theme'

export const Hero = ({
profileImage,
name,
}: {
profileImage: string
name: string
}) => {
const { theme } = useTheme()
const [side, setSide] = useState<ForceSide>('theme')
const [replayQueued, setReplayQueued] = useState(false)

useEffect(() => {
// resets the activeMode to default when the theme changes
setSide('theme')
}, [theme])

const handleGlowsticksClick = () => {
switch (theme) {
case 'light':
side !== 'light' ? setSide('light') : setSide('theme')
break
case 'dark':
side !== 'dark' ? setSide('dark') : setSide('theme')
break
default:
setSide('theme')
break
}
}

const avatarUrl =
side === 'light'
? '/grogu.jpg'
: side === 'dark'
? '/anakin.png'
: profileImage

const spielProps = {
imgName: name,
glowstickImgs: { light: '/grogu.jpg', dark: '/anakin.png' },
defaultImg: profileImage,
avatarUrl,
onGlowsticksClick: handleGlowsticksClick,
onCodeClick: () => {
setReplayQueued(!replayQueued)
},
} as SpielProps

const text = 'Hello_Internet'

return (
<div className="flex w-full flex-col p-1 align-middle">
<div className="flex h-16 flex-row place-self-center py-4 font-mono text-4xl tracking-tight hover:cursor-default md:h-20 md:text-5xl lg:h-24 lg:text-7xl">
<TypingAnimation finalText="Hello_Internet" />
<TypingAnimation finalText={text} reverse={replayQueued} />
</div>
<div className="space-y-2 text-center hover:cursor-default">
<div className="text-lg tracking-tight">
Expand Down
68 changes: 20 additions & 48 deletions src/components/spiel.tsx
Original file line number Diff line number Diff line change
@@ -1,56 +1,22 @@
'use client'

import { useEffect, useState } from 'react'

import { useTheme } from 'next-themes'

import { ImageProfile } from './image-profile'

type ImageVariations = {
light: string
dark: string
}

export type SpielProps = {
imgName: string
glowstickImgs: ImageVariations
defaultImg: string
avatarUrl: string
onGlowsticksClick?: () => void
onLizardsClick?: () => void
onCodeClick?: () => void
}

export const Spiel = ({ imgName, glowstickImgs, defaultImg }: SpielProps) => {
const { theme } = useTheme()

const [activeMode, setActiveMode] = useState<'light' | 'dark' | 'default'>(
'default',
)

useEffect(() => {
// resets the activeMode to default when the theme changes
setActiveMode('default')
}, [theme])

const avatarUrl =
activeMode === 'light'
? glowstickImgs.light
: activeMode === 'dark'
? glowstickImgs.dark
: defaultImg

const handleGlowsticksClick = () => {
switch (theme) {
case 'light':
activeMode !== 'light'
? setActiveMode('light')
: setActiveMode('default')
break
case 'dark':
activeMode !== 'dark' ? setActiveMode('dark') : setActiveMode('default')
break
default:
setActiveMode('default')
break
}
}
export const Spiel = ({
imgName,
avatarUrl,
onGlowsticksClick,
onLizardsClick,
onCodeClick,
}: SpielProps) => {
return (
<>
<div>
Expand All @@ -59,16 +25,22 @@ export const Spiel = ({ imgName, glowstickImgs, defaultImg }: SpielProps) => {
<span className="absolute -inset-0 block -skew-y-6 rounded-full bg-gradient-to-r from-transparent to-blue-400 hover:animate-pulse dark:to-red-800"></span>
<span
className="relative font-semibold hover:animate-pulse dark:text-white"
onClick={handleGlowsticksClick}
onClick={onGlowsticksClick}
>
glowsticks
</span>
</span>
,{' '}
<span className="text-nowrap tracking-wide text-green-800 underline decoration-wavy decoration-1 underline-offset-2">
<span
className="text-nowrap tracking-wide text-green-800 underline decoration-wavy decoration-1 underline-offset-2"
onClick={onLizardsClick}
>
space-lizards
</span>
, and lines of <span className="font-mono tracking-wider">code</span>{' '}
, and lines of{' '}
<span className="font-mono tracking-wider" onClick={onCodeClick}>
code
</span>{' '}
lying around
</div>
<div className="mx-auto flex w-full flex-row place-content-center pt-2 align-middle">
Expand Down

0 comments on commit afa4d2a

Please sign in to comment.