diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 59d9e38..170a90e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,8 +36,3 @@ jobs: run: bun lint env: CI: true - - - name: Danger - run: bun danger ci - env: - GITHUB_TOKEN: ${{ secrets.DANGER_TOKEN }} diff --git a/bun.lockb b/bun.lockb index c333ba5..b7d71d5 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index ac524e7..06c30ff 100644 --- a/package.json +++ b/package.json @@ -16,44 +16,43 @@ "@tailwindcss/forms": "^0.5.7", "@types/common-tags": "^1.8.4", "@types/cookie": "^0.6.0", - "@types/jsonwebtoken": "^9.0.5", + "@types/jsonwebtoken": "^9.0.6", "@types/jwa": "^2.0.3", - "@types/react": "^18.2.47", - "@types/react-dom": "^18.2.18", - "@types/uuid": "^9.0.7", - "autoprefixer": "^10.4.16", - "danger": "^11.3.1", - "eslint": "8.56.0", - "eslint-config-next": "14.0.4", - "postcss": "^8.4.33", - "prettier": "^3.2.2", - "prettier-plugin-tailwindcss": "^0.5.11", - "sharp": "^0.33.2", - "tailwindcss": "^3.4.1", - "typescript": "^5.3.3" + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@types/uuid": "^9.0.8", + "autoprefixer": "^10.4.19", + "eslint": "9.4.0", + "eslint-config-next": "14.2.3", + "postcss": "^8.4.38", + "prettier": "^3.3.1", + "prettier-plugin-tailwindcss": "^0.6.2", + "sharp": "^0.33.4", + "tailwindcss": "^3.4.4", + "typescript": "^5.4.5" }, "dependencies": { - "@marsidev/react-turnstile": "^0.4.1", + "@marsidev/react-turnstile": "^0.7.1", "@otters/monzo": "^2.1.2", - "alistair": "^1.5.6", - "bwitch": "^0.1.3", - "clsx": "^2.1.0", + "alistair": "^1.9.0", + "bwitch": "^0.3.0", + "clsx": "^2.1.1", "common-tags": "^1.8.2", "cookie": "^0.6.0", - "dayjs": "^1.11.10", - "dotenv": "^16.3.1", + "dayjs": "^1.11.11", + "dotenv": "^16.4.5", "envsafe": "^2.0.3", - "framer-motion": "^10.18.0", + "framer-motion": "^11.2.10", "jsonwebtoken": "^9.0.2", "jwa": "^2.0.0", - "next": "^14.0.4", + "next": "^14.2.3", "nextkit": "^3.4.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", "react-hot-toast": "^2.4.1", - "react-icons": "^5.0.1", + "react-icons": "^5.2.1", "use-lanyard": "^1.5.2", "uuid": "^9.0.1", - "zod": "^3.22.4" + "zod": "^3.23.8" } } diff --git a/public/album.png b/public/album.png new file mode 100644 index 0000000..d9b8b67 Binary files /dev/null and b/public/album.png differ diff --git a/public/alistair.jpeg b/public/alistair.jpeg new file mode 100644 index 0000000..021ce49 Binary files /dev/null and b/public/alistair.jpeg differ diff --git a/public/grain.jpeg b/public/grain.jpeg new file mode 100644 index 0000000..38b5459 Binary files /dev/null and b/public/grain.jpeg differ diff --git a/src/components/message.tsx b/src/components/message.tsx new file mode 100644 index 0000000..466da4e --- /dev/null +++ b/src/components/message.tsx @@ -0,0 +1,87 @@ +import clsx from 'clsx'; +import {motion} from 'framer-motion'; +import type {ReactNode} from 'react'; +import alistair from '../../public/alistair.jpeg'; + +export interface MessageGroupProps { + messages: Array<{key: string; content: ReactNode}>; +} + +const group = { + hidden: {opacity: 0, x: -5}, + show: {opacity: 1, x: 0}, +}; + +const item = { + hidden: {opacity: 0}, + show: {opacity: 1}, +}; + +function MessageBubble({ + isLast, + isFirst, + content, +}: { + isLast?: boolean; + isFirst?: boolean; + content: ReactNode; +}) { + return ( + + {content} + + ); +} + +export function MessageGroup({messages}: MessageGroupProps) { + return ( + + Me standing in front of some tents + +
+ {messages.map(({key: id, content}, i) => ( + + ))} +
+
+ ); +} diff --git a/src/components/spinquee.tsx b/src/components/spinquee.tsx new file mode 100644 index 0000000..d6f24ed --- /dev/null +++ b/src/components/spinquee.tsx @@ -0,0 +1,104 @@ +import {MotionValue, motion, useSpring, useTransform} from 'framer-motion'; +import {useCallback, useEffect, useState, type PropsWithChildren} from 'react'; + +export interface SpinqueeProps { + children: React.ReactNode[]; + size: number; +} + +function duplicateChildren(children: React.ReactNode[], multiplier: number) { + return [...Array(multiplier)].flatMap(() => children); +} + +function Item({ + children, + index, + angle, + total, + size, + activeIndex, +}: PropsWithChildren<{ + index: number; + angle: MotionValue; + total: number; + size: number; + activeIndex: number; +}>) { + const isActive = index % total === activeIndex; + + return ( + value + index * (360 / total)), + }} + > +
+ {children} +
+
+ ); +} + +export function Spinquee({children, size}: SpinqueeProps) { + const angle = useSpring(0, { + stiffness: 1200, + damping: 30, + mass: 0.05, + }); + const [activeIndex, setActiveIndex] = useState(0); + const [container, setContainer] = useState(null); + + const duplicatedChildren = duplicateChildren(children, 10); + + const snapToNearest = useCallback(() => { + const currentAngle = angle.get(); + const anglePerItem = 360 / duplicatedChildren.length; + const nearestIndex = Math.round(currentAngle / anglePerItem) % duplicatedChildren.length; + const nearestAngle = nearestIndex * anglePerItem; + angle.set(nearestAngle); + setActiveIndex(nearestIndex); + }, [angle, duplicatedChildren.length]); + + useEffect(() => { + if (!container) return; + + let timeout: NodeJS.Timeout; + const wheel = (e: WheelEvent) => { + angle.set(angle.get() + e.deltaY / 10); + clearTimeout(timeout); + timeout = setTimeout(snapToNearest, 150); // Adjust delay as needed + }; + + container.addEventListener('wheel', wheel); + + return () => { + container.removeEventListener('wheel', wheel); + }; + }, [container, angle, snapToNearest]); + + return ( +
+
+ {duplicatedChildren.map((child, index) => ( + + {child} + + ))} +
+
+ ); +} diff --git a/src/components/stats.tsx b/src/components/stats.tsx index 55b5bb9..a3f8a9c 100644 --- a/src/components/stats.tsx +++ b/src/components/stats.tsx @@ -8,7 +8,7 @@ export function Stats() { const firstEverLoadTime = useMemo(() => new Date(stats.time), [stats.time]); return ( -
+

You first visited my website on {firstEverLoadTime.toLocaleDateString()} at{' '} {firstEverLoadTime.toLocaleTimeString()} and on this first visit, you were on the{' '} diff --git a/src/globals.css b/src/globals.css index 9085082..f635d30 100644 --- a/src/globals.css +++ b/src/globals.css @@ -3,5 +3,16 @@ @tailwind utilities; body { - @apply bg-grid-neutral-200/40 bg-neutral-100 text-neutral-900 antialiased dark:bg-grid-neutral-800/50 dark:bg-neutral-900 dark:text-neutral-100; + @apply overscroll-none bg-[#fefefe] text-neutral-900 antialiased dark:bg-[#040404] dark:text-neutral-100; +} + +#__next { + @apply absolute inset-0; +} + +#__next::before { + content: ''; + background: url(/grain.jpeg) repeat center center; + background-size: 50%; + @apply fixed inset-0 -z-10 opacity-[0.03] dark:opacity-[0.018]; } diff --git a/src/hooks/use-lerp-transform.ts b/src/hooks/use-lerp-transform.ts new file mode 100644 index 0000000..f5bc024 --- /dev/null +++ b/src/hooks/use-lerp-transform.ts @@ -0,0 +1,15 @@ +import {MotionValue, useTransform} from 'framer-motion'; +import {useRef} from 'react'; + +export function useLerpTransform(value: MotionValue) { + const prev = useRef(); + + return useTransform(value, newValue => { + const prevValue = prev.current ?? newValue; + const lerpValue = prevValue + (newValue - prevValue) * 10; + + prev.current = newValue; + + return lerpValue; + }); +} diff --git a/src/pages/404.tsx b/src/pages/404.tsx index 89ce5b7..e53c312 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -3,7 +3,7 @@ import Link from 'next/link'; export default function Page404() { return (

-

404

+

404

Sorry, I couldn't locate that page for ya

diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index c7e3578..dd810c6 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,22 +1,21 @@ import '../globals.css'; import type {AppProps} from 'next/app'; -import {Newsreader} from 'next/font/google'; -import font from 'next/font/local'; +import {Inter, Newsreader} from 'next/font/google'; import Head from 'next/head'; import {useEffect} from 'react'; import {Toaster} from 'react-hot-toast'; import {useFirstEverLoad, useVisitCounts} from '../hooks/use-first-ever-load'; -const title = Newsreader({ +const serif = Newsreader({ subsets: ['latin'], weight: ['400', '200'], style: 'italic', fallback: ['serif'], }); -const body = font({ - src: '../fonts/roobert-variable.woff2', +const body = Inter({ + subsets: ['latin'], }); export default function App({Component, pageProps}: AppProps) { @@ -33,7 +32,7 @@ export default function App({Component, pageProps}: AppProps) {