diff --git a/app/(home)/page.tsx b/app/(home)/page.tsx index b58bfe6b..37a8f68a 100644 --- a/app/(home)/page.tsx +++ b/app/(home)/page.tsx @@ -2,6 +2,8 @@ import { ArrowUpRight } from "lucide-react"; import Image, { type StaticImageData } from "next/image"; import Link from "next/link"; +import { FadeTransition } from "@/components/page-transition"; + import domainstackIcon from "./icons/domainstack.png"; import snoozleIcon from "./icons/snoozle.png"; import sofaIcon from "./icons/sofa.png"; @@ -49,8 +51,8 @@ const projects: readonly Project[] = [ ] as const; const Page = () => ( - <> -

+ +

Hi there! I’m Jake.{" "} 👋 @@ -58,7 +60,7 @@ const Page = () => (

-

+

I’m a developer based in the{" "} ( decoding="async" className="ring-border size-6 shrink-0 rounded-[26%] ring-1" /> - + {project.name}

@@ -105,7 +107,7 @@ const Page = () => ( ))} - +
); export default Page; diff --git a/app/globals.css b/app/globals.css index 688965cd..e1f6add1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -62,6 +62,9 @@ :root { --radius: 0.625rem; + --duration-exit: 150ms; + --duration-enter: 210ms; + --duration-move: 400ms; --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); @@ -104,6 +107,12 @@ --selection-foreground: oklch(1 0 0); } +@keyframes via-blur { + 30% { + filter: blur(3px); + } +} + .dark { --background: oklch(0 0 0); --foreground: oklch(0.985 0 0); @@ -202,34 +211,131 @@ cursor: pointer; } - /* View Transitions - uses tw-animate-css's `enter` and `exit` keyframes */ - @media (prefers-reduced-motion: reduce) { - ::view-transition-group(*), - ::view-transition-old(*), - ::view-transition-new(*) { - animation: none !important; - } - } - /* Disable the default cross-fade on root (header/footer/chrome stay static) */ ::view-transition-old(root), ::view-transition-new(root) { animation: none; } - /* Main content: fade + slide */ - main { - view-transition-name: main-content; - } - ::view-transition-old(main-content) { + ::view-transition-old(.fade-out) { + --tw-exit-blur: 3px; --tw-exit-opacity: 0; - --tw-exit-translate-y: -8px; - animation: 150ms ease-in forwards exit; + animation: var(--duration-exit) ease-in forwards exit; } - ::view-transition-new(main-content) { + ::view-transition-new(.fade-in) { + --tw-enter-blur: 3px; --tw-enter-opacity: 0; - --tw-enter-translate-y: 12px; - animation: 200ms ease-out forwards enter; + animation: var(--duration-enter) ease-out var(--duration-exit) both enter; + } + + ::view-transition-old(.slide-down) { + --tw-exit-opacity: 0; + --tw-exit-translate-y: 10px; + animation: var(--duration-exit) ease-out both exit; + } + ::view-transition-new(.slide-up) { + --tw-enter-opacity: 0; + --tw-enter-translate-y: 10px; + animation: var(--duration-enter) ease-in var(--duration-exit) both enter; + } + + ::view-transition-new(.slide-from-right) { + --tw-enter-opacity: 0; + --tw-enter-translate-x: 60px; + animation: var(--duration-move) ease-in-out both enter; + } + ::view-transition-old(.slide-to-left) { + --tw-exit-opacity: 0; + --tw-exit-translate-x: -60px; + animation: var(--duration-exit) ease-in both exit; + } + + ::view-transition-new(.slide-from-left) { + --tw-enter-opacity: 0; + --tw-enter-translate-x: -60px; + animation: var(--duration-move) ease-in-out both enter; + } + ::view-transition-old(.slide-to-right) { + --tw-exit-opacity: 0; + --tw-exit-translate-x: 60px; + animation: var(--duration-exit) ease-in both exit; + } + + ::view-transition-old(.nav-forward) { + --tw-exit-opacity: 0; + --tw-exit-translate-x: -60px; + animation: var(--duration-exit) ease-in both exit; + } + ::view-transition-new(.nav-forward) { + --tw-enter-opacity: 0; + --tw-enter-translate-x: 60px; + animation: var(--duration-move) ease-in-out both enter; + } + + ::view-transition-old(.nav-back) { + --tw-exit-opacity: 0; + --tw-exit-translate-x: 60px; + animation: var(--duration-exit) ease-in both exit; + } + ::view-transition-new(.nav-back) { + --tw-enter-opacity: 0; + --tw-enter-translate-x: -60px; + animation: var(--duration-move) ease-in-out both enter; + } + + ::view-transition-group(.morph) { + animation-duration: var(--duration-move); + } + + ::view-transition-image-pair(.morph) { + animation-name: via-blur; + } + + ::view-transition-group(.text-morph) { + animation-duration: var(--duration-move); + } + ::view-transition-old(.text-morph) { + display: none; + } + ::view-transition-new(.text-morph) { + animation: none; + object-fit: none; + object-position: left top; + } + + ::view-transition-old(.scale-out) { + --tw-exit-opacity: 0; + --tw-exit-scale: 85%; + animation: var(--duration-exit) ease-in exit; + } + ::view-transition-new(.scale-in) { + --tw-enter-opacity: 0; + --tw-enter-scale: 85%; + animation: var(--duration-enter) ease-out var(--duration-exit) both enter; + } + + ::view-transition-group(persistent-nav), + ::view-transition-group(persistent-footer) { + animation: none; + z-index: 100; + } + + ::view-transition-old(persistent-nav) { + display: none; + } + ::view-transition-new(persistent-nav), + ::view-transition-old(persistent-footer), + ::view-transition-new(persistent-footer) { + animation: none; + } + + @media (prefers-reduced-motion: reduce) { + ::view-transition-old(*), + ::view-transition-new(*), + ::view-transition-group(*) { + animation-delay: 0s !important; + animation-duration: 0s !important; + } } } diff --git a/app/layout.tsx b/app/layout.tsx index cc51452d..e03647ba 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,4 +1,3 @@ -import { ViewTransition } from "react"; import { JsonLd } from "react-schemaorg"; import type { Person, WebSite } from "schema-dts"; @@ -65,9 +64,7 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => (
-
- {children} -
+
{children}
diff --git a/app/leo/page.tsx b/app/leo/page.tsx index f4e1b906..44f0c7d3 100644 --- a/app/leo/page.tsx +++ b/app/leo/page.tsx @@ -2,6 +2,7 @@ import { JsonLd } from "react-schemaorg"; import type { VideoObject } from "schema-dts"; import { PageTitle } from "@/components/layout/page-title"; +import { FadeTransition } from "@/components/page-transition"; import { Video } from "@/components/video"; import { createMetadata } from "@/lib/metadata"; @@ -22,7 +23,7 @@ export const metadata = createMetadata({ }); const Page = () => ( - <> + item={{ "@context": "https://schema.org", @@ -65,7 +66,7 @@ const Page = () => ( . © 2007 G4 Media, Inc.

- +
); export default Page; diff --git a/app/not-found.tsx b/app/not-found.tsx index bbd82c0b..f6c668f9 100644 --- a/app/not-found.tsx +++ b/app/not-found.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import Link from "next/link"; +import { FadeTransition } from "@/components/page-transition"; import { Button } from "@/components/ui/button"; import { Video } from "@/components/video"; @@ -14,7 +15,7 @@ export const metadata: Metadata = { }; const Page = () => ( - <> + ); export default Page; diff --git a/app/notes/[slug]/page.tsx b/app/notes/[slug]/page.tsx index d4d1c3f7..a6b89c37 100644 --- a/app/notes/[slug]/page.tsx +++ b/app/notes/[slug]/page.tsx @@ -8,13 +8,14 @@ import { import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; -import { Suspense } from "react"; +import { Suspense, ViewTransition } from "react"; import { JsonLd } from "react-schemaorg"; import type { BlogPosting } from "schema-dts"; import { CommentCount } from "@/components/comment-count"; import { Comments } from "@/components/comments/comments"; import { CommentsSkeleton } from "@/components/comments/comments-skeleton"; +import { DirectionalTransition } from "@/components/page-transition"; import { ViewCounter } from "@/components/view-counter"; import authorConfig from "@/lib/config/author"; import siteConfig from "@/lib/config/site"; @@ -83,7 +84,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => { const { default: MDXContent } = await import(`../../../${POSTS_DIR}/${slug}/index.mdx`); return ( - <> + item={{ "@context": "https://schema.org", @@ -109,7 +110,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => { }} /> -
+
}) => {
-

- -

+ +

+ +

+
@@ -190,13 +190,21 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {

Comments are closed.

) : ( - }> - + + + + } + > + + + )} - + ); }; diff --git a/app/notes/page.tsx b/app/notes/page.tsx index 711487c6..f675e7c9 100644 --- a/app/notes/page.tsx +++ b/app/notes/page.tsx @@ -1,6 +1,8 @@ import Link from "next/link"; +import { ViewTransition } from "react"; import { PageTitle } from "@/components/layout/page-title"; +import { DirectionalTransition } from "@/components/page-transition"; import { PostStats, PostStatsProvider } from "@/components/post-stats"; import authorConfig from "@/lib/config/author"; import { createMetadata } from "@/lib/metadata"; @@ -56,24 +58,26 @@ const PostsList = async () => { Object.entries(postsByYear).forEach(([year, yearPosts]) => { sections.push(
-

+

{year}

    {yearPosts.map(({ slug, dateISO, dateTitle, dateDisplay, title, htmlTitle }) => ( -
  • +
  • - + + +
    @@ -89,12 +93,12 @@ const PostsList = async () => { }; const Page = async () => ( - <> + Notes - + ); export default Page; diff --git a/app/projects/page.tsx b/app/projects/page.tsx index 1407e16b..64cc8ab3 100644 --- a/app/projects/page.tsx +++ b/app/projects/page.tsx @@ -4,6 +4,7 @@ import { Suspense } from "react"; import { ActivityCalendar } from "@/components/activity-calendar"; import { PageTitle } from "@/components/layout/page-title"; +import { FadeTransition } from "@/components/page-transition"; import { RelativeTime } from "@/components/relative-time"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; @@ -30,10 +31,10 @@ const Page = async () => { const [contributions, repos] = await Promise.all([getContributions(), getRepos()]); return ( - <> + Projects -

    +

    { )} -

    +

    {