Spaces:
Running
Running
File size: 13,049 Bytes
21a686e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
"use client";
import { cn } from "@/lib/utils";
import { AnimatePresence, AnimationProps, motion } from "framer-motion";
import React, { useEffect, useState } from "react";
const FlippingText = ({
words,
className,
}: {
words: string[];
className?: string;
}) => {
const [currentWordIndex, setCurrentWordIndex] = useState(0);
const [visibleCharacters, setVisibleCharacters] = useState(0);
const [isDeleting, setIsDeleting] = useState(false);
const currentWord = words[currentWordIndex];
useEffect(() => {
const typingSpeed = 50; // Speed for typing characters
const deletingSpeed = 50; // Speed for deleting characters
const pauseBeforeDelete = 1000; // Pause before starting to delete
let timeout: NodeJS.Timeout;
if (!isDeleting && visibleCharacters < currentWord.length) {
// Typing mode - add characters
timeout = setTimeout(() => {
setVisibleCharacters((prev) => prev + 1);
}, typingSpeed);
} else if (!isDeleting && visibleCharacters === currentWord.length) {
// Finished typing - pause before deleting
timeout = setTimeout(() => {
setIsDeleting(true);
}, pauseBeforeDelete);
} else if (isDeleting && visibleCharacters > 0) {
// Deleting mode - remove characters
timeout = setTimeout(() => {
setVisibleCharacters((prev) => prev - 1);
}, deletingSpeed);
} else if (isDeleting && visibleCharacters === 0) {
// Finished deleting - move to next word
setIsDeleting(false);
setCurrentWordIndex((prev) => (prev + 1) % words.length);
}
return () => clearTimeout(timeout);
}, [
currentWord,
currentWordIndex,
isDeleting,
visibleCharacters,
words.length,
]);
return (
<span className={cn("relative inline-block", className)}>
<span className="tracking-tighter">
{currentWord
.substring(0, visibleCharacters)
.split("")
.map((char, index) => (
<motion.span
key={`${index}-${char}`}
initial={{
opacity: 0,
rotateY: 90,
y: 10,
filter: "blur(10px)",
}}
animate={{
opacity: 1,
rotateY: 0,
y: 0,
filter: "blur(0px)",
}}
exit={{
opacity: 0,
rotateY: -90,
y: -10,
filter: "blur(10px)",
}}
transition={{ duration: 0.3 }}
className="inline-block"
>
{char}
</motion.span>
))}
</span>
<motion.span
layout
className="absolute -right-4 bottom-2 inline-block rounded-full bg-black"
style={{
width: isDeleting ? "0.45em" : "0.25em",
height: "0.25em",
}}
animate={{
backgroundColor: isDeleting
? "#ef4444" // hex for red-500
: [
"#60a5fa", // hex for blue-400
"#22c55e", // hex for green-500
"#3b82f6", // hex for blue-500
],
}}
transition={{
duration: 0.1,
}}
/>
</span>
);
};
export function TextAnimationFlippingWords() {
const words = ["Next.js", "Tailwind CSS", "Motion", "Typescript", "Web GL"];
return (
<div className="mx-auto grid w-full max-w-7xl grid-cols-1 items-center gap-10 px-4 py-20 md:grid-cols-2 md:px-8">
<div>
<h1 className="mb-4 text-left text-2xl font-bold md:text-5xl">
Built with <FlippingText words={words} />
</h1>
<p className="mt-4 text-left text-base text-neutral-600 dark:text-neutral-400">
Create stunning animations that bring your website to life. Elevate
your user experience with smooth transitions and eye-catching effects
that captivate visitors and keep them engaged.
</p>
<LogoCloud />
</div>
<IphoneMockup>
<div className="flex flex-col space-y-4 overflow-y-auto p-4">
{[
{
title: "Getting Started",
description: "Learn how to build your first animation in minutes",
icon: "π",
},
{
title: "Advanced Techniques",
description:
"Take your animations to the next level with these pro tips",
icon: "β‘",
},
{
title: "Performance Tips",
description: "Optimize your animations for smooth rendering",
icon: "βοΈ",
},
{
title: "Animation Library",
description: "Browse our collection of pre-built animations",
icon: "π",
},
{
title: "Community Showcase",
description: "See what others are building with our tools",
icon: "π",
},
].map((card, index) => (
<div
key={index}
className="rounded-xl bg-white p-4 shadow-md transition-all hover:shadow-lg dark:bg-neutral-800"
>
<div className="flex items-start space-x-3">
<div className="text-xl">{card.icon}</div>
<div>
<h3 className="font-medium text-neutral-900 dark:text-neutral-100">
{card.title}
</h3>
<p className="text-sm text-neutral-500 dark:text-neutral-400">
{card.description}
</p>
</div>
</div>
</div>
))}
</div>
</IphoneMockup>
</div>
);
}
const LogoCloud = () => {
return (
<div className="mt-8">
<p className="text-sm font-medium text-neutral-500 dark:text-neutral-400">
Trusted by leading companies
</p>
{/* Company logos display */}
<div className="mt-4 flex flex-wrap gap-6">
{[
{
name: "Aceternity UI",
src: "https://assets.aceternity.com/pro/logos/aceternity-ui.png",
},
{
name: "Gamity",
src: "https://assets.aceternity.com/pro/logos/gamity.png",
},
{
name: "Host it",
src: "https://assets.aceternity.com/pro/logos/hostit.png",
},
{
name: "Asteroid Kit",
src: "https://assets.aceternity.com/pro/logos/asteroid-kit.png",
},
].map((logo) => (
<div key={logo.name} className="flex items-center">
<img
src={logo.src}
alt={logo.name}
className="h-10 w-auto object-contain transition-all dark:invert dark:filter"
/>
</div>
))}
</div>
</div>
);
};
interface IphoneMockupProps {
image?: string;
children?: React.ReactNode;
}
export const IphoneMockup = ({ image, children }: IphoneMockupProps) => {
return (
<div className="relative mx-auto h-[600px] w-[300px] md:h-[680px] md:w-[340px]">
{/* iPhone frame */}
<div className="absolute inset-0 rounded-[50px] border-[14px] border-black bg-black shadow-xl dark:border-neutral-800 dark:bg-neutral-900">
{/* Dynamic Island */}
<div className="absolute top-[0.5rem] left-1/2 z-10 h-[1.8rem] w-[6rem] -translate-x-1/2 rounded-full bg-black dark:bg-neutral-800">
<div className="absolute top-1/2 right-3 h-[0.6rem] w-[0.6rem] -translate-y-1/2 rounded-full bg-[#1a1a1a] ring-[1.5px] ring-[#2a2a2a] dark:bg-neutral-700 dark:ring-neutral-600">
<div className="absolute inset-[1.5px] rounded-full bg-[#0f0f0f] dark:bg-neutral-800">
<div className="absolute inset-[1.5px] rounded-full bg-[#151515] ring-[0.75px] ring-[#202020] dark:bg-neutral-700 dark:ring-neutral-600" />
</div>
</div>
</div>
{/* Status icons */}
<div className="absolute inset-0 top-[0.5rem] z-20 flex h-[1.8rem] items-center justify-between px-4 text-[0.65rem] text-black dark:text-white">
{/* Time */}
<span className="text-[0.9rem] font-medium">9:41</span>
{/* Right icons */}
<div className="flex items-center gap-1">
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="13"
viewBox="0 0 20 13"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M19.2 1.72656C19.2 1.10524 18.7224 0.601562 18.1333 0.601562H17.0667C16.4776 0.601562 16 1.10524 16 1.72656V11.4766C16 12.0979 16.4776 12.6016 17.0667 12.6016H18.1333C18.7224 12.6016 19.2 12.0979 19.2 11.4766V1.72656ZM11.7659 3H12.8326C13.4217 3 13.8992 3.51577 13.8992 4.152V11.448C13.8992 12.0842 13.4217 12.6 12.8326 12.6H11.7659C11.1768 12.6 10.6992 12.0842 10.6992 11.448V4.152C10.6992 3.51577 11.1768 3 11.7659 3ZM7.43411 5.60156H6.36745C5.77834 5.60156 5.30078 6.1239 5.30078 6.76823V11.4349C5.30078 12.0792 5.77834 12.6016 6.36745 12.6016H7.43411C8.02322 12.6016 8.50078 12.0792 8.50078 11.4349V6.76823C8.50078 6.1239 8.02322 5.60156 7.43411 5.60156ZM2.13333 8H1.06667C0.477563 8 0 8.51487 0 9.15V11.45C0 12.0851 0.477563 12.6 1.06667 12.6H2.13333C2.72244 12.6 3.2 12.0851 3.2 11.45V9.15C3.2 8.51487 2.72244 8 2.13333 8Z"
fill="currentColor"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="13"
viewBox="0 0 18 13"
fill="none"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.27052 3.11983C11.7576 3.11993 14.1496 4.02494 15.9521 5.6478C16.0879 5.77309 16.3048 5.77151 16.4385 5.64426L17.736 4.40419C17.8037 4.33964 17.8414 4.25222 17.8409 4.16125C17.8403 4.07029 17.8015 3.98328 17.733 3.9195C13.002 -0.374207 5.53829 -0.374207 0.807275 3.9195C0.738743 3.98324 0.699859 4.07021 0.699227 4.16118C0.698595 4.25214 0.736267 4.3396 0.803908 4.40419L2.10177 5.64426C2.23537 5.7717 2.45249 5.77328 2.58814 5.6478C4.39088 4.02483 6.78317 3.11982 9.27052 3.11983ZM9.26717 7.26377C10.6245 7.26369 11.9334 7.76595 12.9395 8.67297C13.0756 8.80169 13.2899 8.7989 13.4226 8.66668L14.7099 7.3718C14.7777 7.30388 14.8153 7.21174 14.8143 7.116C14.8133 7.02025 14.7738 6.92889 14.7047 6.86236C11.6408 4.02505 6.8961 4.02505 3.83227 6.86236C3.76306 6.92889 3.72357 7.0203 3.72266 7.11607C3.72176 7.21185 3.7595 7.30398 3.82744 7.3718L5.11435 8.66668C5.247 8.7989 5.46136 8.80169 5.59745 8.67297C6.6029 7.76655 7.91074 7.26433 9.26717 7.26377ZM11.7916 10.0035C11.7935 10.1069 11.7565 10.2066 11.6892 10.279L9.51249 12.6883C9.44868 12.7591 9.36169 12.799 9.27092 12.799C9.18015 12.799 9.09316 12.7591 9.02935 12.6883L6.85232 10.279C6.78507 10.2065 6.74808 10.1068 6.75007 10.0034C6.75206 9.90002 6.79287 9.8021 6.86286 9.73279C8.25296 8.44324 10.2889 8.44324 11.679 9.73279C11.7489 9.80216 11.7897 9.90011 11.7916 10.0035Z"
fill="currentColor"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="28"
height="14"
viewBox="0 0 28 14"
fill="none"
>
<rect
opacity="0.35"
x="0.699219"
y="0.601562"
width="24"
height="12"
rx="3.8"
fill="currentColor"
stroke="white"
/>
<path
opacity="0.4"
d="M26.0977 4.76953V8.76953C26.9024 8.43075 27.4257 7.64266 27.4257 6.76953C27.4257 5.8964 26.9024 5.10831 26.0977 4.76953Z"
fill="currentColor"
/>
<rect
x="2"
y="2.10156"
width="21"
height="9"
rx="2.5"
fill="currentColor"
/>
</svg>
</div>
</div>
{/* Screen */}
<div className="relative h-full w-full overflow-hidden rounded-[35px] bg-white dark:bg-neutral-950">
<div className="absolute inset-0 top-[2.3rem]">
{image && (
<img src={image} alt="App screenshot" className="object-cover" />
)}
{children}
</div>
</div>
</div>
{/* Side Buttons */}
<div className="absolute top-[170px] -right-[2px] h-12 w-[3px] rounded-l-lg bg-black dark:bg-neutral-800"></div>
<div className="absolute top-[120px] -left-[2px] h-12 w-[3px] rounded-r-lg bg-black dark:bg-neutral-800"></div>
<div className="absolute top-[170px] -left-[2px] h-14 w-[3px] rounded-r-lg bg-black dark:bg-neutral-800"></div>
</div>
);
}; |