feat (v1.0.0): initial refactor and redesign

This commit is contained in:
Aidan 2025-10-09 04:12:05 -04:00
parent 3058aa1ab4
commit fe9b50b30e
134 changed files with 17792 additions and 3670 deletions

View file

@ -0,0 +1,29 @@
"use client"
import { useEffect } from "react";
export default function AnimatedTitle() {
useEffect(() => {
const title = 'aidan.so';
let index = 1;
let forward = true;
const interval = setInterval(() => {
document.title = title.substring(0, index);
if (forward) {
index++;
if (index > title.length) {
forward = false;
index = title.length - 1;
}
} else {
index--;
if (index < 1) {
forward = true;
index = 1;
}
}
}, 500);
return () => clearInterval(interval);
}, []);
return null;
}

View file

@ -1,22 +1,42 @@
import { default as NextLink } from 'next/link'
import { cn } from '@/lib/theme'
import { externalLinkProps } from '@/lib/utils/styles'
interface LinkProps {
href: string
className?: string
target?: string
rel?: string
variant?: 'default' | 'nav' | 'muted'
external?: boolean
children: React.ReactNode
}
export default function Link(props: LinkProps) {
export default function Link({
href,
className,
target,
rel,
variant = 'default',
external,
children
}: LinkProps) {
const isExternal = external || href.startsWith('http')
const variantStyles = {
default: 'text-blue-400 hover:underline',
nav: 'text-gray-300 hover:text-white',
muted: 'text-gray-400 hover:text-gray-300'
}
return (
<NextLink
href={props.href}
className={`text-blue-400 hover:underline ${props.className}`}
target={props.target}
rel={props.rel}
href={href}
className={cn(variantStyles[variant], className)}
target={target || (isExternal ? externalLinkProps.target : undefined)}
rel={rel || (isExternal ? externalLinkProps.rel : undefined)}
>
{props.children}
{children}
</NextLink>
)
}

View file

@ -0,0 +1,26 @@
import { ReactNode } from 'react'
interface PageHeaderProps {
icon: ReactNode
title: string
subtitle?: string
className?: string
}
export default function PageHeader({ icon, title, subtitle, className }: PageHeaderProps) {
return (
<div className={className}>
<div className="flex flex-col gap-4">
<div className="flex justify-center">
{icon}
</div>
<h1 className="text-4xl font-bold mt-2 text-center text-gray-200 glow">
{title}
</h1>
{subtitle && (
<p className="text-gray-400 text-center">{subtitle}</p>
)}
</div>
</div>
)
}

View file

@ -1,83 +1,46 @@
"use client"
import {
SiNextdotjs,
SiLucide,
SiVercel,
SiSimpleicons,
SiFontawesome,
SiShadcnui,
SiTailwindcss
} from "react-icons/si"
import Link from 'next/link'
import { useState, useEffect } from 'react'
import { footerMessages } from './footerMessages'
export const footerMessages = [
[
"Built with Next.js",
"https://nextjs.org",
<SiNextdotjs key="nextjs" className="text-md mr-2" />
],
[
"Icons by Lucide",
"https://lucide.dev/",
<SiLucide key="lucide" className="text-md mr-2" />
],
[
"Icons by Simple Icons",
"https://simpleicons.org/",
<SiSimpleicons key="simpleicons" className="text-md mr-2" />
],
[
"Font by Vercel",
"https://vercel.com/font",
<SiVercel key="vercel" className="text-md mr-2" />
],
[
"Icons by Font Awesome",
"https://fontawesome.com/",
<SiFontawesome key="fontawesome" className="text-md mr-2" />
],
[
"Components by Shadcn",
"https://ui.shadcn.com/",
<SiShadcnui key="shadcn" className="text-md mr-2" />
],
[
"Styled with Tailwind",
"https://tailwindcss.com/",
<SiTailwindcss key="tailwind" className="text-md mr-2" />
]
]
interface RandomFooterMsgProps {
index?: number
}
export default function RandomFooterMsg() {
const [randomIndex, setRandomIndex] = useState(0)
const [isMounted, setIsMounted] = useState(false)
const fallbackMessage = footerMessages[0] ?? null
useEffect(() => {
setIsMounted(true)
setRandomIndex(Math.floor(Math.random() * footerMessages.length))
}, [])
if (!isMounted) {
const [message, url, icon] = footerMessages[0]
return (
<Link href={String(url)} target="_blank" rel="noopener noreferrer" className="hover:text-white transition-colors mb-2 sm:mb-0">
<div className="flex items-center justify-center">
{icon}
{message}
</div>
</Link>
)
const getMessageByIndex = (index: number | undefined) => {
if (!footerMessages.length) {
return null
}
const [message, url, icon] = footerMessages[randomIndex]
if (typeof index !== 'number' || Number.isNaN(index)) {
return fallbackMessage
}
const safeIndex = ((Math.floor(index) % footerMessages.length) + footerMessages.length) % footerMessages.length
return footerMessages[safeIndex] ?? fallbackMessage
}
export default function RandomFooterMsg({ index }: RandomFooterMsgProps) {
const message = getMessageByIndex(index)
if (!message) {
return null
}
const { text, url, Icon } = message
return (
<Link href={String(url)} target="_blank" rel="noopener noreferrer" className="hover:text-white transition-colors mb-2 sm:mb-0">
<Link
href={url}
target="_blank"
rel="noopener noreferrer"
className="hover:text-white transition-colors mb-2 sm:mb-0"
>
<div className="flex items-center justify-center">
{icon}
{message}
<Icon className="text-md mr-2" />
{text}
</div>
</Link>
)

View file

@ -0,0 +1,54 @@
import type { IconType } from 'react-icons'
import {
SiFontawesome,
SiLucide,
SiNextdotjs,
SiShadcnui,
SiSimpleicons,
SiTailwindcss,
SiVercel
} from 'react-icons/si'
export type FooterMessage = {
text: string
url: string
Icon: IconType
}
export const footerMessages: FooterMessage[] = [
{
text: 'Built with Next.js',
url: 'https://nextjs.org',
Icon: SiNextdotjs
},
{
text: 'Icons by Lucide',
url: 'https://lucide.dev/',
Icon: SiLucide
},
{
text: 'Icons by Simple Icons',
url: 'https://simpleicons.org/',
Icon: SiSimpleicons
},
{
text: 'Font by Vercel',
url: 'https://vercel.com/font',
Icon: SiVercel
},
{
text: 'Icons by Font Awesome',
url: 'https://fontawesome.com/',
Icon: SiFontawesome
},
{
text: 'Components by Shadcn',
url: 'https://ui.shadcn.com/',
Icon: SiShadcnui
},
{
text: 'Styled with Tailwind',
url: 'https://tailwindcss.com/',
Icon: SiTailwindcss
}
]