From 433e08f2f798b4c04d57cd2d61b16a58aa5196be Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Sun, 13 Oct 2024 00:38:37 +0800 Subject: core: added home page --- components/blob.tsx | 175 ++++++++++++++++++++++++++++++++++++++++++ components/hacktype.tsx | 55 +++++++++++++ components/header.css | 123 +++++++++++++++++++++++++++++ components/header.tsx | 39 ++++++++++ components/theme-provider.tsx | 9 +++ 5 files changed, 401 insertions(+) create mode 100644 components/blob.tsx create mode 100644 components/hacktype.tsx create mode 100644 components/header.css create mode 100644 components/header.tsx create mode 100644 components/theme-provider.tsx (limited to 'components') 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(null); + const [isResetting, setIsResetting] = useState(false); + const [isFirstTime, setIsFirstTime] = useState(true); + const [mainInterval, setMainInterval] = useState(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 ( +
{ + if (isResetting) { + return; + } + setIsResetting(true); + if (mainInterval) { + clearTimeout(mainInterval); + } + main(); + }} + /> + ); +}; + +function mouseBlob() { + // Implement the mouseBlob function + return
Mouse Blob
; +} + +export default function Blob({ blobtype: initialBlobType }: { blobtype?: string }) { + const [blobtype, setBlobtype] = useState(initialBlobType); + const mainIntervalRef = useRef(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' ? : 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(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 ( +
+ {text} +
+ ); +} \ 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 ( + + ) +} \ 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 {children} +} -- cgit v1.2.3