├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── next.config.mjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.mjs ├── public ├── albums │ ├── 0.jpg │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ └── 7.jpg ├── love-this │ ├── 2.25-hours.mp4 │ ├── 9-hours.mp4 │ ├── fade-away.mp4 │ ├── simple-kit.mp4 │ └── swap-component.mp4 ├── next.svg └── vercel.svg ├── src ├── app │ ├── _components │ │ ├── cards.tsx │ │ ├── show-more.tsx │ │ ├── toolbar │ │ │ ├── dev-switch.tsx │ │ │ └── index.tsx │ │ ├── ui-toolbar.tsx │ │ └── verify-address-button.tsx │ ├── _page-blank.tsx │ ├── _page-phone.tsx │ ├── affirmations │ │ └── page.tsx │ ├── album │ │ ├── beach.jpg │ │ ├── case.png │ │ ├── couple.jpg │ │ ├── iphone.png │ │ ├── mexico.jpg │ │ ├── page.tsx │ │ ├── salt-flats.jpg │ │ └── styles.module.css │ ├── animated-list │ │ ├── page.tsx │ │ └── transactions.ts │ ├── animated-tabs │ │ └── page.tsx │ ├── audiobooks │ │ ├── all-the-light.jpg │ │ ├── dune.jpg │ │ ├── farewell.jpg │ │ └── page.tsx │ ├── bird │ │ └── page.tsx │ ├── carousel │ │ └── page.tsx │ ├── cd │ │ └── page.tsx │ ├── checkout │ │ └── page.tsx │ ├── create-new │ │ └── page.tsx │ ├── dynamic-tabs │ │ └── page.tsx │ ├── explore │ │ └── page.tsx │ ├── favicon.ico │ ├── flashlight │ │ └── page.tsx │ ├── folder │ │ └── page.tsx │ ├── globals.css │ ├── happy-cards │ │ ├── iphone-black.svg │ │ └── page.tsx │ ├── layout.tsx │ ├── love-folder │ │ └── page.tsx │ ├── love-this │ │ ├── her.jpg │ │ ├── interstellar.jpg │ │ ├── love-this │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ │ ├── murph.jpg │ │ ├── page.tsx │ │ └── scarlett.jpg │ ├── magic-button │ │ └── page.tsx │ ├── neu │ │ ├── page.tsx │ │ └── styles.module.css │ ├── northern-lights │ │ └── page.tsx │ ├── page.tsx │ ├── phone-glow │ │ └── page.tsx │ ├── podcasts │ │ └── page.tsx │ ├── popcorn │ │ ├── iphone-black.svg │ │ ├── mesh-1.png │ │ ├── mesh-2.png │ │ ├── mesh-3.png │ │ └── page.tsx │ ├── shop-claim │ │ ├── page.tsx │ │ ├── shop-dollar.jpg │ │ ├── shop.png │ │ └── shopify.png │ ├── slingshot │ │ └── page.tsx │ ├── smooth-dropdown │ │ ├── interstellar.jpg │ │ ├── murph.jpg │ │ └── page.tsx │ ├── switch │ │ └── page.tsx │ ├── thunder-airdrop │ │ └── page.tsx │ ├── thunder-speed │ │ └── page.tsx │ ├── timer │ │ └── page.tsx │ ├── toast │ │ └── page.tsx │ ├── todo │ │ └── page.tsx │ ├── toggle-theme │ │ ├── interstellar.jpg │ │ ├── murph.jpg │ │ └── page.tsx │ ├── tooltip-nav │ │ └── page.tsx │ ├── transaction-drawer │ │ ├── page.tsx │ │ └── transactions.ts │ ├── ui-course-form │ │ └── page.tsx │ └── vinyl │ │ └── page.tsx ├── assets │ ├── azura.png │ ├── barn.jpg │ ├── beach.jpg │ ├── behance.png │ ├── bird.jpg │ ├── dribble.png │ ├── field.jpg │ ├── fish.jpg │ ├── forest-sky.jpg │ ├── framer-motion.svg │ ├── huberman.jpg │ ├── i-just-need-daniel.webp │ ├── i-just-need.png │ ├── iphone-black.svg │ ├── iphone-gold.svg │ ├── iphone-silver.svg │ ├── joe-rogan.jpg │ ├── linkedin.png │ ├── monkey.jpg │ ├── moral.jpg │ ├── orlando.jpg │ ├── person.jpg │ ├── playful-inca.jpg │ ├── snow-board.jpg │ ├── stoic-inca.jpg │ ├── tailwind.jpg │ ├── thunder-bg.png │ ├── tiger.jpg │ ├── uncommon.png │ ├── vaun-logo.png │ └── x.png ├── components │ ├── grainy-background.tsx │ ├── icons.tsx │ ├── morph.tsx │ ├── sidebar.tsx │ └── ui │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── checkbox.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── scroll-area.tsx │ │ └── switch.tsx ├── hooks │ ├── use-media-query.tsx │ └── use-mouse-position.tsx └── lib │ └── utils.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Welcome to my lab. 2 | 3 | These are my raw, unfiltered designs for my **100 hours challenge**. 4 | 5 | Please roast me on [X](https://x.com/vaunblu). 6 | 7 | ## Getting Started 8 | 9 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 10 | 11 | First, run the development server: 12 | 13 | ```bash 14 | npm run dev 15 | # or 16 | yarn dev 17 | # or 18 | pnpm dev 19 | # or 20 | bun dev 21 | ``` 22 | 23 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 24 | 25 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 26 | 27 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 28 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lab", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbo", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@number-flow/react": "^0.5.7", 13 | "@radix-ui/react-checkbox": "^1.1.3", 14 | "@radix-ui/react-dialog": "^1.1.5", 15 | "@radix-ui/react-dropdown-menu": "^2.1.5", 16 | "@radix-ui/react-popover": "^1.1.5", 17 | "@radix-ui/react-progress": "^1.1.1", 18 | "@radix-ui/react-scroll-area": "^1.2.2", 19 | "@radix-ui/react-select": "^2.1.5", 20 | "@radix-ui/react-slot": "^1.1.1", 21 | "@radix-ui/react-switch": "^1.1.2", 22 | "@radix-ui/react-tabs": "^1.1.2", 23 | "class-variance-authority": "^0.7.1", 24 | "clsx": "^2.1.1", 25 | "framer-motion": "^11.18.2", 26 | "geist": "^1.3.1", 27 | "jotai": "^2.12.2", 28 | "lucide-react": "^0.407.0", 29 | "motion-number": "^0.1.7", 30 | "nanoid": "^5.1.4", 31 | "next": "14.2.5", 32 | "react": "^18.3.1", 33 | "react-dom": "^18.3.1", 34 | "react-use-measure": "^2.1.7", 35 | "tailwind-merge": "^2.6.0", 36 | "tailwindcss-animate": "^1.0.7", 37 | "vaul": "^1.1.2" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "^20.17.16", 41 | "@types/react": "^18.3.18", 42 | "@types/react-dom": "^18.3.5", 43 | "eslint": "^8.57.1", 44 | "eslint-config-next": "14.2.5", 45 | "postcss": "^8.5.1", 46 | "tailwindcss": "^3.4.17", 47 | "typescript": "^5.7.3" 48 | }, 49 | "packageManager": "pnpm@8.15.4+sha512.0bd3a9be9eb0e9a692676deec00a303ba218ba279d99241475616b398dbaeedd11146f92c2843458f557b1d127e09d4c171e105bdcd6b61002b39685a8016b9e" 50 | } 51 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/albums/0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/albums/0.jpg -------------------------------------------------------------------------------- /public/albums/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/albums/1.jpg -------------------------------------------------------------------------------- /public/albums/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/albums/2.jpg -------------------------------------------------------------------------------- /public/albums/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/albums/3.jpg -------------------------------------------------------------------------------- /public/albums/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/albums/4.jpg -------------------------------------------------------------------------------- /public/albums/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/albums/5.jpg -------------------------------------------------------------------------------- /public/albums/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/albums/6.jpg -------------------------------------------------------------------------------- /public/albums/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/albums/7.jpg -------------------------------------------------------------------------------- /public/love-this/2.25-hours.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/love-this/2.25-hours.mp4 -------------------------------------------------------------------------------- /public/love-this/9-hours.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/love-this/9-hours.mp4 -------------------------------------------------------------------------------- /public/love-this/fade-away.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/love-this/fade-away.mp4 -------------------------------------------------------------------------------- /public/love-this/simple-kit.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/love-this/simple-kit.mp4 -------------------------------------------------------------------------------- /public/love-this/swap-component.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/public/love-this/swap-component.mp4 -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/_components/cards.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion, MotionConfig, useMotionValue, Variants } from "framer-motion"; 4 | import * as React from "react"; 5 | import { useTransform } from "framer-motion"; 6 | import { cn } from "@/lib/utils"; 7 | import { useMousePosition } from "@/hooks/use-mouse-position"; 8 | import { Heart } from "lucide-react"; 9 | 10 | function Card(props: { 11 | className?: string; 12 | cardClassName?: string; 13 | index: number; 14 | activeCard: number; 15 | setActiveCard: React.Dispatch>; 16 | src: string; 17 | title: string; 18 | }) { 19 | const videoRef = React.useRef(null); 20 | const x = useMotionValue(0.5); 21 | const xFromCenter = useMotionValue(0); 22 | 23 | const rotate = useTransform(x, [0, 1], [5, -5]); 24 | const translateY = useTransform(xFromCenter, [0, 1], [-30, -5]); 25 | 26 | function handleMouse(event: React.MouseEvent) { 27 | const rect = event.currentTarget.getBoundingClientRect(); 28 | 29 | x.set((event.clientX - rect.left) / rect.width); 30 | xFromCenter.set( 31 | (Math.abs(event.clientX - rect.left - rect.width / 2) / rect.width) * 2, 32 | ); 33 | } 34 | 35 | const cardVariants: Variants = { 36 | press: { 37 | rotate: rotate.get() * (props.activeCard === props.index ? -1 : 1), 38 | translateY: 39 | translateY.get() * (props.activeCard === props.index ? -1 : 1), 40 | }, 41 | }; 42 | 43 | const titleOverlayVariants: Variants = { 44 | press: { opacity: props.activeCard === props.index ? 0 : 0.5 }, 45 | }; 46 | 47 | return ( 48 | 60 | 65 | props.setActiveCard((prevState) => { 66 | if (prevState === props.index) return 0; 67 | return props.index; 68 | }) 69 | } 70 | onMouseOver={() => videoRef.current?.play()} 71 | onMouseLeave={() => { 72 | if (props.activeCard !== props.index) { 73 | videoRef.current?.pause(); 74 | } 75 | }} 76 | onMouseMove={handleMouse} 77 | className={cn( 78 | "relative aspect-square overflow-hidden rounded-2xl bg-gray-300 text-background drop-shadow-[0_0_20px_rgba(0,0,0,0.0)] grayscale first:drop-shadow-[0_-50px_100px_rgba(0,0,0,0.075)] hover:grayscale-0", 79 | { "grayscale-0": props.activeCard === props.index }, 80 | props.cardClassName, 81 | )} 82 | > 83 | 98 | 99 | ); 100 | } 101 | 102 | export function Cards() { 103 | const [activeCard, setActiveCard] = React.useState(0); 104 | useMousePosition(); 105 | 106 | return ( 107 | 108 |
109 | 113 |
114 |

July recap

115 |

2024

116 |
117 |
118 |
119 | 123 |
124 |
125 |
126 |

Thank you

127 |

For all the support

128 |
129 |
130 |
131 |
132 | 480+ followers 133 |
134 |
135 |
136 |
137 | 141 | 148 | 156 | 164 | 172 | 180 |
181 |
182 | ); 183 | } 184 | -------------------------------------------------------------------------------- /src/app/_components/show-more.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as Dialog from "@radix-ui/react-dialog"; 4 | import * as React from "react"; 5 | import { X } from "lucide-react"; 6 | import { AnimatePresence, motion, MotionConfig } from "framer-motion"; 7 | import { Button } from "@/components/ui/button"; 8 | 9 | function ShowMoreDialog(props: { 10 | open: boolean; 11 | setOpen: React.Dispatch>; 12 | }) { 13 | return ( 14 | 15 | 16 | {props.open && ( 17 | 18 | 19 | 25 | 26 | e.preventDefault()}> 27 | 33 |
34 |
35 | 36 | 40 | Pages 41 | 42 | 43 | 44 | 48 | 17 unique pages viewed 49 | 50 | 51 |
52 | 53 | 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | 68 |
69 | 70 | 71 | 72 | Close 73 | 74 | 75 | 76 | 77 | )} 78 | 79 | 80 | ); 81 | } 82 | 83 | export function ShowMore() { 84 | const [open, setOpen] = React.useState(false); 85 | 86 | return ( 87 | 88 | 89 | 90 |
91 | 95 | Pages 96 | 97 | 101 | 17 unique pages viewed 102 | 103 |
104 | 105 | 106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 | 117 | 118 |
119 | 126 |
127 | 128 | 129 | ); 130 | } 131 | -------------------------------------------------------------------------------- /src/app/_components/toolbar/dev-switch.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SwitchPrimitives from "@radix-ui/react-switch"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | import { CodeXml } from "lucide-react"; 8 | 9 | const DevSwitch = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 26 | 27 | 28 | 29 | )); 30 | DevSwitch.displayName = SwitchPrimitives.Root.displayName; 31 | 32 | export { DevSwitch }; 33 | -------------------------------------------------------------------------------- /src/app/_components/verify-address-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Button } from "@/components/ui/button"; 5 | import { AnimatePresence, motion } from "framer-motion"; 6 | 7 | export function VerifyAddressButton() { 8 | const [open, setOpen] = React.useState(false); 9 | 10 | React.useEffect(() => { 11 | const handleKeyDown = (event: KeyboardEvent) => { 12 | if (event.key === "Escape") { 13 | setOpen(false); 14 | } 15 | }; 16 | 17 | window.addEventListener("keydown", handleKeyDown); 18 | return () => window.removeEventListener("keydown", handleKeyDown); 19 | }, [open]); 20 | 21 | return ( 22 | <> 23 | 24 | 31 | 32 | 33 | 34 | {open ? ( 35 | 40 | 44 | Verify wallet 45 | 46 | 53 |
54 |

Are you sure you want to verify the wallet

55 |

0x123...abc

56 |
57 |
58 | 64 | 71 |
72 |
73 |
74 | ) : null} 75 |
76 | 77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /src/app/_page-blank.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GrainyBackground } from "@/components/grainy-background"; 4 | import { motion, MotionConfig, type Transition } from "framer-motion"; 5 | import React from "react"; 6 | 7 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 8 | 9 | const Context = React.createContext<{ 10 | status: string; 11 | setStatus: React.Dispatch>; 12 | }>({ status: "", setStatus: () => null }); 13 | 14 | function InnerContent() { 15 | const ctx = React.useContext(Context); 16 | 17 | return ( 18 |
19 |

Lab

20 |
21 | ); 22 | } 23 | 24 | export default function HomePage() { 25 | const [status, setStatus] = React.useState("idle"); 26 | 27 | React.useEffect(() => { 28 | function handleEscape(e: KeyboardEvent) { 29 | if (e.key === "Escape") { 30 | setStatus("idle"); 31 | } 32 | } 33 | window.addEventListener("keydown", handleEscape); 34 | return () => window.removeEventListener("keydown", handleEscape); 35 | }, [setStatus]); 36 | 37 | return ( 38 | 39 | 40 | 45 | 46 | 47 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/app/_page-phone.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GrainyBackground } from "@/components/grainy-background"; 4 | import { cn } from "@/lib/utils"; 5 | import { 6 | AnimatePresence, 7 | motion, 8 | MotionConfig, 9 | Transition, 10 | } from "framer-motion"; 11 | import React from "react"; 12 | import Image from "next/image"; 13 | import svgPhone from "@/assets/iphone-black.svg"; 14 | 15 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 16 | 17 | const Context = React.createContext<{ 18 | status: string; 19 | setStatus: React.Dispatch>; 20 | }>({ status: "", setStatus: () => null }); 21 | 22 | function InnerContent() { 23 | const ctx = React.useContext(Context); 24 | 25 | return ( 26 |
27 |

Lab

28 |
29 | ); 30 | } 31 | 32 | export default function HomePage() { 33 | const [status, setStatus] = React.useState("idle"); 34 | 35 | React.useEffect(() => { 36 | function handleEscape(e: KeyboardEvent) { 37 | if (e.key === "Escape") { 38 | setStatus("idle"); 39 | } 40 | } 41 | window.addEventListener("keydown", handleEscape); 42 | return () => window.removeEventListener("keydown", handleEscape); 43 | }, [setStatus]); 44 | 45 | return ( 46 | 47 | 48 |
49 | 54 |
55 | 56 |
57 | 58 |
59 |
60 |
61 | 62 | iphone mock 67 | 68 |
69 |
70 |
71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/app/album/beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/album/beach.jpg -------------------------------------------------------------------------------- /src/app/album/case.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/album/case.png -------------------------------------------------------------------------------- /src/app/album/couple.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/album/couple.jpg -------------------------------------------------------------------------------- /src/app/album/iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/album/iphone.png -------------------------------------------------------------------------------- /src/app/album/mexico.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/album/mexico.jpg -------------------------------------------------------------------------------- /src/app/album/salt-flats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/album/salt-flats.jpg -------------------------------------------------------------------------------- /src/app/album/styles.module.css: -------------------------------------------------------------------------------- 1 | .blur-up { 2 | mask: linear-gradient(to top, white 50%, transparent); 3 | backdrop-filter: blur(4px); 4 | } 5 | 6 | .blur-down { 7 | mask: linear-gradient(to bottom, black 50%, transparent); 8 | backdrop-filter: blur(4px); 9 | } 10 | 11 | .metal { 12 | background: radial-gradient(circle, 13 | rgba(221, 221, 221, 1) 0%, 14 | rgba(240, 240, 240, 1) 100%); 15 | } 16 | -------------------------------------------------------------------------------- /src/app/animated-list/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GrainyBackground } from "@/components/grainy-background"; 4 | import { 5 | AnimatePresence, 6 | motion, 7 | MotionConfig, 8 | Transition, 9 | } from "framer-motion"; 10 | import React, { useMemo, useState } from "react"; 11 | import Image from "next/image"; 12 | import svgPhone from "@/assets/iphone-black.svg"; 13 | import { nanoid } from "nanoid"; 14 | import { transactionsData } from "./transactions"; 15 | import NumberFlow from "@number-flow/react"; 16 | import { Plus } from "lucide-react"; 17 | 18 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 19 | 20 | const Context = React.createContext<{ 21 | status: string; 22 | setStatus: React.Dispatch>; 23 | }>({ status: "", setStatus: () => null }); 24 | 25 | type Transaction = { 26 | id: string; 27 | title: string; 28 | cost: number; 29 | date: string; 30 | }; 31 | 32 | function InnerContent() { 33 | let [transactions, setTransactions] = useState>([ 34 | { id: nanoid(), ...transactionsData[0] }, 35 | { id: nanoid(), ...transactionsData[1] }, 36 | { id: nanoid(), ...transactionsData[2] }, 37 | ]); 38 | 39 | const transactionTotal = useMemo(() => { 40 | let total = 0; 41 | for (const transaction of transactions) { 42 | total += transaction.cost; 43 | } 44 | return total; 45 | }, [transactions]); 46 | 47 | function addTodo() { 48 | let newId = nanoid(); 49 | const randomIndex = Math.floor(Math.random() * 95) + 3; 50 | const randomTransaction = transactionsData[randomIndex]; 51 | setTransactions([{ id: newId, ...randomTransaction }, ...transactions]); 52 | } 53 | 54 | function removeTodo(transaction: Transaction) { 55 | setTransactions((transactions) => 56 | transactions.filter((t) => t.id !== transaction.id), 57 | ); 58 | } 59 | 60 | return ( 61 |
62 |
63 |
64 |
65 |

Total balance

66 | 75 |
76 |
77 |
78 |
79 |

Transactions

80 | 85 | 86 | 87 |
88 |
    89 | 90 | {transactions.map((transaction) => ( 91 | 98 |
    removeTodo(transaction)} 100 | className="mb-2 flex w-full items-center justify-between rounded-xl bg-[#ced8da]/20 p-3 text-left" 101 | > 102 |
    103 |

    {transaction.date}

    104 |

    {transaction.title}

    105 |
    106 |

    ${transaction.cost}

    107 |
    108 |
    109 | ))} 110 |
    111 |
112 |
113 | 114 |
115 |
116 |
117 |
118 | ); 119 | } 120 | 121 | export default function HomePage() { 122 | const [status, setStatus] = React.useState("idle"); 123 | 124 | React.useEffect(() => { 125 | function handleEscape(e: KeyboardEvent) { 126 | if (e.key === "Escape") { 127 | setStatus("idle"); 128 | } 129 | } 130 | window.addEventListener("keydown", handleEscape); 131 | return () => window.removeEventListener("keydown", handleEscape); 132 | }, [setStatus]); 133 | 134 | return ( 135 | 136 | 137 |
138 | 143 |
144 | 145 |
146 | 147 | {/*
*/} 148 |
149 |
150 |
151 | 152 | iphone mock 157 | 158 |
159 |
160 |
161 | ); 162 | } 163 | -------------------------------------------------------------------------------- /src/app/animated-list/transactions.ts: -------------------------------------------------------------------------------- 1 | export const transactionsData = [ 2 | { title: "Coffee Shop Purchase", cost: 4.75, date: "3 Jan 2025" }, 3 | { title: "Grocery Store", cost: 67.32, date: "17 Feb 2025" }, 4 | { title: "Gas Station", cost: 45.89, date: "5 Dec 2024" }, 5 | { title: "Online Subscription", cost: 9.99, date: "22 Mar 2025" }, 6 | { title: "Restaurant Dinner", cost: 34.56, date: "14 Nov 2024" }, 7 | { title: "Pharmacy", cost: 12.43, date: "8 Apr 2025" }, 8 | { title: "Movie Tickets", cost: 24.5, date: "29 Jan 2025" }, 9 | { title: "Clothing Store", cost: 78.25, date: "11 May 2025" }, 10 | { title: "Fast Food", cost: 8.75, date: "2 Oct 2024" }, 11 | { title: "Hardware Store", cost: 56.78, date: "19 Jun 2025" }, 12 | { title: "Book Store", cost: 19.99, date: "7 Sep 2024" }, 13 | { title: "Uber Ride", cost: 15.67, date: "25 Feb 2025" }, 14 | { title: "Parking Fee", cost: 5.0, date: "13 Jul 2025" }, 15 | { title: "Electronics Store", cost: 89.99, date: "30 Dec 2024" }, 16 | { title: "Hair Salon", cost: 45.0, date: "16 Mar 2025" }, 17 | { title: "Pet Store", cost: 32.45, date: "1 Aug 2024" }, 18 | { title: "Office Supplies", cost: 23.56, date: "24 Apr 2025" }, 19 | { title: "Gym Membership", cost: 29.99, date: "9 Jan 2025" }, 20 | { title: "Convenience Store", cost: 7.85, date: "28 Nov 2024" }, 21 | { title: "Car Wash", cost: 12.0, date: "15 May 2025" }, 22 | { title: "Bakery", cost: 8.5, date: "3 Oct 2024" }, 23 | { title: "Dentist Copay", cost: 25.0, date: "21 Jun 2025" }, 24 | { title: "Streaming Service", cost: 14.99, date: "6 Feb 2025" }, 25 | { title: "Home Goods", cost: 67.89, date: "27 Jul 2024" }, 26 | { title: "Mobile Phone Bill", cost: 85.34, date: "12 Mar 2025" }, 27 | { title: "Ice Cream Shop", cost: 6.75, date: "31 Aug 2024" }, 28 | { title: "Dry Cleaning", cost: 22.5, date: "18 Apr 2025" }, 29 | { title: "Concert Tickets", cost: 75.0, date: "4 Jan 2025" }, 30 | { title: "Sporting Goods", cost: 49.99, date: "23 Sep 2024" }, 31 | { title: "Florist", cost: 35.0, date: "10 May 2025" }, 32 | { title: "Shoe Store", cost: 64.5, date: "26 Dec 2024" }, 33 | { title: "Taxi Fare", cost: 18.75, date: "14 Jun 2025" }, 34 | { title: "Charity Donation", cost: 10.0, date: "2 Feb 2025" }, 35 | { title: "Veterinary Clinic", cost: 95.0, date: "20 Jul 2024" }, 36 | { title: "Music Download", cost: 1.29, date: "7 Mar 2025" }, 37 | { title: "Pizza Delivery", cost: 22.95, date: "25 Aug 2024" }, 38 | { title: "Tool Rental", cost: 38.5, date: "12 Apr 2025" }, 39 | { title: "Bike Shop", cost: 52.75, date: "28 Nov 2024" }, 40 | { title: "Laundromat", cost: 4.5, date: "15 Jan 2025" }, 41 | { title: "Toy Store", cost: 27.99, date: "3 Jun 2025" }, 42 | { title: "ATM Withdrawal", cost: 40.0, date: "19 Oct 2024" }, 43 | { title: "Eyeglasses", cost: 95.99, date: "8 Feb 2025" }, 44 | { title: "Jewelry Store", cost: 78.5, date: "24 Jul 2025" }, 45 | { title: "Food Truck", cost: 11.25, date: "11 Mar 2025" }, 46 | { title: "Magazine Subscription", cost: 19.95, date: "29 Aug 2024" }, 47 | { title: "Craft Store", cost: 43.27, date: "16 Apr 2025" }, 48 | { title: "Bowling Alley", cost: 32.0, date: "5 Dec 2024" }, 49 | { title: "Art Supplies", cost: 28.75, date: "22 May 2025" }, 50 | { title: "Fitness Class", cost: 15.0, date: "9 Jan 2025" }, 51 | { title: "Bagel Shop", cost: 5.49, date: "27 Jun 2024" }, 52 | { title: "Garden Center", cost: 47.85, date: "13 Feb 2025" }, 53 | { title: "Parking Ticket", cost: 35.0, date: "30 Sep 2024" }, 54 | { title: "Furniture Store", cost: 99.99, date: "18 Apr 2025" }, 55 | { title: "Thrift Shop", cost: 12.75, date: "6 Nov 2024" }, 56 | { title: "Smoothie Bar", cost: 7.25, date: "23 Mar 2025" }, 57 | { title: "Hotel Booking", cost: 89.0, date: "10 Aug 2024" }, 58 | { title: "Tire Shop", cost: 76.5, date: "28 Jan 2025" }, 59 | { title: "Museum Admission", cost: 18.0, date: "15 Jun 2025" }, 60 | { title: "Bookstore Cafe", cost: 9.5, date: "1 Dec 2024" }, 61 | { title: "Fishing Supplies", cost: 42.35, date: "19 Apr 2025" }, 62 | { title: "Phone Accessories", cost: 24.99, date: "7 Sep 2024" }, 63 | { title: "Yoga Studio", cost: 22.0, date: "24 Feb 2025" }, 64 | { title: "Cooking Class", cost: 65.0, date: "11 Jul 2025" }, 65 | { title: "Vitamin Shop", cost: 33.45, date: "29 Dec 2024" }, 66 | { title: "Sports Event", cost: 55.0, date: "17 Mar 2025" }, 67 | { title: "Donut Shop", cost: 6.99, date: "4 Aug 2024" }, 68 | { title: "Camera Store", cost: 87.5, date: "22 Jan 2025" }, 69 | { title: "Luggage Shop", cost: 79.95, date: "9 Jun 2025" }, 70 | { title: "Cake Bakery", cost: 28.5, date: "26 Nov 2024" }, 71 | { title: "Spa Treatment", cost: 85.0, date: "14 Apr 2025" }, 72 | { title: "Wine Shop", cost: 23.99, date: "2 Sep 2024" }, 73 | { title: "Locksmith", cost: 45.0, date: "20 Feb 2025" }, 74 | { title: "Pottery Studio", cost: 39.5, date: "8 Jul 2025" }, 75 | { title: "Arcade", cost: 15.75, date: "25 Dec 2024" }, 76 | { title: "Aquarium Visit", cost: 29.5, date: "13 Mar 2025" }, 77 | { title: "Watch Repair", cost: 55.0, date: "30 Aug 2024" }, 78 | { title: "Camping Gear", cost: 67.25, date: "18 Jan 2025" }, 79 | { title: "Cosmetics Store", cost: 32.99, date: "5 Jun 2025" }, 80 | { title: "Cheese Shop", cost: 18.45, date: "23 Oct 2024" }, 81 | { title: "Photo Printing", cost: 12.99, date: "10 Apr 2025" }, 82 | { title: "Hiking Tour", cost: 45.0, date: "27 Sep 2024" }, 83 | { title: "Vinyl Records", cost: 27.5, date: "15 Feb 2025" }, 84 | { title: "Candy Store", cost: 8.75, date: "3 Jul 2025" }, 85 | { title: "Boat Rental", cost: 75.0, date: "21 Nov 2024" }, 86 | { title: "Ski Lift Ticket", cost: 85.0, date: "8 Mar 2025" }, 87 | { title: "Tailoring Service", cost: 22.5, date: "26 Aug 2024" }, 88 | { title: "Karaoke Bar", cost: 35.0, date: "13 Jan 2025" }, 89 | { title: "Plant Nursery", cost: 29.99, date: "2 May 2025" }, 90 | { title: "Driving Range", cost: 18.5, date: "19 Oct 2024" }, 91 | { title: "Sunglasses Kiosk", cost: 24.95, date: "7 Apr 2025" }, 92 | { title: "Escape Room", cost: 32.0, date: "24 Sep 2024" }, 93 | { title: "Pottery Barn", cost: 59.5, date: "12 Feb 2025" }, 94 | { title: "Candle Shop", cost: 17.25, date: "31 Jul 2025" }, 95 | { title: "Surf Shop", cost: 49.99, date: "17 Dec 2024" }, 96 | { title: "Gourmet Food Store", cost: 38.75, date: "5 May 2025" }, 97 | { title: "Bike Rental", cost: 25.0, date: "22 Oct 2024" }, 98 | { title: "Mini Golf", cost: 12.5, date: "9 Mar 2025" }, 99 | { title: "Antique Store", cost: 65.0, date: "27 Aug 2024" }, 100 | ]; 101 | -------------------------------------------------------------------------------- /src/app/animated-tabs/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GrainyBackground } from "@/components/grainy-background"; 4 | import { 5 | AnimatePresence, 6 | motion, 7 | MotionConfig, 8 | Variants, 9 | type Transition, 10 | } from "framer-motion"; 11 | import React, { useRef, useState } from "react"; 12 | 13 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 14 | 15 | const Context = React.createContext<{ 16 | status: string; 17 | setStatus: React.Dispatch>; 18 | }>({ status: "", setStatus: () => null }); 19 | 20 | const tabs = [ 21 | { id: "home", label: "Home" }, 22 | { id: "features", label: "Features" }, 23 | { id: "about", label: "About" }, 24 | { id: "contact", label: "Contact" }, 25 | ]; 26 | 27 | function InnerContent() { 28 | const [activeTab, setActiveTab] = useState(tabs[0]?.id ?? ""); 29 | const previousTabRef = useRef(activeTab); 30 | 31 | const handleTabChange = (tabId: string) => { 32 | previousTabRef.current = activeTab; 33 | setActiveTab(tabId); 34 | }; 35 | 36 | const getTabIndex = (id: string) => tabs.findIndex((tab) => tab.id === id); 37 | const previousTabIndex = getTabIndex(previousTabRef.current); 38 | const currentTabIndex = getTabIndex(activeTab); 39 | const indexDifference = Math.abs(currentTabIndex - previousTabIndex); 40 | 41 | const activeTabVariants: Variants = { 42 | transitioning: (indexDifference: number) => ({ 43 | filter: `blur(${indexDifference * 2}px)`, 44 | }), 45 | idle: { filter: "blur(0px)" }, 46 | }; 47 | 48 | return ( 49 |
50 | {tabs.map((tab) => ( 51 | handleTabChange(tab.id)} 54 | className="relative px-3 py-1.5 font-mono text-sm font-medium text-[#1f1f1f] outline-2 outline-[#1f1f1f]/50 transition-colors focus-visible:outline" 55 | > 56 | 57 | {activeTab === tab.id && ( 58 | 67 | )} 68 | 69 | {tab.label} 70 | 71 | ))} 72 |
73 | ); 74 | } 75 | 76 | export default function HomePage() { 77 | const [status, setStatus] = React.useState("idle"); 78 | 79 | React.useEffect(() => { 80 | function handleEscape(e: KeyboardEvent) { 81 | if (e.key === "Escape") { 82 | setStatus("idle"); 83 | } 84 | } 85 | window.addEventListener("keydown", handleEscape); 86 | return () => window.removeEventListener("keydown", handleEscape); 87 | }, [setStatus]); 88 | 89 | return ( 90 | 91 | 92 | 97 | 98 | 99 | 100 | 101 | ); 102 | } 103 | -------------------------------------------------------------------------------- /src/app/audiobooks/all-the-light.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/audiobooks/all-the-light.jpg -------------------------------------------------------------------------------- /src/app/audiobooks/dune.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/audiobooks/dune.jpg -------------------------------------------------------------------------------- /src/app/audiobooks/farewell.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/audiobooks/farewell.jpg -------------------------------------------------------------------------------- /src/app/audiobooks/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | motion, 5 | MotionConfig, 6 | type Transition, 7 | useVelocity, 8 | useMotionValue, 9 | useMotionValueEvent, 10 | useTransform, 11 | useSpring, 12 | } from "framer-motion"; 13 | import { Play } from "lucide-react"; 14 | import Image from "next/image"; 15 | import React from "react"; 16 | import imageFarewell from "./farewell.jpg"; 17 | import imageDune from "./dune.jpg"; 18 | import imageAllTheLight from "./all-the-light.jpg"; 19 | 20 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 21 | 22 | const Context = React.createContext<{ 23 | status: string; 24 | setStatus: React.Dispatch>; 25 | }>({ status: "", setStatus: () => null }); 26 | 27 | function InnerContent() { 28 | const ctx = React.useContext(Context); 29 | 30 | const x = useMotionValue(0); 31 | const xSmooth = useSpring(x, { damping: 10, stiffness: 100 }); 32 | const xVelocity = useVelocity(xSmooth); 33 | const rotate = useTransform( 34 | xVelocity, 35 | [-3500, 0, 3500], 36 | ["15deg", "0deg", "-15deg"], 37 | { 38 | clamp: false, 39 | }, 40 | ); 41 | 42 | useMotionValueEvent(xVelocity, "change", (latestVelocity) => { 43 | console.log("Velocity", latestVelocity); 44 | }); 45 | 46 | return ( 47 |
48 |

Audiobooks

49 | 61 |
  • 62 | 66 | Farewell 71 |
    72 |
    73 | 74 |
    75 |
    76 |

    A Farewell To Arms

    77 |

    Ernest Hemingway

    78 |
    79 |
    80 | 81 |
    82 |
    83 |
    84 |

    85 | 1h 24m 86 |

    87 |
    88 |
    89 |
  • 90 |
  • 91 | 95 | Dune 100 |
    101 |
    102 | 103 |
    104 |
    105 |

    Dune

    106 |

    Frank Herbert

    107 |
    108 |
    109 | 110 |
    111 |
    112 |
    113 |

    114 | 3h 6m 115 |

    116 |
    117 |
    118 |
  • 119 |
  • 120 | 124 | All the light we cannot see 129 |
    130 |
    131 | 132 |
    133 |
    134 |

    135 | All the Light We Cannot See 136 |

    137 |

    Anthony Doerr

    138 |
    139 |
    140 | 141 |
    142 |
    143 |
    144 |

    145 | 3h 51m 146 |

    147 |
    148 |
    149 |
  • 150 |
    151 |
    152 | ); 153 | } 154 | 155 | export default function HomePage() { 156 | const [status, setStatus] = React.useState("idle"); 157 | 158 | React.useEffect(() => { 159 | function handleEscape(e: KeyboardEvent) { 160 | if (e.key === "Escape") { 161 | setStatus("idle"); 162 | } 163 | } 164 | window.addEventListener("keydown", handleEscape); 165 | return () => window.removeEventListener("keydown", handleEscape); 166 | }, [setStatus]); 167 | 168 | return ( 169 | 170 | 171 |
    172 | 173 |
    174 |
    175 |
    176 | ); 177 | } 178 | -------------------------------------------------------------------------------- /src/app/bird/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AnimatePresence, motion, MotionConfig } from "framer-motion"; 4 | import imageBird from "@/assets/bird.jpg"; 5 | import Image from "next/image"; 6 | import React from "react"; 7 | import { X } from "lucide-react"; 8 | 9 | export default function HomePage() { 10 | const [open, setOpen] = React.useState(false); 11 | const [closeHovered, setCloseHovered] = React.useState(false); 12 | 13 | const containerVariants = { 14 | hover: (custom: { open: boolean }) => ({ 15 | scale: !custom.open ? 1.05 : 1, 16 | }), 17 | }; 18 | 19 | const blurVariants = { 20 | hover: (custom: { open: boolean }) => ({ 21 | height: !custom.open ? "60%" : "33%", 22 | }), 23 | }; 24 | 25 | return ( 26 | 27 |
    28 | 29 | {open && ( 30 | 36 | 41 |

    42 | Close 43 |

    44 |
    45 | setCloseHovered(true)} 47 | onHoverEnd={() => setCloseHovered(false)} 48 | onClick={() => { 49 | setOpen(false); 50 | setCloseHovered(false); 51 | }} 52 | > 53 | 54 | 55 |
    56 | )} 57 |
    58 | 59 | setOpen((prevState) => !prevState)} 73 | data-open={open} 74 | className="relative overflow-hidden rounded-[48px] data-[open=false]:shadow-[-16px_-16px_64px_-32px_rgba(56,76,55,1),0_32px_64px_-32px_rgba(53,113,201,0.8),32px_0_64px_-32px_rgba(153,170,4,0.6)]" 75 | > 76 | Bird 81 | 82 | 88 | 89 | 97 | 98 |
    99 |

    {`Bold New Contemporary\nArt Showcase`}

    100 |

    101 | See new media evolve 102 |

    103 |
    104 |
    105 |
    106 |
    107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /src/app/carousel/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | AnimatePresence, 5 | motion, 6 | MotionConfig, 7 | type Transition, 8 | } from "framer-motion"; 9 | import Image from "next/image"; 10 | import React from "react"; 11 | import imageBarn from "@/assets/barn.jpg"; 12 | import imageOrlando from "@/assets/orlando.jpg"; 13 | import imageSnow from "@/assets/snow-board.jpg"; 14 | import imageField from "@/assets/field.jpg"; 15 | import { ChevronLeft, ChevronRight } from "lucide-react"; 16 | import { cn } from "@/lib/utils"; 17 | 18 | const Context = React.createContext<{ 19 | index: number; 20 | setIndex: React.Dispatch>; 21 | status: string; 22 | setStatus: React.Dispatch>; 23 | }>({ index: 0, setIndex: () => null, status: "", setStatus: () => null }); 24 | 25 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 26 | 27 | const locationData = [ 28 | { 29 | title: "Orlando Beach", 30 | city: "Orlando", 31 | state: "FL", 32 | image: imageOrlando, 33 | }, 34 | { 35 | title: "Mount Elbert", 36 | city: "Leadville", 37 | state: "CO", 38 | image: imageSnow, 39 | }, 40 | { 41 | title: "Mount Rainier", 42 | city: "Paradise", 43 | state: "WA", 44 | image: imageBarn, 45 | }, 46 | { 47 | title: "Galt Ranch", 48 | city: "White Sulphur Springs", 49 | state: "MT", 50 | image: imageField, 51 | }, 52 | ]; 53 | 54 | function NavigationIndicator() { 55 | const ctx = React.useContext(Context); 56 | 57 | return ( 58 |
    59 |
    67 |
    75 |
    83 |
    91 |
    92 | ); 93 | } 94 | 95 | function PreviousButton() { 96 | const ctx = React.useContext(Context); 97 | 98 | function handlePreviousClick() { 99 | if (ctx.index <= 0) return; 100 | ctx.setIndex((prev) => prev - 1); 101 | } 102 | 103 | return ( 104 | 116 | ); 117 | } 118 | 119 | function NextButton() { 120 | const ctx = React.useContext(Context); 121 | 122 | function handleNextClick() { 123 | if (ctx.index >= 3) return; 124 | ctx.setIndex((prev) => prev + 1); 125 | } 126 | 127 | return ( 128 | 140 | ); 141 | } 142 | 143 | function InnerContent() { 144 | const ctx = React.useContext(Context); 145 | const location = locationData[ctx.index]; 146 | const isPressingNext = ctx.status === "pressing-next"; 147 | const isPressingPrevious = ctx.status === "pressing-previous"; 148 | 149 | return ( 150 |
    151 | 152 | 177 | {location.title} 182 | 183 | 184 | 185 |
    186 |
    187 | 188 | 205 |

    206 | {location.title} 207 |
    208 | {location.city} 209 |
    210 | {location.state} 211 |

    212 |
    213 | 214 | 215 | 216 | 217 | 218 | 219 |
    220 | ); 221 | } 222 | 223 | export default function HomePage() { 224 | const [index, setIndex] = React.useState(2); 225 | const [status, setStatus] = React.useState("idle"); 226 | 227 | React.useEffect(() => { 228 | function handleEscape(e: KeyboardEvent) { 229 | if (e.key === "Escape") { 230 | setStatus("idle"); 231 | } 232 | } 233 | window.addEventListener("keydown", handleEscape); 234 | return () => window.removeEventListener("keydown", handleEscape); 235 | }, [setStatus]); 236 | 237 | return ( 238 | 239 | 240 |
    241 | 242 |
    243 |
    244 |
    245 | ); 246 | } 247 | -------------------------------------------------------------------------------- /src/app/checkout/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AnimatePresence, motion, MotionConfig } from "framer-motion"; 4 | import { ShoppingCart } from "lucide-react"; 5 | import React from "react"; 6 | 7 | export default function AddToCartPage() { 8 | const [hovered, setHovered] = React.useState(false); 9 | 10 | return ( 11 | 12 |
    13 |
    14 | 19 | 3 Items in cart 20 | 21 | 43 |
    44 |
    45 |
    46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/app/create-new/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | AnimatePresence, 5 | motion, 6 | MotionConfig, 7 | type Transition, 8 | } from "framer-motion"; 9 | import { 10 | BellRing, 11 | ClipboardList, 12 | Flag, 13 | Folder, 14 | Plus, 15 | StickyNote, 16 | Trophy, 17 | X, 18 | } from "lucide-react"; 19 | import React from "react"; 20 | 21 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 22 | 23 | const Context = React.createContext<{ 24 | status: string; 25 | setStatus: React.Dispatch>; 26 | }>({ status: "", setStatus: () => null }); 27 | 28 | function InnerContent() { 29 | const ctx = React.useContext(Context); 30 | const isOpen = ctx.status === "open"; 31 | const isHovered = ctx.status === "hovered"; 32 | 33 | return ( 34 | 35 | {isOpen || isHovered ? ( 36 | 41 |
    42 | 43 | Create New 44 | 45 |
    46 | 47 | {isHovered && ( 48 | 54 | Close 55 | 56 | )} 57 | 58 | ctx.setStatus("idle")} 61 | initial={{ opacity: 0, x: -20, y: 10 }} 62 | animate={{ opacity: 1, x: 0, y: 0 }} 63 | exit={{ opacity: 0, x: -20, y: 10 }} 64 | transition={{ ...transition, delay: 0.15 }} 65 | whileTap={{ 66 | scale: 0.9, 67 | transition: { ...transition, duration: 0.2 }, 68 | }} 69 | onHoverStart={() => ctx.setStatus("hovered")} 70 | onHoverEnd={() => ctx.setStatus("open")} 71 | className="size-6 flex items-center justify-center rounded-full bg-[#b8b6af]" 72 | > 73 | 77 | 78 |
    79 |
    80 | 90 |
    91 | 100 | 109 | 118 |
    119 |
    120 | 129 | 138 | 147 |
    148 |
    149 |
    150 | ) : ( 151 | ctx.setStatus("open")} 154 | whileTap={{ scale: 0.95 }} 155 | style={{ borderRadius: 22 }} 156 | className="flex items-center gap-1.5 bg-[#fafafa] px-5 py-2.5 tracking-tight text-[#202020] shadow-mixed ring-1 ring-black/[8%] transition-[box-shadow] active:shadow-none" 157 | > 158 | 165 | 166 | 167 | 168 | Create New 169 | 170 | 171 | )} 172 |
    173 | ); 174 | } 175 | 176 | export default function HomePage() { 177 | const [status, setStatus] = React.useState("idle"); 178 | 179 | React.useEffect(() => { 180 | function handleEscape(e: KeyboardEvent) { 181 | if (e.key === "Escape") { 182 | setStatus("idle"); 183 | } 184 | } 185 | window.addEventListener("keydown", handleEscape); 186 | return () => window.removeEventListener("keydown", handleEscape); 187 | }, [setStatus]); 188 | 189 | return ( 190 | 191 | 192 |
    193 | 194 |
    195 |
    196 |
    197 | ); 198 | } 199 | -------------------------------------------------------------------------------- /src/app/dynamic-tabs/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GrainyBackground } from "@/components/grainy-background"; 4 | import { cn } from "@/lib/utils"; 5 | import { 6 | AnimatePresence, 7 | motion, 8 | MotionConfig, 9 | type Transition, 10 | } from "framer-motion"; 11 | import React from "react"; 12 | 13 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 14 | 15 | const Context = React.createContext<{ 16 | status: string; 17 | setStatus: React.Dispatch>; 18 | bgStatus: string; 19 | setBgStatus: React.Dispatch>; 20 | }>({ 21 | status: "", 22 | setStatus: () => null, 23 | bgStatus: "", 24 | setBgStatus: () => null, 25 | }); 26 | 27 | function InnerContent() { 28 | const ctx = React.useContext(Context); 29 | 30 | return ( 31 |
    32 | ctx.setBgStatus("system")} 34 | animate={ 35 | { 36 | // color: ctx.bgStatus === "system" ? "#ffffff" : "#000000", 37 | } 38 | } 39 | className={cn("relative h-12 w-[175px] rounded-full")} 40 | > 41 | {ctx.bgStatus === "system" && ( 42 | 47 | )} 48 | 49 | System 50 | 51 | 52 | 53 |
    54 |
    55 | 56 | {ctx.bgStatus === "system" ? ( 57 | ctx.setBgStatus("manual")} 59 | initial={{ opacity: 0, color: "#ffffff" }} 60 | animate={{ opacity: 1, color: "#000000" }} 61 | exit={{ opacity: 0, color: "#ffffff" }} 62 | className={cn("absolute inset-0")} 63 | > 64 | 65 | 72 | Manual 73 | 74 |
    75 | 79 | Light 80 | 81 | / 82 | 86 | Dark 87 | 88 |
    89 |
    90 |
    91 | ) : ( 92 | <> 93 | 98 | 104 | ctx.setStatus("light")} 107 | className="relative h-full w-full rounded-full text-white" 108 | > 109 | {ctx.status === "light" && ( 110 | 115 | )} 116 | 120 | Light 121 | 122 | 123 | ctx.setStatus("dark")} 126 | className="relative h-full w-full rounded-full text-white" 127 | > 128 | {ctx.status === "dark" && ( 129 | 134 | )} 135 | 139 | Dark 140 | 141 | 142 | 143 | 144 | )} 145 |
    146 |
    147 | 148 | 149 | {ctx.bgStatus === "manual" && ( 150 | 157 | Manual 158 | 159 | )} 160 | 161 |
    162 |
    163 | ); 164 | } 165 | 166 | export default function HomePage() { 167 | const [status, setStatus] = React.useState("light"); 168 | const [bgStatus, setBgStatus] = React.useState("system"); 169 | 170 | React.useEffect(() => { 171 | function handleEscape(e: KeyboardEvent) { 172 | if (e.key === "Escape") { 173 | setStatus("light"); 174 | } 175 | } 176 | window.addEventListener("keydown", handleEscape); 177 | return () => window.removeEventListener("keydown", handleEscape); 178 | }, [setStatus]); 179 | 180 | return ( 181 | 182 | 183 | 191 | 192 | 193 | 194 | 195 | ); 196 | } 197 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/flashlight/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion, MotionConfig, type Transition } from "framer-motion"; 4 | import { Flashlight } from "lucide-react"; 5 | import React from "react"; 6 | 7 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 8 | 9 | const Context = React.createContext<{ 10 | status: string; 11 | setStatus: React.Dispatch>; 12 | }>({ status: "", setStatus: () => null }); 13 | 14 | function InnerContent() { 15 | const ctx = React.useContext(Context); 16 | const isNarrow = ctx.status === "narrow"; 17 | 18 | return ( 19 |
    20 | 25 | 46 | 68 | 83 | 100 | 101 |
    ctx.setStatus("narrow")} 103 | className="size-16 absolute -bottom-20 left-1/2 -translate-x-1/2 bg-white bg-[radial-gradient(circle_at_50%_40%,#f5f1ff_10%,#dcd8ff_40%)] bg-no-repeat [mask:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBjbGFzcz0ibHVjaWRlIGx1Y2lkZS1mbGFzaGxpZ2h0Ij48cGF0aCBkPSJNMTggNmMwIDItMiAyLTIgNHYxMGEyIDIgMCAwIDEtMiAyaC00YTIgMiAwIDAgMS0yLTJWMTBjMC0yLTItMi0yLTRWMmgxMnoiLz48bGluZSB4MT0iNiIgeDI9IjE4IiB5MT0iNiIgeTI9IjYiLz48bGluZSB4MT0iMTIiIHgyPSIxMiIgeTE9IjEyIiB5Mj0iMTIiLz48L3N2Zz4=)] [maskPosition:center] [maskRepeat:no-repeat] [maskSize:100%_100%]" 104 | /> 105 |
    106 | ); 107 | } 108 | 109 | export default function HomePage() { 110 | const [status, setStatus] = React.useState("idle"); 111 | 112 | React.useEffect(() => { 113 | function handleEscape(e: KeyboardEvent) { 114 | if (e.key === "Escape") { 115 | setStatus("idle"); 116 | } 117 | } 118 | window.addEventListener("keydown", handleEscape); 119 | return () => window.removeEventListener("keydown", handleEscape); 120 | }, [setStatus]); 121 | 122 | return ( 123 | 124 | 125 |
    126 | 127 |
    128 |
    129 |
    130 | ); 131 | } 132 | -------------------------------------------------------------------------------- /src/app/folder/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion, MotionConfig, type Transition } from "framer-motion"; 4 | import React from "react"; 5 | 6 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 7 | 8 | const Context = React.createContext<{ 9 | status: string; 10 | setStatus: React.Dispatch>; 11 | }>({ status: "", setStatus: () => null }); 12 | 13 | function InnerContent() { 14 | const ctx = React.useContext(Context); 15 | const isOpen = ctx.status === "open"; 16 | 17 | return ( 18 |
    { 20 | if (isOpen) { 21 | ctx.setStatus("idle"); 22 | return; 23 | } 24 | ctx.setStatus("open"); 25 | }} 26 | className="relative aspect-[1.3] h-64" 27 | > 28 | 44 | 60 | 80 | 81 | 82 | 98 |
    99 | 104 | 109 |
    110 |
    111 |
    112 | ); 113 | } 114 | 115 | export default function HomePage() { 116 | const [status, setStatus] = React.useState("idle"); 117 | 118 | React.useEffect(() => { 119 | function handleEscape(e: KeyboardEvent) { 120 | if (e.key === "Escape") { 121 | setStatus("idle"); 122 | } 123 | } 124 | window.addEventListener("keydown", handleEscape); 125 | return () => window.removeEventListener("keydown", handleEscape); 126 | }, [setStatus]); 127 | 128 | return ( 129 | 130 | 131 |
    132 | 133 |
    134 |
    135 |
    136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 98%; 8 | --foreground: 240 10% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 240 10% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 240 10% 3.9%; 13 | --primary: 240 5.9% 10%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 240 4.8% 95.9%; 16 | --secondary-foreground: 240 5.9% 10%; 17 | --muted: 240 4.8% 95.9%; 18 | --muted-foreground: 240 3.8% 46.1%; 19 | --accent: 240 4.8% 95.9%; 20 | --accent-foreground: 240 5.9% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 240 5.9% 90%; 24 | --input: 240 5.9% 90%; 25 | --ring: 240 10% 3.9%; 26 | --radius: 0.5rem; 27 | --chart-1: 12 76% 61%; 28 | --chart-2: 173 58% 39%; 29 | --chart-3: 197 37% 24%; 30 | --chart-4: 43 74% 66%; 31 | --chart-5: 27 87% 67%; 32 | --shadow-mixed: 0 1px 1px -0.5px rgba(0, 0, 0, 0.04), 33 | 0 3px 3px -1.5px rgba(0, 0, 0, 0.04), 0 6px 6px -3px rgba(0, 0, 0, 0.04), 34 | 0 12px 12px -6px rgba(0, 0, 0, 0.04), 35 | 0 24px 24px -12px rgba(0, 0, 0, 0.04); 36 | } 37 | 38 | .dark { 39 | --background: 240 10% 3.9%; 40 | --foreground: 0 0% 98%; 41 | --card: 240 10% 3.9%; 42 | --card-foreground: 0 0% 98%; 43 | --popover: 240 10% 3.9%; 44 | --popover-foreground: 0 0% 98%; 45 | --primary: 0 0% 98%; 46 | --primary-foreground: 240 5.9% 10%; 47 | --secondary: 240 3.7% 15.9%; 48 | --secondary-foreground: 0 0% 98%; 49 | --muted: 240 3.7% 15.9%; 50 | --muted-foreground: 240 5% 64.9%; 51 | --accent: 240 3.7% 15.9%; 52 | --accent-foreground: 0 0% 98%; 53 | --destructive: 0 62.8% 30.6%; 54 | --destructive-foreground: 0 0% 98%; 55 | --border: 240 3.7% 15.9%; 56 | --input: 240 3.7% 15.9%; 57 | --ring: 240 4.9% 83.9%; 58 | --chart-1: 220 70% 50%; 59 | --chart-2: 160 60% 45%; 60 | --chart-3: 30 80% 55%; 61 | --chart-4: 280 65% 60%; 62 | --chart-5: 340 75% 55%; 63 | } 64 | } 65 | 66 | @layer base { 67 | * { 68 | @apply border-border; 69 | } 70 | 71 | body { 72 | @apply bg-background text-foreground; 73 | } 74 | } 75 | 76 | @layer utilities { 77 | 78 | /* Hide scrollbar for Chrome, Safari and Opera */ 79 | .no-scrollbar::-webkit-scrollbar { 80 | display: none; 81 | } 82 | 83 | /* Hide scrollbar for IE, Edge and Firefox */ 84 | .no-scrollbar { 85 | -ms-overflow-style: none; 86 | /* IE and Edge */ 87 | scrollbar-width: none; 88 | /* Firefox */ 89 | } 90 | } 91 | 92 | [vaul-drawer] { 93 | transition: transform 2.2s cubic-bezier(0.165, 0.84, 0.44, 1); 94 | } 95 | 96 | [vaul-overlay] { 97 | transition: opacity 2.2s cubic-bezier(0.165, 0.84, 0.44, 1); 98 | } 99 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { GeistSans } from "geist/font/sans"; 3 | import { GeistMono } from "geist/font/mono"; 4 | import { EB_Garamond, Dancing_Script, JetBrains_Mono } from "next/font/google"; 5 | import "./globals.css"; 6 | 7 | export const metadata: Metadata = { 8 | title: "Lab | Vaun Blu", 9 | }; 10 | 11 | const EBGaramond = EB_Garamond({ 12 | subsets: ["latin"], 13 | variable: "--font-eb-garamond", 14 | display: "swap", 15 | }); 16 | 17 | const Mono = JetBrains_Mono({ 18 | subsets: ["latin"], 19 | variable: "--font-mono", 20 | display: "swap", 21 | }); 22 | 23 | const DancingScript = Dancing_Script({ 24 | subsets: ["latin"], 25 | variable: "--font-dancing-script", 26 | display: "swap", 27 | }); 28 | 29 | export default function RootLayout({ 30 | children, 31 | }: Readonly<{ 32 | children: React.ReactNode; 33 | }>) { 34 | return ( 35 | 36 | 39 | {children} 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /src/app/love-folder/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion, MotionConfig, type Transition } from "framer-motion"; 4 | import React from "react"; 5 | import Image from "next/image"; 6 | import imagePlayfulInca from "@/assets/playful-inca.jpg"; 7 | import imageStoicInca from "@/assets/stoic-inca.jpg"; 8 | 9 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 10 | 11 | const Context = React.createContext<{ 12 | status: string; 13 | setStatus: React.Dispatch>; 14 | }>({ status: "", setStatus: () => null }); 15 | 16 | function InnerContent() { 17 | const ctx = React.useContext(Context); 18 | const isOpen = ctx.status === "open"; 19 | 20 | return ( 21 |
    { 23 | if (isOpen) { 24 | ctx.setStatus("idle"); 25 | return; 26 | } 27 | ctx.setStatus("open"); 28 | }} 29 | className="relative h-64 w-[332px]" 30 | > 31 | 47 | 48 | 52 | 53 | 54 | 55 | 68 |
    69 | 70 | 83 |
    84 | 85 | 86 | 91 | Tailwind 96 | 97 | 102 | Framer Motion 107 | 108 | 109 | 125 |
    126 | 131 | 136 |
    137 |
    138 |
    139 | ); 140 | } 141 | 142 | export default function HomePage() { 143 | const [status, setStatus] = React.useState("idle"); 144 | 145 | React.useEffect(() => { 146 | function handleEscape(e: KeyboardEvent) { 147 | if (e.key === "Escape") { 148 | setStatus("idle"); 149 | } 150 | } 151 | window.addEventListener("keydown", handleEscape); 152 | return () => window.removeEventListener("keydown", handleEscape); 153 | }, [setStatus]); 154 | 155 | return ( 156 | 157 | 158 |
    159 | 160 |
    161 |
    162 |
    163 | ); 164 | } 165 | -------------------------------------------------------------------------------- /src/app/love-this/her.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/love-this/her.jpg -------------------------------------------------------------------------------- /src/app/love-this/interstellar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/love-this/interstellar.jpg -------------------------------------------------------------------------------- /src/app/love-this/love-this/styles.module.css: -------------------------------------------------------------------------------- 1 | .border .child { 2 | filter: drop-shadow(2.5px 0 0 hsl(var(--background))) drop-shadow(-2.5px 0 0 hsl(var(--background))) drop-shadow(0 2.5px 0 hsl(var(--background))) drop-shadow(0 -2.5px 0 hsl(var(--background))); 3 | } 4 | 5 | .border:hover .child { 6 | filter: drop-shadow(2.5px 0 0 hsl(var(--muted))) drop-shadow(-2.5px 0 0 hsl(var(--muted))) drop-shadow(0 2.5px 0 hsl(var(--muted))) drop-shadow(0 -2.5px 0 hsl(var(--muted))); 7 | } 8 | -------------------------------------------------------------------------------- /src/app/love-this/murph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/love-this/murph.jpg -------------------------------------------------------------------------------- /src/app/love-this/page.tsx: -------------------------------------------------------------------------------- 1 | import { LoveThis } from "./love-this"; 2 | import imageInterstellar from "./interstellar.jpg"; 3 | import imageHer from "./her.jpg"; 4 | import imageMurph from "./murph.jpg"; 5 | import imageScarlett from "./scarlett.jpg"; 6 | import Image from "next/image"; 7 | 8 | export default function Home() { 9 | return ( 10 |
    11 | Interstellar cover 17 | 18 |
    19 |

    Her

    20 |
    21 | Scarlett 26 |

    Samantha (Scarlett)

    27 |

    @scarlettjo

    28 |
    29 |

    December 18, 2013

    30 |
    31 | 32 |
    33 |

    34 | Theodore is a lonely man in the final stages of his divorce. When 35 | he's not working as a letter writer, his down time is spent 36 | playing video games and occasionally hanging out with friends. He 37 | decides to purchase the new OS1, which is advertised as the 38 | world's first artificially intelligent operating system, 39 | "It's not just an operating system, it's a 40 | consciousness," the ad states. Theodore quickly finds himself 41 | drawn in with Samantha, the voice behind his OS1. 42 |

    43 |

    44 | As they start spending time together they grow closer and closer and 45 | eventually find themselves in love. Having fallen in love with his OS, 46 | Theodore finds himself dealing with feelings of both great joy and 47 | doubt. As an OS, Samantha has powerful intelligence that she uses to 48 | help Theodore in ways others hadn't, but how does she help him 49 | deal with his inner conflict of being in love with an OS? 50 |

    51 |

    52 | A great film about loneliness. Splendid performance of Joaquin 53 | Phoenix. And pure poetry. Admirable poem about isolation, need of 54 | other, social surogate and , off course, freedom. Its basic virtue - 55 | the proposed questions creating perfect atmosphere , becoming inspired 56 | challenges to discover new perspectives. 57 |

    58 |
    59 | 60 | 61 |
    62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /src/app/love-this/scarlett.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/love-this/scarlett.jpg -------------------------------------------------------------------------------- /src/app/magic-button/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { AnimatePresence, motion, MotionConfig } from "framer-motion"; 5 | import { Sparkle } from "lucide-react"; 6 | import React from "react"; 7 | 8 | export default function HomePage() { 9 | const [loading, setLoading] = React.useState(false); 10 | 11 | React.useEffect(() => { 12 | if (loading) { 13 | const timeout = setTimeout(() => setLoading(false), 2000); 14 | return () => clearTimeout(timeout); 15 | } 16 | }, [loading, setLoading]); 17 | 18 | return ( 19 | 20 |
    21 | 100 |
    101 |
    102 | ); 103 | } 104 | -------------------------------------------------------------------------------- /src/app/neu/styles.module.css: -------------------------------------------------------------------------------- 1 | .toolbar { 2 | background: linear-gradient(145deg, #2b2b2b, #242424); 3 | box-shadow: 4 | 30px 30px 60px rgba(32, 32, 32, 0.5), 5 | -30px -30px 60px rgba(48, 48, 48, 0.4); 6 | } 7 | 8 | .drop-down { 9 | background: linear-gradient(145deg, #2b2b2b, #242424); 10 | box-shadow: 11 | 30px 30px 60px rgba(32, 32, 32, 0.5), 12 | -30px -30px 60px rgba(48, 48, 48, 0.1); 13 | } 14 | 15 | .drop-down-trigger { 16 | background: linear-gradient(145deg, #3c3c3c, #323232); 17 | border: 2px rgba(0, 0, 0, 0); 18 | border-top-style: solid; 19 | border-left-style: solid; 20 | } 21 | 22 | .drop-down-trigger:hover { 23 | background: linear-gradient(145deg, #3c3c3c, #323232); 24 | } 25 | 26 | .drop-down-trigger[data-open="true"] { 27 | border-color: rgba(76, 76, 76, 0.6); 28 | background: linear-gradient(145deg, #3c3c3c, #323232); 29 | box-shadow: 30 | 12px 12px 28px rgba(45, 45, 45, 0.2), 31 | -12px -12px 28px rgba(67, 67, 67, 0.2); 32 | } 33 | 34 | .drop-down-button { 35 | border: 2px rgba(0, 0, 0, 0); 36 | border-top-style: solid; 37 | border-left-style: solid; 38 | } 39 | 40 | .drop-down-button:hover { 41 | background: rgba(60, 60, 60, 0.8); 42 | } 43 | 44 | .drop-down-button:active { 45 | border-color: rgba(76, 76, 76, 0.6); 46 | background: linear-gradient(145deg, #323232, #3c3c3c); 47 | box-shadow: 48 | 0 12px 12px -6px rgba(0, 0, 0, 0.2), 49 | 12px 12px 28px rgba(45, 45, 45, 0.2), 50 | -12px -12px 28px rgba(67, 67, 67, 0.2); 51 | } 52 | 53 | .button { 54 | border: 2px rgba(0, 0, 0, 0); 55 | border-top-style: solid; 56 | border-left-style: solid; 57 | } 58 | 59 | .button:active, 60 | .button:focus { 61 | border-color: rgba(76, 76, 76, 0.6); 62 | background: linear-gradient(145deg, #323232, #3c3c3c); 63 | box-shadow: 64 | 0 12px 12px -6px rgba(0, 0, 0, 0.2), 65 | 12px 12px 28px rgba(45, 45, 45, 0.2), 66 | -12px -12px 28px rgba(67, 67, 67, 0.2); 67 | } 68 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { Sidebar } from "../components/sidebar"; 5 | 6 | function MainContent() { 7 | return ( 8 |
    9 |

    Welcome to the Lab

    10 |

    11 | This is the main page of our UI component laboratory. 12 |

    13 |

    14 | Select an example from the sidebar to explore different UI components 15 | and experiments. 16 |

    17 |
    18 | ); 19 | } 20 | 21 | export default function HomePage() { 22 | return ( 23 |
    24 |
    25 | 26 |
    27 |
    28 | 29 |
    30 |
    31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /src/app/phone-glow/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { 5 | AnimatePresence, 6 | motion, 7 | MotionConfig, 8 | Transition, 9 | } from "framer-motion"; 10 | import React from "react"; 11 | import Image from "next/image"; 12 | import svgPhone from "@/assets/iphone-black.svg"; 13 | 14 | const Context = React.createContext<{ 15 | status: string; 16 | setStatus: React.Dispatch>; 17 | }>({ status: "", setStatus: () => null }); 18 | 19 | const transition: Transition = { 20 | type: "tween", 21 | ease: "easeInOut", 22 | duration: 2, 23 | }; 24 | 25 | function InnerContent() { 26 | const ctx = React.useContext(Context); 27 | const isActive = ctx.status === "active"; 28 | 29 | return ( 30 |
    ctx.setStatus("active")} 32 | className="relative flex h-full flex-col overflow-hidden rounded-[51px] bg-[#000000] py-20" 33 | > 34 | 47 | 48 | 61 | 62 | 75 | 76 | 89 |
    90 | ); 91 | } 92 | 93 | export default function HomePage() { 94 | const [status, setStatus] = React.useState("idle"); 95 | 96 | React.useEffect(() => { 97 | function handleEscape(e: KeyboardEvent) { 98 | if (e.key === "Escape") { 99 | setStatus("idle"); 100 | } 101 | } 102 | window.addEventListener("keydown", handleEscape); 103 | return () => window.removeEventListener("keydown", handleEscape); 104 | }, [setStatus]); 105 | 106 | return ( 107 | 108 | 109 |
    110 |
    115 |
    116 | 117 |
    118 | 119 |
    120 |
    121 |
    122 | 123 | iphone mock 128 |
    129 |
    130 |
    131 |
    132 | ); 133 | } 134 | -------------------------------------------------------------------------------- /src/app/popcorn/mesh-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/popcorn/mesh-1.png -------------------------------------------------------------------------------- /src/app/popcorn/mesh-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/popcorn/mesh-2.png -------------------------------------------------------------------------------- /src/app/popcorn/mesh-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/popcorn/mesh-3.png -------------------------------------------------------------------------------- /src/app/shop-claim/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { 5 | AnimatePresence, 6 | motion, 7 | MotionConfig, 8 | Transition, 9 | useMotionValue, 10 | useTransform, 11 | } from "framer-motion"; 12 | import React from "react"; 13 | import Image from "next/image"; 14 | import svgPhone from "@/assets/iphone-black.svg"; 15 | import imageShopDollar from "./shop-dollar.jpg"; 16 | import imageShop from "./shop.png"; 17 | import imageShopify from "./shopify.png"; 18 | import { ArrowRight, CheckIcon } from "lucide-react"; 19 | 20 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 21 | 22 | const Context = React.createContext<{ 23 | status: string; 24 | setStatus: React.Dispatch>; 25 | }>({ status: "", setStatus: () => null }); 26 | 27 | function InnerContent() { 28 | const ctx = React.useContext(Context); 29 | const isClaimed = ctx.status === "claimed"; 30 | const x = useMotionValue(0); 31 | const opacity = useTransform(x, [0, 130], [1, 0]); 32 | const bgColor = useTransform( 33 | x, 34 | [0, 160], 35 | ["rgb(204 204 204 / 0.3)", "rgb(159 226 191 / 0.4)"], 36 | ); 37 | 38 | React.useEffect(() => { 39 | if (isClaimed) { 40 | const timeout = setTimeout(() => { 41 | x.set(0); 42 | ctx.setStatus("idle"); 43 | }, 2500); 44 | return () => clearTimeout(timeout); 45 | } 46 | }, [isClaimed, ctx.setStatus]); 47 | 48 | return ( 49 |
    50 |
    51 | shop logo 52 |
    53 |
    54 | shop background 59 | 60 | 64 |
    65 |

    66 | Here's $5! 67 |

    68 |

    69 | You can spend it in your Shop 70 |
    71 | app on your favorite brands or products. 72 |

    73 |
    74 |
    75 |
    76 | { 81 | if (info.offset.x >= 196 || info.velocity.x > 500) { 82 | ctx.setStatus("claimed"); 83 | } else { 84 | x.set(0); 85 | } 86 | }} 87 | whileTap={{ scale: 1.05 }} 88 | style={{ x }} 89 | className="relative z-10 grid h-full w-20 place-items-center rounded-full bg-white" 90 | > 91 | 92 | 93 | 97 | Claim $5 Shop Cash 98 | 99 |
    100 |
    101 |

    Powered by

    102 | shopify logo 107 |
    108 |
    109 |
    110 | 111 | 112 | {isClaimed && ( 113 | 119 | 125 | 126 | 127 | 128 | 134 | You claimed $5 in Shop Cash! 135 | 136 | 137 | )} 138 | 139 | 140 | 141 | {isClaimed && ( 142 | <> 143 | 149 | 155 | 156 | )} 157 | 158 |
    159 |
    160 | ); 161 | } 162 | 163 | export default function HomePage() { 164 | const [status, setStatus] = React.useState("idle"); 165 | 166 | React.useEffect(() => { 167 | function handleEscape(e: KeyboardEvent) { 168 | if (e.key === "Escape") { 169 | setStatus("idle"); 170 | } 171 | } 172 | window.addEventListener("keydown", handleEscape); 173 | return () => window.removeEventListener("keydown", handleEscape); 174 | }, [setStatus]); 175 | 176 | return ( 177 | 178 | 179 |
    180 |
    185 |
    186 | 187 |
    188 | 189 | iphone mock 194 |
    195 |
    196 |
    197 |
    198 | ); 199 | } 200 | -------------------------------------------------------------------------------- /src/app/shop-claim/shop-dollar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/shop-claim/shop-dollar.jpg -------------------------------------------------------------------------------- /src/app/shop-claim/shop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/shop-claim/shop.png -------------------------------------------------------------------------------- /src/app/shop-claim/shopify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/shop-claim/shopify.png -------------------------------------------------------------------------------- /src/app/smooth-dropdown/interstellar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/smooth-dropdown/interstellar.jpg -------------------------------------------------------------------------------- /src/app/smooth-dropdown/murph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/smooth-dropdown/murph.jpg -------------------------------------------------------------------------------- /src/app/switch/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GrainyBackground } from "@/components/grainy-background"; 4 | import * as SwitchPrimitive from "@radix-ui/react-switch"; 5 | import { motion, MotionConfig, type Transition } from "framer-motion"; 6 | import React, { useState } from "react"; 7 | 8 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 9 | 10 | const Context = React.createContext<{ 11 | status: string; 12 | setStatus: React.Dispatch>; 13 | }>({ status: "", setStatus: () => null }); 14 | 15 | function InnerContent() { 16 | const ctx = React.useContext(Context); 17 | const [checked, setChecked] = useState(false); 18 | 19 | return ( 20 | 25 | 26 | 40 | 41 | 42 | ); 43 | } 44 | 45 | export default function HomePage() { 46 | const [status, setStatus] = React.useState("idle"); 47 | 48 | React.useEffect(() => { 49 | function handleEscape(e: KeyboardEvent) { 50 | if (e.key === "Escape") { 51 | setStatus("idle"); 52 | } 53 | } 54 | window.addEventListener("keydown", handleEscape); 55 | return () => window.removeEventListener("keydown", handleEscape); 56 | }, [setStatus]); 57 | 58 | return ( 59 | 60 | 61 | 66 | 67 | 68 | 69 | 70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/app/thunder-airdrop/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion, MotionConfig, Transition, useSpring } from "framer-motion"; 4 | import React from "react"; 5 | import imageThunderBg from "@/assets/thunder-bg.png"; 6 | import imageAzura from "@/assets/azura.png"; 7 | import Image from "next/image"; 8 | 9 | // NOTE: TOOK 30 MINS 10 | 11 | const transition: Transition = { 12 | type: "spring", 13 | damping: 80, 14 | stiffness: 100, 15 | }; 16 | 17 | function SpringNumber(props: { value: number }) { 18 | const ref = React.useRef(null); 19 | const springValue = useSpring(0, { 20 | damping: 80, 21 | stiffness: 100, 22 | }); 23 | 24 | React.useEffect(() => { 25 | springValue.set(props.value); 26 | }, [springValue]); 27 | 28 | springValue.on("change", (latest) => { 29 | if (ref.current) { 30 | ref.current.textContent = Intl.NumberFormat("en-US", { 31 | style: "decimal", 32 | minimumSignificantDigits: 1, 33 | }).format(Number(latest.toFixed(0))); 34 | } 35 | }); 36 | 37 | return 0; 38 | } 39 | 40 | export default function HomePage() { 41 | const [status, setStatus] = React.useState("idle"); 42 | 43 | React.useEffect(() => { 44 | function handleEscape(e: KeyboardEvent) { 45 | if (e.key === "Escape") { 46 | setStatus("idle"); 47 | } 48 | } 49 | window.addEventListener("keydown", handleEscape); 50 | return () => window.removeEventListener("keydown", handleEscape); 51 | }, [setStatus]); 52 | 53 | return ( 54 | 55 |
    56 |
    57 |
    58 |

    Airdrop live

    59 |

    60 | for beta users 0% Fees. 61 |

    62 |
    63 | 64 |
    65 |
    66 |
    67 |
    68 |
    69 |
    70 | 71 | 76 | 82 | 83 | 84 | 90 | Thunder 91 | 92 | 93 | 98 | 104 | 105 | 106 | 112 | Competition 113 | 114 | 115 |
    116 |
    117 |

    118 | Thunder 119 |

    120 | Azura 121 |
    122 |
    123 |
    124 | 125 | Thunder BG 130 |
    131 |
    132 |
    133 | 134 | {/* Grain SVG */} 135 | 136 | 137 | 143 | 144 | 145 | 146 | ); 147 | } 148 | -------------------------------------------------------------------------------- /src/app/thunder-speed/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { motion, MotionConfig, Transition, useSpring } from "framer-motion"; 4 | import React from "react"; 5 | import imageThunderBg from "@/assets/thunder-bg.png"; 6 | import imageAzura from "@/assets/azura.png"; 7 | import Image from "next/image"; 8 | 9 | // NOTE: TOOK 90 MINS 10 | 11 | const transition: Transition = { 12 | type: "spring", 13 | damping: 80, 14 | stiffness: 100, 15 | }; 16 | 17 | function SpringNumber(props: { value: number }) { 18 | const ref = React.useRef(null); 19 | const springValue = useSpring(0, { 20 | damping: 80, 21 | stiffness: 100, 22 | }); 23 | 24 | React.useEffect(() => { 25 | springValue.set(props.value); 26 | }, [springValue]); 27 | 28 | springValue.on("change", (latest) => { 29 | if (ref.current) { 30 | ref.current.textContent = Intl.NumberFormat("en-US", { 31 | style: "decimal", 32 | minimumSignificantDigits: 1, 33 | }).format(Number(latest.toFixed(1))); 34 | } 35 | }); 36 | 37 | return 0.0; 38 | } 39 | 40 | export default function HomePage() { 41 | const [status, setStatus] = React.useState("idle"); 42 | 43 | React.useEffect(() => { 44 | function handleEscape(e: KeyboardEvent) { 45 | if (e.key === "Escape") { 46 | setStatus("idle"); 47 | } 48 | } 49 | window.addEventListener("keydown", handleEscape); 50 | return () => window.removeEventListener("keydown", handleEscape); 51 | }, [setStatus]); 52 | 53 | return ( 54 | 55 |
    56 |
    57 |
    58 |

    Meet Thunder:

    59 |

    60 | The New, Fastest Way to Trade On-Chain. 61 |

    62 |
    63 | 64 |
    65 |
    66 |
    67 | 73 | 74 | s 75 | 76 | 81 | 87 | Thunder 88 | 89 | 90 |
    91 |
    92 |

    93 | Thunder 94 |

    95 |
    96 |
    97 |
    98 |
    99 |
    100 |
    101 |
    102 | 108 | 109 | s 110 | 111 | 116 | 122 | Competition 123 | 124 | 125 |
    126 |
    127 | Azura 128 |
    129 |
    130 |
    131 | 132 | Thunder BG 137 |
    138 |
    139 |
    140 | 141 | {/* Grain SVG */} 142 | 143 | 144 | 150 | 151 | 152 | 153 | ); 154 | } 155 | -------------------------------------------------------------------------------- /src/app/timer/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GrainyBackground } from "@/components/grainy-background"; 4 | import { cn } from "@/lib/utils"; 5 | import { 6 | AnimatePresence, 7 | motion, 8 | MotionConfig, 9 | Transition, 10 | } from "framer-motion"; 11 | import React, { useEffect, useRef, useState } from "react"; 12 | import Image from "next/image"; 13 | import svgPhone from "@/assets/iphone-black.svg"; 14 | import NumberFlow from "@number-flow/react"; 15 | import { Button } from "@/components/ui/button"; 16 | 17 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 18 | 19 | const Context = React.createContext<{ 20 | status: string; 21 | setStatus: React.Dispatch>; 22 | }>({ status: "", setStatus: () => null }); 23 | 24 | function InnerContent() { 25 | const ctx = React.useContext(Context); 26 | const [paused, setPaused] = useState(false); 27 | const [minutesLeft, setMinutesLeft] = useState(43); 28 | let interval = useRef(null); 29 | 30 | useEffect(() => { 31 | interval.current = setInterval(() => { 32 | setMinutesLeft((prev) => prev - 1); 33 | }, 1000); 34 | 35 | return () => { 36 | if (interval.current) { 37 | clearInterval(interval.current); 38 | } 39 | }; 40 | }, [setMinutesLeft]); 41 | 42 | function handlePauseToggle() { 43 | console.log(paused); 44 | if (paused) { 45 | interval.current = setInterval(() => { 46 | setMinutesLeft((prev) => prev - 1); 47 | }, 1000); 48 | setPaused(false); 49 | } else { 50 | if (interval.current) { 51 | clearInterval(interval.current); 52 | } 53 | setPaused(true); 54 | } 55 | } 56 | 57 | return ( 58 |
    59 |
    60 |
    61 |
    62 |
    63 |
    64 | 65 |

    :

    66 | 70 |
    71 | 72 |
    73 | 83 | 91 |
    92 |
    93 |
    94 | 95 |
    96 |
    97 |

    Today's activity

    98 |
    99 |
      100 |
    • 101 |
      102 |
      103 |

      Exploration

      104 |

      Mock out timer design

      105 |
      106 |

      45m

      107 |
      108 |
    • 109 |
    • 110 |
      111 |
      112 |

      Deep work

      113 |

      114 | Build page and clip path animation 115 |

      116 |
      117 |

      1h 30m

      118 |
      119 |
    • 120 |
    • 121 |
      122 |
      123 |

      Deep work

      124 |

      Record and edit demo

      125 |
      126 |

      30m

      127 |
      128 |
    • 129 |
    130 |
    131 | 132 |
    133 |
    134 |
    135 |
    136 | 137 | 148 |
    149 | ); 150 | } 151 | 152 | export default function HomePage() { 153 | const [status, setStatus] = React.useState("idle"); 154 | 155 | React.useEffect(() => { 156 | function handleEscape(e: KeyboardEvent) { 157 | if (e.key === "Escape") { 158 | setStatus("idle"); 159 | } 160 | } 161 | window.addEventListener("keydown", handleEscape); 162 | return () => window.removeEventListener("keydown", handleEscape); 163 | }, [setStatus]); 164 | 165 | return ( 166 | 167 | 168 |
    169 | 174 |
    175 | 176 |
    177 | 178 | {/*
    */} 179 | {/*
    */} 180 | {/*
    */} 181 | 182 | iphone mock 187 | 188 |
    189 |
    190 |
    191 | ); 192 | } 193 | -------------------------------------------------------------------------------- /src/app/toast/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { motion, MotionConfig } from "framer-motion"; 5 | import { Check, Loader2, X } from "lucide-react"; 6 | import React from "react"; 7 | 8 | export default function HomePage() { 9 | const [hovered, setHovered] = React.useState(false); 10 | 11 | return ( 12 | 13 |
    14 |
    15 | 26 | 31 | 37 | 38 | 43 |

    44 | Upload Pending 45 |

    46 |
    47 |
    48 |

    Invoice.pdf

    49 |
    50 |

    51 | is being uploaded. Please wait. 52 |

    53 |
    54 |
    55 | 60 | 61 | 62 | 73 | 78 | 79 | 80 | 85 |

    86 | Upload Failed 87 |

    88 |
    89 |
    90 |

    Invoice.pdf

    91 |
    92 |

    93 | failed uploading. Please try again. 94 |

    95 |
    96 |
    97 | 102 | 107 | 108 | 109 |
    setHovered(true)} 111 | onMouseOut={() => setHovered(false)} 112 | className="relative flex w-[800px] gap-4 rounded-[26px] bg-[#151517] bg-[linear-gradient(45deg,#161618_41.67%,#212123_41.67%,#212123_50%,#161618_50%,#161618_91.67%,#212123_91.67%,#212123_100%)] bg-[size:16.97px_16.97px] p-10 shadow-[0_6px_6px_-3px_rgba(0,0,0,0.1),0_0_0px_2.5px_#161618,0_0_0px_2.5px_rgba(255,255,255,0.15)_inset]" 113 | > 114 |
    115 | 116 |
    117 |
    118 |

    119 | Upload Successful 120 |

    121 |
    122 |
    123 |

    Invoice.pdf

    124 |
    125 |

    126 | was uploaded and is ready to use. 127 |

    128 |
    129 |
    130 |
    131 |
    132 |
    133 |
    134 |
    135 | 136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /src/app/todo/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Checkbox } from "@/components/ui/checkbox"; 4 | import { 5 | motion, 6 | MotionConfig, 7 | stagger, 8 | useAnimate, 9 | type Transition, 10 | } from "framer-motion"; 11 | import React from "react"; 12 | 13 | const transition: Transition = { type: "spring", bounce: 0, duration: 0.4 }; 14 | 15 | const Context = React.createContext<{ 16 | status: string; 17 | setStatus: React.Dispatch>; 18 | }>({ status: "", setStatus: () => null }); 19 | 20 | function InnerContent() { 21 | const [items, setItems] = React.useState([ 22 | { id: "1", text: "60 mins practice", checked: true }, 23 | { id: "2", text: "Coffee", checked: true }, 24 | { id: "3", text: "Work", checked: false }, 25 | { id: "4", text: "Walk pupper", checked: true }, 26 | { id: "5", text: "Climb", checked: true }, 27 | { id: "6", text: "Wind down", checked: true }, 28 | ]); 29 | 30 | const [ref, animate] = useAnimate(); 31 | 32 | function handleChange(id: string) { 33 | const newItems = items.map((item) => ({ 34 | ...item, 35 | checked: item.id === id ? !item.checked : item.checked, 36 | })); 37 | 38 | setItems(newItems); 39 | 40 | if (newItems.every((item) => item.checked)) { 41 | const lastCompletedItemIndex = items.findIndex((item) => !item.checked); 42 | const random = Math.random(); 43 | 44 | if (random < 1 / 3) { 45 | // bounce 46 | animate( 47 | '[data-slot="checkbox"]', 48 | { 49 | scale: [1, 1.25, 1], 50 | filter: ["blur(0px)", "blur(1px)", "blur(0px)"], 51 | }, 52 | { 53 | duration: 0.4, 54 | delay: stagger(0.1, { from: lastCompletedItemIndex }), 55 | }, 56 | ); 57 | } else if (random < 2 / 3) { 58 | // shimmy 59 | animate( 60 | '[data-slot="checkbox"]', 61 | { 62 | x: [0, 2, -2, 0], 63 | filter: ["blur(0px)", "blur(1px)", "blur(0px)"], 64 | }, 65 | { 66 | duration: 0.4, 67 | delay: stagger(0.1, { from: lastCompletedItemIndex }), 68 | }, 69 | ); 70 | } else { 71 | // shake 72 | animate( 73 | '[data-slot="checkbox"]', 74 | { 75 | rotate: [0, 12, -12, 0], 76 | filter: ["blur(0px)", "blur(1px)", "blur(0px)"], 77 | }, 78 | { 79 | duration: 1, 80 | delay: stagger(0.2, { from: lastCompletedItemIndex }), 81 | }, 82 | ); 83 | } 84 | } 85 | } 86 | 87 | return ( 88 |
    89 |
    My day
    90 |
    91 | {items.map((item) => ( 92 | 114 | ))} 115 |
    116 |
    117 | ); 118 | } 119 | 120 | export default function TodoPage() { 121 | const [status, setStatus] = React.useState("idle"); 122 | 123 | React.useEffect(() => { 124 | function handleEscape(e: KeyboardEvent) { 125 | if (e.key === "Escape") { 126 | setStatus("idle"); 127 | } 128 | } 129 | window.addEventListener("keydown", handleEscape); 130 | return () => window.removeEventListener("keydown", handleEscape); 131 | }, [setStatus]); 132 | 133 | return ( 134 | 135 | 136 |
    137 | 138 |
    139 |
    140 |
    141 | ); 142 | } 143 | -------------------------------------------------------------------------------- /src/app/toggle-theme/interstellar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/toggle-theme/interstellar.jpg -------------------------------------------------------------------------------- /src/app/toggle-theme/murph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/app/toggle-theme/murph.jpg -------------------------------------------------------------------------------- /src/app/tooltip-nav/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | AnimatePresence, 5 | motion, 6 | MotionConfig, 7 | Variants, 8 | type Transition, 9 | } from "framer-motion"; 10 | import Image from "next/image"; 11 | import Link from "next/link"; 12 | import React from "react"; 13 | import logoX from "@/assets/x.png"; 14 | import logoLinkedIn from "@/assets/linkedin.png"; 15 | import logoBehance from "@/assets/behance.png"; 16 | import logoDribble from "@/assets/dribble.png"; 17 | 18 | const transition: Transition = { type: "spring", bounce: 0.4, duration: 0.3 }; 19 | 20 | const Context = React.createContext<{ 21 | status: string; 22 | setStatus: React.Dispatch>; 23 | }>({ status: "", setStatus: () => null }); 24 | 25 | function InnerContent() { 26 | const ctx = React.useContext(Context); 27 | 28 | const icon: Variants = { 29 | hidden: { 30 | opacity: 0, 31 | y: 10, 32 | translateX: "-50%", 33 | filter: "blur(2px)", 34 | rotate: "0deg", 35 | }, 36 | show: (custom: { rotateRight: boolean }) => ({ 37 | opacity: 1, 38 | y: 0, 39 | filter: "blur(0px)", 40 | rotate: custom?.rotateRight ? "3deg" : "-3deg", 41 | }), 42 | exit: { 43 | opacity: 0, 44 | y: 10, 45 | filter: "blur(2px)", 46 | rotate: "0deg", 47 | transition: { ...transition, duration: 0.5 }, 48 | }, 49 | }; 50 | 51 | return ( 52 |
    53 | ctx.setStatus("x")} 56 | onMouseOut={() => ctx.setStatus("idle")} 57 | className="relative transition-colors duration-300 ease-out hover:!text-[#1f1f1f] group-hover:text-black/30" 58 | > 59 | 60 | {ctx.status === "x" && ( 61 | 69 | x 70 | 71 | )} 72 | 73 | x 74 | 75 | ctx.setStatus("behance")} 78 | onMouseOut={() => ctx.setStatus("idle")} 79 | className="relative transition-colors duration-300 ease-out hover:!text-[#1f1f1f] group-hover:text-black/30" 80 | > 81 | 82 | {ctx.status === "behance" && ( 83 | 90 | behance 91 | 92 | )} 93 | 94 | behance 95 | 96 | ctx.setStatus("linkedin")} 99 | onMouseOut={() => ctx.setStatus("idle")} 100 | className="relative transition-colors duration-300 ease-out hover:!text-[#1f1f1f] group-hover:text-black/30" 101 | > 102 | 103 | {ctx.status === "linkedin" && ( 104 | 112 | linkedin 113 | 114 | )} 115 | 116 | linkedin 117 | 118 | ctx.setStatus("dribble")} 121 | onMouseOut={() => ctx.setStatus("idle")} 122 | className="relative transition-colors duration-300 ease-out hover:!text-[#1f1f1f] group-hover:text-black/30" 123 | > 124 | 125 | {ctx.status === "dribble" && ( 126 | 133 | dribble 134 | 135 | )} 136 | 137 | dribble 138 | 139 |
    140 | ); 141 | } 142 | 143 | export default function HomePage() { 144 | const [status, setStatus] = React.useState("idle"); 145 | 146 | React.useEffect(() => { 147 | function handleEscape(e: KeyboardEvent) { 148 | if (e.key === "Escape") { 149 | setStatus("idle"); 150 | } 151 | } 152 | window.addEventListener("keydown", handleEscape); 153 | return () => window.removeEventListener("keydown", handleEscape); 154 | }, [setStatus]); 155 | 156 | return ( 157 | 158 | 159 |
    160 | 161 |
    162 |
    163 |
    164 | ); 165 | } 166 | -------------------------------------------------------------------------------- /src/app/vinyl/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { MotionConfig } from "framer-motion"; 4 | import React from "react"; 5 | 6 | export default function HomePage() { 7 | return ( 8 | 9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    18 |
    19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/assets/azura.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/azura.png -------------------------------------------------------------------------------- /src/assets/barn.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/barn.jpg -------------------------------------------------------------------------------- /src/assets/beach.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/beach.jpg -------------------------------------------------------------------------------- /src/assets/behance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/behance.png -------------------------------------------------------------------------------- /src/assets/bird.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/bird.jpg -------------------------------------------------------------------------------- /src/assets/dribble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/dribble.png -------------------------------------------------------------------------------- /src/assets/field.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/field.jpg -------------------------------------------------------------------------------- /src/assets/fish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/fish.jpg -------------------------------------------------------------------------------- /src/assets/forest-sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/forest-sky.jpg -------------------------------------------------------------------------------- /src/assets/framer-motion.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/huberman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/huberman.jpg -------------------------------------------------------------------------------- /src/assets/i-just-need-daniel.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/i-just-need-daniel.webp -------------------------------------------------------------------------------- /src/assets/i-just-need.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/i-just-need.png -------------------------------------------------------------------------------- /src/assets/joe-rogan.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/joe-rogan.jpg -------------------------------------------------------------------------------- /src/assets/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/linkedin.png -------------------------------------------------------------------------------- /src/assets/monkey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/monkey.jpg -------------------------------------------------------------------------------- /src/assets/moral.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/moral.jpg -------------------------------------------------------------------------------- /src/assets/orlando.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/orlando.jpg -------------------------------------------------------------------------------- /src/assets/person.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/person.jpg -------------------------------------------------------------------------------- /src/assets/playful-inca.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/playful-inca.jpg -------------------------------------------------------------------------------- /src/assets/snow-board.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/snow-board.jpg -------------------------------------------------------------------------------- /src/assets/stoic-inca.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/stoic-inca.jpg -------------------------------------------------------------------------------- /src/assets/tailwind.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/tailwind.jpg -------------------------------------------------------------------------------- /src/assets/thunder-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/thunder-bg.png -------------------------------------------------------------------------------- /src/assets/tiger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/tiger.jpg -------------------------------------------------------------------------------- /src/assets/uncommon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/uncommon.png -------------------------------------------------------------------------------- /src/assets/vaun-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/vaun-logo.png -------------------------------------------------------------------------------- /src/assets/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vaunblu/lab/91249f26e5eb5274426c1426ed903f83fb05d99c/src/assets/x.png -------------------------------------------------------------------------------- /src/components/grainy-background.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { cn } from "@/lib/utils"; 5 | 6 | interface GrainyBackgroundProps extends React.HTMLAttributes { 7 | baseColor?: string; 8 | grainOpacity?: number; 9 | grainDensity?: number; 10 | grainContrast?: number; 11 | animate?: boolean; 12 | } 13 | 14 | export function GrainyBackground({ 15 | baseColor = "#ffffff", 16 | grainOpacity = 0.12, 17 | grainDensity = 0.9, 18 | grainContrast = 0.7, 19 | animate = false, 20 | className, 21 | children, 22 | ...props 23 | }: GrainyBackgroundProps) { 24 | // Generate a unique ID for the filter 25 | const filterId = React.useId(); 26 | 27 | return ( 28 |
    33 | {/* SVG filter definition */} 34 | 35 | 36 | 43 | {animate && ( 44 | 51 | )} 52 | 53 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | {/* Grain overlay */} 74 |
    82 | 83 | {/* Content */} 84 |
    {children}
    85 |
    86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /src/components/icons.tsx: -------------------------------------------------------------------------------- 1 | export function Italic() { 2 | return ( 3 | 9 | 14 | 15 | ); 16 | } 17 | 18 | export function Underline() { 19 | return ( 20 | 26 | 31 | 32 | ); 33 | } 34 | 35 | export function Link() { 36 | return ( 37 | 43 | 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /src/components/morph.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AnimatePresence, motion } from "framer-motion"; 4 | 5 | export function Morph(props: { children: string | string[]; id?: string }) { 6 | function generateKeys(text: string) { 7 | const charCount: { [key: string]: number } = {}; 8 | 9 | return text.split("").map((char) => { 10 | if (!charCount[char]) { 11 | charCount[char] = 0; 12 | } 13 | const key = `${props.id ?? ""}-${char}-${charCount[char]}`; 14 | charCount[char]++; 15 | return { char, key }; 16 | }); 17 | } 18 | 19 | const textToDisplay = generateKeys(props.children as string); 20 | 21 | return ( 22 | 23 | {textToDisplay.map(({ char, key }) => ( 24 | 42 | {char === " " ? "\u00A0" : char} 43 | 44 | ))} 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /src/components/sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import Link from "next/link"; 4 | import { usePathname } from "next/navigation"; 5 | import { Button } from "@/components/ui/button"; 6 | import { ScrollArea } from "@/components/ui/scroll-area"; 7 | 8 | const examples = [ 9 | "timer", 10 | "transaction-drawer", 11 | "switch", 12 | "animated-list", 13 | "animated-tabs", 14 | "toggle-theme", 15 | "smooth-dropdown", 16 | "todo", 17 | "flashlight", 18 | "shop-claim", 19 | "love-folder", 20 | "tooltip-nav", 21 | "audiobooks", 22 | "folder", 23 | "affirmations", 24 | "album", 25 | "bird", 26 | "carousel", 27 | "cd", 28 | "checkout", 29 | "create-new", 30 | "explore", 31 | "happy-cards", 32 | "love-this", 33 | "magic-button", 34 | "neu", 35 | "northern-lights", 36 | "phone-glow", 37 | "podcasts", 38 | "popcorn", 39 | "slingshot", 40 | "thunder-airdrop", 41 | "thunder-speed", 42 | "timer", 43 | "toast", 44 | "ui-course-form", 45 | "vinyl", 46 | ]; 47 | 48 | export function Sidebar() { 49 | const pathname = usePathname(); 50 | 51 | return ( 52 | 53 |
    54 |
    55 |

    56 | Examples 57 |

    58 |
    59 | {examples.map((example) => ( 60 | 70 | ))} 71 |
    72 |
    73 |
    74 |
    75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cva, type VariantProps } from "class-variance-authority"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | }, 24 | ); 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
    33 | ); 34 | } 35 | 36 | export { Badge, badgeVariants }; 37 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | }, 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button"; 45 | return ( 46 | 51 | ); 52 | }, 53 | ); 54 | Button.displayName = "Button"; 55 | 56 | export { Button, buttonVariants }; 57 | -------------------------------------------------------------------------------- /src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; 5 | import { Check } from "lucide-react"; 6 | 7 | import { cn } from "@/lib/utils"; 8 | 9 | const Checkbox = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 24 | 25 | 26 | 27 | )); 28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName; 29 | 30 | export { Checkbox }; 31 | -------------------------------------------------------------------------------- /src/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = DialogPrimitive.Portal 14 | 15 | const DialogClose = DialogPrimitive.Close 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )) 54 | DialogContent.displayName = DialogPrimitive.Content.displayName 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
    67 | ) 68 | DialogHeader.displayName = "DialogHeader" 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
    81 | ) 82 | DialogFooter.displayName = "DialogFooter" 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )) 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogClose, 116 | DialogTrigger, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /src/components/ui/drawer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Drawer as DrawerPrimitive } from "vaul"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Drawer = ({ 9 | shouldScaleBackground = true, 10 | ...props 11 | }: React.ComponentProps) => ( 12 | 16 | ); 17 | Drawer.displayName = "Drawer"; 18 | 19 | const DrawerTrigger = DrawerPrimitive.Trigger; 20 | 21 | const DrawerPortal = DrawerPrimitive.Portal; 22 | 23 | const DrawerClose = DrawerPrimitive.Close; 24 | 25 | const DrawerOverlay = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 34 | )); 35 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName; 36 | 37 | const DrawerContent = React.forwardRef< 38 | React.ElementRef, 39 | React.ComponentPropsWithoutRef 40 | >(({ className, children, ...props }, ref) => ( 41 | 49 | {/*
    */} 50 | {children} 51 | 52 | )); 53 | DrawerContent.displayName = "DrawerContent"; 54 | 55 | const DrawerHeader = ({ 56 | className, 57 | ...props 58 | }: React.HTMLAttributes) => ( 59 |
    63 | ); 64 | DrawerHeader.displayName = "DrawerHeader"; 65 | 66 | const DrawerFooter = ({ 67 | className, 68 | ...props 69 | }: React.HTMLAttributes) => ( 70 |
    74 | ); 75 | DrawerFooter.displayName = "DrawerFooter"; 76 | 77 | const DrawerTitle = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef 80 | >(({ className, ...props }, ref) => ( 81 | 89 | )); 90 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName; 91 | 92 | const DrawerDescription = React.forwardRef< 93 | React.ElementRef, 94 | React.ComponentPropsWithoutRef 95 | >(({ className, ...props }, ref) => ( 96 | 101 | )); 102 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName; 103 | 104 | export { 105 | Drawer, 106 | DrawerPortal, 107 | DrawerOverlay, 108 | DrawerTrigger, 109 | DrawerClose, 110 | DrawerContent, 111 | DrawerHeader, 112 | DrawerFooter, 113 | DrawerTitle, 114 | DrawerDescription, 115 | }; 116 | -------------------------------------------------------------------------------- /src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )) 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 30 | 31 | export { Popover, PopoverTrigger, PopoverContent } 32 | -------------------------------------------------------------------------------- /src/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ProgressPrimitive from "@radix-ui/react-progress" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )) 26 | Progress.displayName = ProgressPrimitive.Root.displayName 27 | 28 | export { Progress } 29 | -------------------------------------------------------------------------------- /src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )) 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )) 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 47 | 48 | export { ScrollArea, ScrollBar } 49 | -------------------------------------------------------------------------------- /src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SwitchPrimitives from "@radix-ui/react-switch"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | 26 | )); 27 | Switch.displayName = SwitchPrimitives.Root.displayName; 28 | 29 | export { Switch }; 30 | -------------------------------------------------------------------------------- /src/hooks/use-media-query.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export function useMediaQuery(query: string) { 4 | const [value, setValue] = React.useState(false); 5 | 6 | React.useEffect(() => { 7 | function onChange(event: MediaQueryListEvent) { 8 | setValue(event.matches); 9 | } 10 | 11 | const result = matchMedia(query); 12 | result.addEventListener("change", onChange); 13 | setValue(result.matches); 14 | 15 | return () => result.removeEventListener("change", onChange); 16 | }, [query]); 17 | 18 | return value; 19 | } 20 | -------------------------------------------------------------------------------- /src/hooks/use-mouse-position.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export function useMousePosition() { 4 | const [mousePosition, setMousePosition] = React.useState({ 5 | x: 0, 6 | y: 0, 7 | }); 8 | 9 | const updateMousePosition = (ev: MouseEvent) => { 10 | setMousePosition({ x: ev.clientX, y: ev.clientY }); 11 | }; 12 | 13 | React.useEffect(() => { 14 | window.addEventListener("mousemove", updateMousePosition); 15 | return () => window.removeEventListener("mousemove", updateMousePosition); 16 | }, []); 17 | 18 | return mousePosition; 19 | } 20 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | import defaultTheme from "tailwindcss/defaultTheme"; 3 | 4 | const config = { 5 | darkMode: ["class"], 6 | content: [ 7 | "./pages/**/*.{ts,tsx}", 8 | "./components/**/*.{ts,tsx}", 9 | "./app/**/*.{ts,tsx}", 10 | "./src/**/*.{ts,tsx}", 11 | ], 12 | prefix: "", 13 | theme: { 14 | container: { 15 | center: true, 16 | padding: "2rem", 17 | screens: { 18 | "2xl": "1400px", 19 | }, 20 | }, 21 | extend: { 22 | fontFamily: { 23 | sans: ["var(--font-geist-sans)", ...defaultTheme.fontFamily.sans], 24 | serif: ["var(--font-eb-garamond)", ...defaultTheme.fontFamily.serif], 25 | mono: ["var(--font-geist-mono)", ...defaultTheme.fontFamily.mono], 26 | display: [ 27 | "var(--font-dancing-script)", 28 | ...defaultTheme.fontFamily.sans, 29 | ], 30 | }, 31 | backgroundImage: { 32 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 33 | "border-gradient": 34 | "linear-gradient(to bottom, black var(--progress), transparent var(--progress))", 35 | }, 36 | colors: { 37 | border: "hsl(var(--border))", 38 | input: "hsl(var(--input))", 39 | ring: "hsl(var(--ring))", 40 | background: "hsl(var(--background))", 41 | foreground: "hsl(var(--foreground))", 42 | primary: { 43 | DEFAULT: "hsl(var(--primary))", 44 | foreground: "hsl(var(--primary-foreground))", 45 | }, 46 | secondary: { 47 | DEFAULT: "hsl(var(--secondary))", 48 | foreground: "hsl(var(--secondary-foreground))", 49 | }, 50 | destructive: { 51 | DEFAULT: "hsl(var(--destructive))", 52 | foreground: "hsl(var(--destructive-foreground))", 53 | }, 54 | muted: { 55 | DEFAULT: "hsl(var(--muted))", 56 | foreground: "hsl(var(--muted-foreground))", 57 | }, 58 | accent: { 59 | DEFAULT: "hsl(var(--accent))", 60 | foreground: "hsl(var(--accent-foreground))", 61 | }, 62 | popover: { 63 | DEFAULT: "hsl(var(--popover))", 64 | foreground: "hsl(var(--popover-foreground))", 65 | }, 66 | card: { 67 | DEFAULT: "hsl(var(--card))", 68 | foreground: "hsl(var(--card-foreground))", 69 | }, 70 | }, 71 | boxShadow: { 72 | mixed: "var(--shadow-mixed)", 73 | }, 74 | borderRadius: { 75 | lg: "var(--radius)", 76 | md: "calc(var(--radius) - 2px)", 77 | sm: "calc(var(--radius) - 4px)", 78 | }, 79 | keyframes: { 80 | "accordion-down": { 81 | from: { height: "0" }, 82 | to: { height: "var(--radix-accordion-content-height)" }, 83 | }, 84 | "accordion-up": { 85 | from: { height: "var(--radix-accordion-content-height)" }, 86 | to: { height: "0" }, 87 | }, 88 | borderTimer: { 89 | "0%": { clipPath: "inset(0 0 0 0)" }, 90 | "100%": { clipPath: "inset(0 0 100% 0)" }, 91 | }, 92 | }, 93 | animation: { 94 | "accordion-down": "accordion-down 0.2s ease-out", 95 | "accordion-up": "accordion-up 0.2s ease-out", 96 | "border-timer": "borderTimer var(--duration) linear forwards", 97 | }, 98 | }, 99 | }, 100 | plugins: [require("tailwindcss-animate")], 101 | } satisfies Config; 102 | 103 | export default config; 104 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | --------------------------------------------------------------------------------