diff options
| author | littleblack111 <littleblack11111@gmail.com> | 2024-10-13 00:38:37 +0800 |
|---|---|---|
| committer | littleblack111 <littleblack11111@gmail.com> | 2024-10-13 00:38:37 +0800 |
| commit | 433e08f2f798b4c04d57cd2d61b16a58aa5196be (patch) | |
| tree | 699df47962e2fc921c9ddde8a034f41af92b3c63 /components | |
| parent | 308eb96365a7cee0ebc8118673c3b6c85497ce3e (diff) | |
core: added home page
Diffstat (limited to 'components')
| -rw-r--r-- | components/blob.tsx | 175 | ||||
| -rw-r--r-- | components/hacktype.tsx | 55 | ||||
| -rw-r--r-- | components/header.css | 123 | ||||
| -rw-r--r-- | components/header.tsx | 39 | ||||
| -rw-r--r-- | components/theme-provider.tsx | 9 |
5 files changed, 401 insertions, 0 deletions
diff --git a/components/blob.tsx b/components/blob.tsx new file mode 100644 index 0000000..de80ad8 --- /dev/null +++ b/components/blob.tsx @@ -0,0 +1,175 @@ +"use client" +import React, { useState, useEffect, useRef } from 'react'; +import isMobile from '@/app/lib/isMobile'; + +// components/RandomBlob.tsx +function getRandomInt(min: number, max: number): number { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function getRandomColor(): string { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +} + +function generateGradient(): string { + let gradient = 'conic-gradient('; + let firstTimeInter = true; + const colorCount = Math.floor(Math.random() * 5) + 2; + for (let i = 0; i < colorCount; i++) { + if (firstTimeInter) { + gradient += getRandomColor(); + firstTimeInter = false; + } else { + gradient += ', ' + getRandomColor(); + } + } + gradient += ')'; + return gradient; +} + +const RandomBlob: React.FC = () => { + const blobRef = useRef<HTMLDivElement>(null); + const [isResetting, setIsResetting] = useState(false); + const [isFirstTime, setIsFirstTime] = useState(true); + const [mainInterval, setMainInterval] = useState<NodeJS.Timeout | null>(null); + + const randomPosition = (element: HTMLElement) => { + const x = getRandomInt(0, window.innerWidth); + const y = getRandomInt(0, window.innerHeight); + element.style.position = 'absolute'; + element.style.left = `${x}px`; + element.style.top = `${y}px`; + }; + + function setRandomBlob() { + if (blobRef.current) { + blobRef.current.style.width = `${getRandomInt(50, 100)}%`; + blobRef.current.style.height = `${getRandomInt(50, 100)}%`; + blobRef.current.style.transform = `rotate(${getRandomInt(0, 360)}deg)`; + } + }; + + function rrestblob() { + setRandomBlob(); + blobRef.current!.style.background = generateGradient(); + randomPosition(blobRef.current!); + const fadeInAnimation = blobRef.current!.animate([ + { opacity: 0 }, + { opacity: 0.4 } + ], { + duration: 3000, + iterations: 1, + fill: 'forwards' + }); + + fadeInAnimation?.addEventListener('finish', () => { + setIsResetting(false); + }); + } + + function resetBlob() { + if (blobRef.current) { + if (isResetting) { + return; + } + if (!isFirstTime) { + blobRef.current?.animate([ + { opacity: 0.4 }, + { opacity: 0, transform: 'scale(0.8)' } + ], { + duration: 3000, + iterations: 1, + fill: 'forwards' + }).addEventListener('finish', rrestblob); + + setIsResetting(true); + } else { + rrestblob(); + setIsFirstTime(false); + } + } + }; + + function main() { + resetBlob(); + const interval = setTimeout(main, getRandomInt(7500, 30000)); + setMainInterval(interval); + }; + + useEffect(() => { + main(); + return () => { + if (mainInterval) { + clearTimeout(mainInterval); + } + }; + }, []); + + return ( + <div + id="blob" + className="blob" + ref={blobRef} + onClick={() => { + if (isResetting) { + return; + } + setIsResetting(true); + if (mainInterval) { + clearTimeout(mainInterval); + } + main(); + }} + /> + ); +}; + +function mouseBlob() { + // Implement the mouseBlob function + return <div>Mouse Blob</div>; +} + +export default function Blob({ blobtype: initialBlobType }: { blobtype?: string }) { + const [blobtype, setBlobtype] = useState<string | undefined>(initialBlobType); + const mainIntervalRef = useRef<NodeJS.Timeout | null>(null); + + useEffect(() => { + function main() { + if (mainIntervalRef.current) { + clearTimeout(mainIntervalRef.current); + } + mainIntervalRef.current = setTimeout(main, getRandomInt(7500, 30000)); + } + + main(); + + return () => { + if (mainIntervalRef.current) { + clearTimeout(mainIntervalRef.current); + } + }; + }, []); + + useEffect(() => { + if (blobtype === undefined) { + isMobile().then((isMobile) => { + setBlobtype(isMobile ? 'random' : 'mouse'); + }); + } else if (blobtype !== 'random' && blobtype !== 'mouse') { + throw new Error(`Invalid blob type ${blobtype}`); + } + }, [blobtype]); + + if (blobtype === undefined) { + return null; // or a loading spinner + } + + return blobtype === 'random' ? <RandomBlob /> : mouseBlob(); +}
\ No newline at end of file diff --git a/components/hacktype.tsx b/components/hacktype.tsx new file mode 100644 index 0000000..59c91b3 --- /dev/null +++ b/components/hacktype.tsx @@ -0,0 +1,55 @@ +"use client" + +import { useEffect, useRef } from "react"; + +const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +function hackType(element: HTMLElement) { + let iteration = 0; + const interval = setInterval(() => { + element.innerText = element.innerText + .split("") + .map((letter, index) => { + if (index < iteration) { + return element.getAttribute('data-value')![index]; + } + return letters[Math.floor(Math.random() * 26)]; + }) + .join(""); + + if (iteration >= element.getAttribute('data-value')!.length) { + clearInterval(interval); + } + + iteration += 1 / 5; + }, 20); +} + +export default function HackType({ text }: { text: string }) { + const elementRef = useRef<HTMLDivElement>(null); + + useEffect(() => { + if (elementRef.current) { + elementRef.current.setAttribute('data-value', text); + hackType(elementRef.current); + + const interval = setInterval(() => { + hackType(elementRef.current!); + }, 10000); + + document.addEventListener("visibilitychange", () => { + if (!document.hidden && elementRef.current) { + hackType(elementRef.current); + } + }); + + return () => clearInterval(interval); + } + }, [text]); + + return ( + <div ref={elementRef} className="hacktype"> + {text} + </div> + ); +}
\ No newline at end of file diff --git a/components/header.css b/components/header.css new file mode 100644 index 0000000..057ad90 --- /dev/null +++ b/components/header.css @@ -0,0 +1,123 @@ +header { + overflow: hidden; + position: fixed; + top: 2rem; + left: 0; + /* width: 75%; */ + /* width: 100%; */ + /* max-width: fit-content; */ + /* margin-left: 12.5%; */ + height: var(--topBarHeight); + /* justify-content: fit-content; */ + align-items: center; + text-align: center; + /* background: color(0, 0, 0, .1); */ + z-index: 1000; + /* background: rgba(0, 0, 0, 0.2); */ + background-color: #000; + border-radius: 32px; + transition: all 0.1s; + -webkit-user-select: none; /* Safari */ + -ms-user-select: none; /* IE 10 and IE 11 */ + user-select: none; /* Standard syntax */ +} + +.navBar a { + font-family: var(--font-jetbrains); + position: relative; + height: 100%; + text-align: center; + font-size: 2rem; + font-weight: 500; + text-transform: capitalize; + text-align: center; + text-decoration: none; + margin-left: 10mm; + margin-right: 10mm; + padding-top: 5mm; + /* padding-bottom: 3mm; */ + border-radius: 32px; + color: #F4E8C1; + transition: all .3s; + -webkit-user-select: none; /* Safari */ + -ms-user-select: none; /* IE 10 and IE 11 */ + user-select: none; /* Standard syntax */ +} + +.navBar .hleft a {float: left;} +.navBar .hright a {float: right;} + +.navBar a::after { + content: ''; + position: fixed; + left: 0; + bottom: 0; + width: 100%; + height: 1mm; + background: rgba(128, 128, 128, 0.2); + border-radius: 1mm; + transform: scaleX(0); + -webkit-transition: all 0.3s; + transition: all 0.3s; +} + +.navBar a:hover::after { + transform: scaleX(1.1); +} + +.navBar a:hover { + /* background: rgba(255, 255, 255, 0.2); */ + /* outline: ridge rgba(128, 128, 128, .6); */ + /* -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); */ + -webkit-transition: all 1s; + transition: all 1s +} + +.navBar a.active::after { + content: ''; + position: absolute; + left: 0; + bottom: 0; + width: 100%; + height: 1mm; + background: rgba(128, 128, 128, .9); + border-radius: 1mm; + transform: scaleX(1); +} + +.navBar:active, +.navBar:hover, +header:hover, +#navBarHeader:hover +/* #navBarHeader:active */ +{ + transform: scale(1.005); + /* -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); */ + transition: all 0.5s +} + +header:active, +#navBarHeader:active, +.navBar:active { + transform: scale(1.05); + /* transition: transform 700ms; */ +} + +.navBar, +header, +#navBarHeader { + /* -webkit-backdrop-filter: blur(2.5px); + backdrop-filter: blur(2.5px); */ + transition: all 0.5s +} + +header.minimizedIsland { + opacity: 0; + scale: 0; + -webkit-animation-name: minimizeIsland; + animation-name: minimizeIsland; + animation-fill-mode: forwards; + transition: all 0.3s +}
\ No newline at end of file diff --git a/components/header.tsx b/components/header.tsx new file mode 100644 index 0000000..5b850e2 --- /dev/null +++ b/components/header.tsx @@ -0,0 +1,39 @@ +"use client" + +import Link from "next/link" +import { usePathname } from "next/navigation" +import { cn } from "@/lib/utils" +import { useEffect, useState } from 'react'; +import "./header.css" + + +export default function Header() { + const pathname = usePathname() + const [isMinimized, setIsMinimized] = useState(false); + + useEffect(() => { + const handleScroll = () => { + setIsMinimized(window.scrollY > 100); + }; + + window.addEventListener('scroll', handleScroll); + + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + + return ( + <header id="navBarHeader" className={isMinimized ? "minimizedIsland" : ""}> + <nav className="navBar"> + <div className="hleft"> + <Link id="btnHome" className={pathname === "/" ? "active": ""} style={{ textDecoration: 'unset' }} href="/">Home</Link> + </div> + <div className="hright"> + <Link id="btnAbout" className={pathname === "/about" ? "active": ""} style={{ textDecoration: 'unset' }} href="/about">About</Link> + <Link id="btnGhRepo" className={pathname === "/ghrepos" ? "active": ""} style={{ textDecoration: 'unset' }} href="/ghrepos">Github Repos</Link> + </div> + </nav> + </header> + ) +}
\ No newline at end of file diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx new file mode 100644 index 0000000..6d17554 --- /dev/null +++ b/components/theme-provider.tsx @@ -0,0 +1,9 @@ +"use client" + +import * as React from "react" +import { ThemeProvider as NextThemesProvider } from "next-themes" +import { type ThemeProviderProps } from "next-themes/dist/types" + +export function ThemeProvider({ children, ...props }: ThemeProviderProps) { + return <NextThemesProvider {...props}>{children}</NextThemesProvider> +} |
