Skip to content

Commit 627cb1b

Browse files
committed
style(hero): add rotating text
1 parent 879d5d1 commit 627cb1b

File tree

7 files changed

+137
-38
lines changed

7 files changed

+137
-38
lines changed

app/bun.lock

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"clsx": "^2.1.1",
1818
"elkjs": "^0.10.0",
1919
"lucide-react": "^0.514.0",
20+
"motion": "^12.16.0",
2021
"next": "15.3.3",
2122
"next-themes": "^0.4.6",
2223
"nuqs": "^2.4.3",
@@ -405,6 +406,8 @@
405406

406407
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
407408

409+
"framer-motion": ["framer-motion@12.16.0", "", { "dependencies": { "motion-dom": "^12.16.0", "motion-utils": "^12.12.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-xryrmD4jSBQrS2IkMdcTmiS4aSKckbS7kLDCuhUn9110SQKG1w3zlq1RTqCblewg+ZYe+m3sdtzQA6cRwo5g8Q=="],
410+
408411
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
409412

410413
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -471,6 +474,12 @@
471474

472475
"monaco-editor": ["monaco-editor@0.52.2", "", {}, "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ=="],
473476

477+
"motion": ["motion@12.16.0", "", { "dependencies": { "framer-motion": "^12.16.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-P3HA83fnPMEGBLfKdD5vDdjH1Aa3wM3jT3+HX3fCVpy/4/lJiqvABajLgZenBu+rzkFzmeaPkvT7ouf9Tq5tVQ=="],
478+
479+
"motion-dom": ["motion-dom@12.16.0", "", { "dependencies": { "motion-utils": "^12.12.1" } }, "sha512-Z2nGwWrrdH4egLEtgYMCEN4V2qQt1qxlKy/uV7w691ztyA41Q5Rbn0KNGbsNVDZr9E8PD2IOQ3hSccRnB6xWzw=="],
480+
481+
"motion-utils": ["motion-utils@12.12.1", "", {}, "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w=="],
482+
474483
"mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
475484

476485
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
@@ -615,7 +624,7 @@
615624

616625
"ts-pattern": ["ts-pattern@5.7.1", "", {}, "sha512-EGs8PguQqAAUIcQfK4E9xdXxB6s2GK4sJfT/vcc9V1ELIvC4LH/zXu2t/5fajtv6oiRCxdv7BgtVK3vWgROxag=="],
617626

618-
"tslib": ["tslib@2.7.0", "", {}, "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="],
627+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
619628

620629
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
621630

@@ -649,20 +658,12 @@
649658

650659
"zustand": ["zustand@5.0.5", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg=="],
651660

652-
"@emnapi/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
653-
654-
"@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
655-
656-
"@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
657-
658-
"@swc/helpers/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
659-
660-
"@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
661-
662661
"@xyflow/react/zustand": ["zustand@4.5.6", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["immer"] }, "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ=="],
663662

664663
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
665664

665+
"aria-hidden/tslib": ["tslib@2.7.0", "", {}, "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="],
666+
666667
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
667668

668669
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -675,12 +676,6 @@
675676

676677
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
677678

678-
"react-remove-scroll/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
679-
680-
"react-remove-scroll-bar/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
681-
682-
"react-style-singleton/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
683-
684679
"readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
685680

686681
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -697,10 +692,6 @@
697692

698693
"tailwindcss/postcss": ["postcss@8.4.30", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g=="],
699694

700-
"use-callback-ref/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
701-
702-
"use-sidecar/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
703-
704695
"wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
705696

706697
"wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],

app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"clsx": "^2.1.1",
3333
"elkjs": "^0.10.0",
3434
"lucide-react": "^0.514.0",
35+
"motion": "^12.16.0",
3536
"next": "15.3.3",
3637
"next-themes": "^0.4.6",
3738
"nuqs": "^2.4.3",

app/src/app/page.tsx

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ArrowRightIcon,
88
} from "lucide-react";
99

10-
import { Button } from "components/ui";
10+
import { Button, RotatingText } from "components/ui";
1111
import cn from "lib/util/cn";
1212

1313
const features = [
@@ -75,24 +75,29 @@ const HomePage = () => (
7575
</div>
7676

7777
<div className="space-y-4">
78-
<div className="flex h-full items-center justify-center gap-4">
79-
<h1 className="bg-gradient-to-r from-primary to-primary/80 bg-clip-text py-2 font-bold text-3xl text-transparent tracking-tighter sm:text-4xl md:text-5xl">
80-
Visualize Your Ecosystem{" "}
81-
</h1>
78+
<RotatingText
79+
words={[
80+
"Product",
81+
"Service",
82+
"Team",
83+
"Organization",
84+
"Project",
85+
"Platform",
86+
]}
87+
className="py-2"
88+
/>
8289

83-
<SparklesIcon className="hidden h-10 w-10 md:flex" />
84-
</div>
85-
86-
<p className="mx-auto max-w-[700px] text-muted-foreground md:text-xl">
87-
Transform complex relationships into clear, interactive maps.
90+
<p className="mx-auto max-w-[700px] text-muted-foreground md:text-lg">
91+
Transform complex business ecosystem relationships into clear,
92+
interactive visualizations.
8893
</p>
8994
</div>
9095

9196
<div className="mt-8 space-x-4">
9297
<Link href="/visualizer">
9398
<Button
9499
size="lg"
95-
className="gap-2 bg-primary hover:bg-primary/90"
100+
className="gap-2 bg-primary hover:bg-primary/90 mt-4"
96101
>
97102
Start Modeling
98103
<ArrowRightIcon className="h-4 w-4" />
@@ -111,7 +116,7 @@ const HomePage = () => (
111116
key={feature.title}
112117
className={cn(
113118
"group relative flex flex-col items-center space-y-4 text-center",
114-
"rounded-lg border bg-background p-6 shadow-lg transition-shadow hover:shadow-xl",
119+
"rounded-lg border bg-background p-6 shadow-lg transition-shadow hover:shadow-xl"
115120
)}
116121
>
117122
<div className="rounded-full bg-primary/10 p-4 text-primary">

app/src/components/layout/Header/Header.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import { ThemeToggle } from "components/layout";
1010
*/
1111
const Header = () => (
1212
<header className="sticky top-0 z-50 flex w-full items-center justify-between border-b bg-background/30 p-4 backdrop-blur-lg">
13-
<Link href="/" className="flex items-center gap-3">
14-
<SproutIcon className="h-8 w-8 text-primary" />
15-
<h1 className="font-bold text-3xl">Garden</h1>
13+
<Link href="/" className="flex items-center gap-2">
14+
<SproutIcon className="h-6 w-6 text-primary" />
15+
<h1 className="font-bold text-xl">Garden</h1>
1616
</Link>
1717

1818
<ThemeToggle />

app/src/components/ui/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@ export {
1717
ChartLegendContent,
1818
ChartStyle,
1919
} from "./chart";
20-
export { Collapsible, CollapsibleTrigger, CollapsibleContent } from "./collapsible";
20+
export {
21+
Collapsible,
22+
CollapsibleTrigger,
23+
CollapsibleContent,
24+
} from "./collapsible";
2125
export {
2226
Dialog,
2327
DialogPortal,
@@ -31,6 +35,7 @@ export {
3135
DialogDescription,
3236
} from "./dialog";
3337
export { Label } from "./label";
38+
export { RotatingText } from "./rotating-text";
3439
export {
3540
Sheet,
3641
SheetPortal,
@@ -44,4 +49,4 @@ export {
4449
SheetDescription,
4550
} from "./sheet";
4651
export { Switch } from "./switch";
47-
export { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs";
52+
export { Tabs, TabsList, TabsTrigger, TabsContent } from "./tabs";
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"use client";
2+
3+
import { useEffect, useState, useRef } from "react";
4+
import cn from "lib/util/cn";
5+
6+
interface RotatingTextProps {
7+
prefix?: string;
8+
suffix?: string;
9+
words: string[];
10+
interval?: number;
11+
className?: string;
12+
}
13+
14+
const colors = [
15+
"from-purple-500 to-blue-500",
16+
"from-blue-500 to-teal-500",
17+
"from-teal-500 to-emerald-500",
18+
"from-emerald-500 to-yellow-500",
19+
"from-yellow-500 to-orange-500",
20+
"from-orange-500 to-red-500",
21+
"from-red-500 to-pink-500",
22+
"from-pink-500 to-purple-500",
23+
];
24+
25+
export const RotatingText = ({
26+
prefix = "Visualize your",
27+
suffix = "Ecosystem",
28+
interval = 3000,
29+
className = "",
30+
words,
31+
}: RotatingTextProps) => {
32+
const [currentIndex, setCurrentIndex] = useState(0);
33+
const [currentColor, setCurrentColor] = useState(0);
34+
const [isChanging, setIsChanging] = useState(false);
35+
const wordRef = useRef<HTMLSpanElement>(null);
36+
const containerRef = useRef<HTMLDivElement>(null);
37+
38+
// handle word rotation
39+
useEffect(() => {
40+
const timer = setInterval(() => {
41+
setIsChanging(true);
42+
43+
setTimeout(() => {
44+
setCurrentIndex((prevIndex) => (prevIndex + 1) % words.length);
45+
setCurrentColor((prevColor) => (prevColor + 1) % colors.length);
46+
47+
setTimeout(() => {
48+
setIsChanging(false);
49+
}, 100);
50+
}, 300);
51+
}, interval);
52+
53+
return () => clearInterval(timer);
54+
}, [interval, words.length]);
55+
56+
return (
57+
<div className={`flex items-center justify-center ${className}`}>
58+
<h1 className="py-2 font-bold text-3xl tracking-tighter sm:text-4xl md:text-5xl flex flex-wrap items-center">
59+
<span
60+
className="transition-all duration-300 ease-in-out"
61+
style={{
62+
transform: isChanging ? "translateX(8px)" : "translateX(0)",
63+
}}
64+
>
65+
{prefix}
66+
</span>
67+
68+
<div
69+
ref={containerRef}
70+
className="inline-block mx-3 text-center relative"
71+
>
72+
<span
73+
ref={wordRef}
74+
className={cn(
75+
`inline-block bg-gradient-to-r ${colors[currentColor]} bg-clip-text text-transparent transition-all duration-300 py-1`,
76+
isChanging ? "opacity-0 scale-95" : "opacity-100 scale-100"
77+
)}
78+
>
79+
{words[currentIndex]}
80+
</span>
81+
</div>
82+
83+
<span
84+
className="transition-all duration-300 ease-in-out"
85+
style={{
86+
transform: isChanging ? "translateX(-8px)" : "translateX(0)",
87+
}}
88+
>
89+
{suffix}
90+
</span>
91+
</h1>
92+
</div>
93+
);
94+
};
95+
96+
export default RotatingText;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as RotatingText } from "./RotatingText";

0 commit comments

Comments
 (0)