History

chore(marketing): apply formatting and make beam Row sub optional

Rhys Sullivan committed 6 months ago

Commit 7fe56321 parent 587196f4 files · +300 −185Browse files

Unified · Split

4 files
  1. apps/marketing/src/components/animated-beam-demo.tsx +183 −91
  2. apps/marketing/src/components/ui/animated-beam.tsx +52 −74
  3. apps/marketing/src/lib/utils.ts +3 −3
  4. apps/marketing/src/styles/global.css +62 −17
apps/marketing/src/components/animated-beam-demo.tsx+183−91
@@ -1,26 +1,26 @@
1"use client"1"use client";
2 2
3import React, { forwardRef, useRef } from "react"3import React, { forwardRef, useRef } from "react";
4 4
5import { cn } from "../lib/utils"5import { cn } from "../lib/utils";
6import { AnimatedBeam } from "./ui/animated-beam"6import { AnimatedBeam } from "./ui/animated-beam";
7 7
8type Variant = "blueprint" | "brutalist" | "pastel" | "cyber" | "editorial" | "stripe"8type Variant = "blueprint" | "brutalist" | "pastel" | "cyber" | "editorial" | "stripe";
9 9
10const variantStyles: Record<10const variantStyles: Record<
11 Variant,11 Variant,
12 {12 {
13 nodeBg: string13 nodeBg: string;
14 nodeBorder: string14 nodeBorder: string;
15 nodeRadius: string15 nodeRadius: string;
16 hubBg: string16 hubBg: string;
17 hubBorder: string17 hubBorder: string;
18 label: string18 label: string;
19 sub: string19 sub: string;
20 beam: string20 beam: string;
21 path: string21 path: string;
22 pathOpacity: number22 pathOpacity: number;
23 pathWidth: number23 pathWidth: number;
24 }24 }
25> = {25> = {
26 blueprint: {26 blueprint: {
@@ -101,15 +101,15 @@ const variantStyles: Record< · 74 unmodified lines
101 pathOpacity: 0.4,101 pathOpacity: 0.4,
102 pathWidth: 1.25,102 pathWidth: 1.25,
103 },103 },
104}104};
105 105
106const Node = forwardRef<106const Node = forwardRef<
107 HTMLDivElement,107 HTMLDivElement,
108 {108 {
109 className?: string109 className?: string;
110 children?: React.ReactNode110 children?: React.ReactNode;
111 size?: "sm" | "lg"111 size?: "sm" | "lg";
112 style?: React.CSSProperties112 style?: React.CSSProperties;
113 }113 }
114>(({ className, children, size = "sm", style }, ref) => {114>(({ className, children, size = "sm", style }, ref) => {
115 return (115 return (
@@ -119,116 +119,190 @@ const Node = forwardRef< · 3 unmodified lines
119 className={cn(119 className={cn(
120 "z-10 flex items-center justify-center",120 "z-10 flex items-center justify-center",
121 size === "sm" ? "size-10 p-2" : "size-16 p-2",121 size === "sm" ? "size-10 p-2" : "size-16 p-2",
122 className122 className,
123 )}123 )}
124 >124 >
125 {children}125 {children}
126 </div>126 </div>
127 )127 );
128})128});
129Node.displayName = "Node"129Node.displayName = "Node";
130 130
131export function AnimatedBeamDemo({131export function AnimatedBeamDemo({
132 className,132 className,
133 variant = "blueprint",133 variant = "blueprint",
134}: {134}: {
135 className?: string135 className?: string;
136 variant?: Variant136 variant?: Variant;
137}) {137}) {
138 const containerRef = useRef<HTMLDivElement>(null)138 const containerRef = useRef<HTMLDivElement>(null);
139 const agent1 = useRef<HTMLDivElement>(null)139 const agent1 = useRef<HTMLDivElement>(null);
140 const agent2 = useRef<HTMLDivElement>(null)140 const agent2 = useRef<HTMLDivElement>(null);
141 const agent3 = useRef<HTMLDivElement>(null)141 const agent3 = useRef<HTMLDivElement>(null);
142 const hub = useRef<HTMLDivElement>(null)142 const hub = useRef<HTMLDivElement>(null);
143 const tool1 = useRef<HTMLDivElement>(null)143 const tool1 = useRef<HTMLDivElement>(null);
144 const tool2 = useRef<HTMLDivElement>(null)144 const tool2 = useRef<HTMLDivElement>(null);
145 const tool3 = useRef<HTMLDivElement>(null)145 const tool3 = useRef<HTMLDivElement>(null);
146 146
147 const v = variantStyles[variant]147 const v = variantStyles[variant];
148 const beamColor = v.beam148 const beamColor = v.beam;
149 const pathColor = v.path149 const pathColor = v.path;
150 const pathOpacity = v.pathOpacity150 const pathOpacity = v.pathOpacity;
151 const pathWidth = v.pathWidth151 const pathWidth = v.pathWidth;
152 const nodeStyle: React.CSSProperties = {152 const nodeStyle: React.CSSProperties = {
153 background: v.nodeBg,153 background: v.nodeBg,
154 border: `1px solid ${v.nodeBorder}`,154 border: `1px solid ${v.nodeBorder}`,
155 borderRadius: v.nodeRadius,155 borderRadius: v.nodeRadius,
156 }156 };
157 const hubStyle: React.CSSProperties = {157 const hubStyle: React.CSSProperties = {
158 background: v.hubBg,158 background: v.hubBg,
159 border: `${variant === "brutalist" ? "2px" : "1px"} solid ${v.hubBorder}`,159 border: `${variant === "brutalist" ? "2px" : "1px"} solid ${v.hubBorder}`,
160 borderRadius: v.nodeRadius,160 borderRadius: v.nodeRadius,
161 }161 };
162 const labelStyle: React.CSSProperties = { color: v.label }162 const labelStyle: React.CSSProperties = { color: v.label };
163 const subStyle: React.CSSProperties = { color: v.sub }163 const subStyle: React.CSSProperties = { color: v.sub };
164 164
165 return (165 return (
166 <div166 <div
167 ref={containerRef}167 ref={containerRef}
168 className={cn(168 className={cn("relative flex w-full items-center justify-center py-2", className)}
169 "relative flex w-full items-center justify-center py-2",
170 className
171 )}
172 >169 >
173 <div className="flex w-full flex-row items-stretch justify-between gap-6 sm:gap-8">170 <div className="flex w-full flex-row items-stretch justify-between gap-6 sm:gap-8">
174
175 {/* Agents (left) */}171 {/* Agents (left) */}
176 <div className="flex flex-col justify-center gap-7">172 <div className="flex flex-col justify-center gap-7">
177 <Row label="Claude Code" reverse={false} labelStyle={labelStyle}>173 <Row label="Claude Code" reverse={false} labelStyle={labelStyle}>
178 <Node ref={agent1} style={nodeStyle}><Icons.claude /></Node>174 <Node ref={agent1} style={nodeStyle}>
175 <Icons.claude />
176 </Node>
179 </Row>177 </Row>
180 <Row label="Cursor" reverse={false} labelStyle={labelStyle}>178 <Row label="Cursor" reverse={false} labelStyle={labelStyle}>
181 <Node ref={agent2} style={nodeStyle}><Icons.cursor /></Node>179 <Node ref={agent2} style={nodeStyle}>
180 <Icons.cursor />
181 </Node>
182 </Row>182 </Row>
183 <Row label="Codex" reverse={false} labelStyle={labelStyle}>183 <Row label="Codex" reverse={false} labelStyle={labelStyle}>
184 <Node ref={agent3} style={nodeStyle}><Icons.openai /></Node>184 <Node ref={agent3} style={nodeStyle}>
185 <Icons.openai />
186 </Node>
185 </Row>187 </Row>
186 </div>188 </div>
187 189
188 {/* Hub (center) */}190 {/* Hub (center) */}
189 <div className="flex flex-col justify-center">191 <div className="flex flex-col justify-center">
190 <Node ref={hub} size="lg" style={hubStyle}>192 <Node ref={hub} size="lg" style={hubStyle}>
191 <img src="/favicon-192.png" alt="Executor" className="w-full h-full object-contain" style={variant === "cyber" || variant === "brutalist" ? { filter: "invert(1)" } : undefined} />193 <img
194 src="/favicon-192.png"
195 alt="Executor"
196 className="w-full h-full object-contain"
197 style={
198 variant === "cyber" || variant === "brutalist" ? { filter: "invert(1)" } : undefined
199 }
200 />
192 </Node>201 </Node>
193 </div>202 </div>
194 203
195 {/* Tools (right) */}204 {/* Tools (right) */}
196 <div className="flex flex-col justify-center gap-7">205 <div className="flex flex-col justify-center gap-7">
197 <Row label="Sentry" sub="OpenAPI" reverse labelStyle={labelStyle} subStyle={subStyle}>206 <Row label="Sentry" sub="OpenAPI" reverse labelStyle={labelStyle} subStyle={subStyle}>
198 <Node ref={tool1} style={nodeStyle}><Icons.sentry /></Node>207 <Node ref={tool1} style={nodeStyle}>
208 <Icons.sentry />
209 </Node>
199 </Row>210 </Row>
200 <Row label="GitHub" sub="GraphQL" reverse labelStyle={labelStyle} subStyle={subStyle}>211 <Row label="GitHub" sub="GraphQL" reverse labelStyle={labelStyle} subStyle={subStyle}>
201 <Node ref={tool2} style={nodeStyle}><Icons.github /></Node>212 <Node ref={tool2} style={nodeStyle}>
213 <Icons.github />
214 </Node>
202 </Row>215 </Row>
203 <Row label="Linear" sub="MCP" reverse labelStyle={labelStyle} subStyle={subStyle}>216 <Row label="Linear" sub="MCP" reverse labelStyle={labelStyle} subStyle={subStyle}>
204 <Node ref={tool3} style={nodeStyle}><Icons.linear /></Node>217 <Node ref={tool3} style={nodeStyle}>
218 <Icons.linear />
219 </Node>
205 </Row>220 </Row>
206 </div>221 </div>
207 </div>222 </div>
208 223
209 {/* Beams: agents → hub */}224 {/* Beams: agents → hub */}
210 <AnimatedBeam containerRef={containerRef} fromRef={agent1} toRef={hub}225 <AnimatedBeam
211 curvature={-50} pathColor={pathColor} pathOpacity={pathOpacity} pathWidth={pathWidth}226 containerRef={containerRef}
212 gradientStartColor={beamColor} gradientStopColor={beamColor} duration={4} />227 fromRef={agent1}
213 <AnimatedBeam containerRef={containerRef} fromRef={agent2} toRef={hub}228 toRef={hub}
214 curvature={0} pathColor={pathColor} pathOpacity={pathOpacity} pathWidth={pathWidth}229 curvature={-50}
215 gradientStartColor={beamColor} gradientStopColor={beamColor} duration={4} delay={0.3} />230 pathColor={pathColor}
216 <AnimatedBeam containerRef={containerRef} fromRef={agent3} toRef={hub}231 pathOpacity={pathOpacity}
217 curvature={50} pathColor={pathColor} pathOpacity={pathOpacity} pathWidth={pathWidth}232 pathWidth={pathWidth}
218 gradientStartColor={beamColor} gradientStopColor={beamColor} duration={4} delay={0.6} />233 gradientStartColor={beamColor}
234 gradientStopColor={beamColor}
235 duration={4}
236 />
237 <AnimatedBeam
238 containerRef={containerRef}
239 fromRef={agent2}
240 toRef={hub}
241 curvature={0}
242 pathColor={pathColor}
243 pathOpacity={pathOpacity}
244 pathWidth={pathWidth}
245 gradientStartColor={beamColor}
246 gradientStopColor={beamColor}
247 duration={4}
248 delay={0.3}
249 />
250 <AnimatedBeam
251 containerRef={containerRef}
252 fromRef={agent3}
253 toRef={hub}
254 curvature={50}
255 pathColor={pathColor}
256 pathOpacity={pathOpacity}
257 pathWidth={pathWidth}
258 gradientStartColor={beamColor}
259 gradientStopColor={beamColor}
260 duration={4}
261 delay={0.6}
262 />
219 263
220 {/* Beams: hub → tools */}264 {/* Beams: hub → tools */}
221 <AnimatedBeam containerRef={containerRef} fromRef={hub} toRef={tool1}265 <AnimatedBeam
222 curvature={50} pathColor={pathColor} pathOpacity={pathOpacity} pathWidth={pathWidth}266 containerRef={containerRef}
223 gradientStartColor={beamColor} gradientStopColor={beamColor} duration={4} delay={0.15} />267 fromRef={hub}
224 <AnimatedBeam containerRef={containerRef} fromRef={hub} toRef={tool2}268 toRef={tool1}
225 curvature={0} pathColor={pathColor} pathOpacity={pathOpacity} pathWidth={pathWidth}269 curvature={50}
226 gradientStartColor={beamColor} gradientStopColor={beamColor} duration={4} delay={0.45} />270 pathColor={pathColor}
227 <AnimatedBeam containerRef={containerRef} fromRef={hub} toRef={tool3}271 pathOpacity={pathOpacity}
228 curvature={-50} pathColor={pathColor} pathOpacity={pathOpacity} pathWidth={pathWidth}272 pathWidth={pathWidth}
229 gradientStartColor={beamColor} gradientStopColor={beamColor} duration={4} delay={0.75} />273 gradientStartColor={beamColor}
274 gradientStopColor={beamColor}
275 duration={4}
276 delay={0.15}
277 />
278 <AnimatedBeam
279 containerRef={containerRef}
280 fromRef={hub}
281 toRef={tool2}
282 curvature={0}
283 pathColor={pathColor}
284 pathOpacity={pathOpacity}
285 pathWidth={pathWidth}
286 gradientStartColor={beamColor}
287 gradientStopColor={beamColor}
288 duration={4}
289 delay={0.45}
290 />
291 <AnimatedBeam
292 containerRef={containerRef}
293 fromRef={hub}
294 toRef={tool3}
295 curvature={-50}
296 pathColor={pathColor}
297 pathOpacity={pathOpacity}
298 pathWidth={pathWidth}
299 gradientStartColor={beamColor}
300 gradientStopColor={beamColor}
301 duration={4}
302 delay={0.75}
303 />
230 </div>304 </div>
231 )305 );
232}306}
233 307
234function Row({308function Row({
@@ -239,41 +313,59 @@ function Row({ · 4 unmodified lines
239 labelStyle,313 labelStyle,
240 subStyle,314 subStyle,
241}: {315}: {
242 children: React.ReactNode316 children: React.ReactNode;
243 label: string317 label: string;
244 sub: string318 sub?: string;
245 reverse: boolean319 reverse: boolean;
246 labelStyle?: React.CSSProperties320 labelStyle?: React.CSSProperties;
247 subStyle?: React.CSSProperties321 subStyle?: React.CSSProperties;
248}) {322}) {
249 return (323 return (
250 <div className={cn("flex items-center gap-3", reverse && "flex-row-reverse text-right")}>324 <div className={cn("flex items-center gap-3", reverse && "flex-row-reverse text-right")}>
251 {children}325 {children}
252 <div className="flex flex-col leading-tight">326 <div className="flex flex-col leading-tight">
253 <span className="text-[12px] font-medium" style={labelStyle}>{label}</span>327 <span className="text-[12px] font-medium" style={labelStyle}>
254 <span className="font-mono text-[10px] tracking-tight" style={subStyle}>{sub}</span>328 {label}
329 </span>
330 {sub ? (
331 <span className="font-mono text-[10px] tracking-tight" style={subStyle}>
332 {sub}
333 </span>
334 ) : null}
255 </div>335 </div>
256 </div>336 </div>
257 )337 );
258}338}
259 339
260const Icons = {340const Icons = {
261 sentry: () => (341 sentry: () => (
262 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#362D59]"><path d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z"/></svg>342 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#362D59]">
343 <path d="M13.91 2.505c-.873-1.448-2.972-1.448-3.844 0L6.904 7.92a15.478 15.478 0 0 1 8.53 12.811h-2.221A13.301 13.301 0 0 0 5.784 9.814l-2.926 5.06a7.65 7.65 0 0 1 4.435 5.848H2.194a.365.365 0 0 1-.298-.534l1.413-2.402a5.16 5.16 0 0 0-1.614-.913L.296 19.275a2.182 2.182 0 0 0 .812 2.999 2.24 2.24 0 0 0 1.086.288h6.983a9.322 9.322 0 0 0-3.845-8.318l1.11-1.922a11.47 11.47 0 0 1 4.95 10.24h5.915a17.242 17.242 0 0 0-7.885-15.28l2.244-3.845a.37.37 0 0 1 .504-.13c.255.14 9.75 16.708 9.928 16.9a.365.365 0 0 1-.327.543h-2.287c.029.612.029 1.223 0 1.831h2.297a2.206 2.206 0 0 0 1.922-3.31z" />
344 </svg>
263 ),345 ),
264 github: () => (346 github: () => (
265 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>347 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]">
348 <path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
349 </svg>
266 ),350 ),
267 linear: () => (351 linear: () => (
268 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#5E6AD2]"><path d="M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z"/></svg>352 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#5E6AD2]">
353 <path d="M2.886 4.18A11.982 11.982 0 0 1 11.99 0C18.624 0 24 5.376 24 12.009c0 3.64-1.62 6.903-4.18 9.105L2.887 4.18ZM1.817 5.626l16.556 16.556c-.524.33-1.075.62-1.65.866L.951 7.277c.247-.575.537-1.126.866-1.65ZM.322 9.163l14.515 14.515c-.71.172-1.443.282-2.195.322L0 11.358a12 12 0 0 1 .322-2.195Zm-.17 4.862 9.823 9.824a12.02 12.02 0 0 1-9.824-9.824Z" />
354 </svg>
269 ),355 ),
270 claude: () => (356 claude: () => (
271 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#D97757]"><path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"/></svg>357 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[#D97757]">
358 <path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z" />
359 </svg>
272 ),360 ),
273 cursor: () => (361 cursor: () => (
274 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]"><path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23"/></svg>362 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]">
363 <path d="M11.503.131 1.891 5.678a.84.84 0 0 0-.42.726v11.188c0 .3.162.575.42.724l9.609 5.55a1 1 0 0 0 .998 0l9.61-5.55a.84.84 0 0 0 .42-.724V6.404a.84.84 0 0 0-.42-.726L12.497.131a1.01 1.01 0 0 0-.996 0M2.657 6.338h18.55c.263 0 .43.287.297.515L12.23 22.918c-.062.107-.229.064-.229-.06V12.335a.59.59 0 0 0-.295-.51l-9.11-5.257c-.109-.063-.064-.23.061-.23" />
364 </svg>
275 ),365 ),
276 openai: () => (366 openai: () => (
277 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]"><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>367 <svg viewBox="0 0 24 24" fill="currentColor" className="w-full h-full text-[var(--color-ink)]">
368 <path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
369 </svg>
278 ),370 ),
279}371};
apps/marketing/src/components/ui/animated-beam.tsx+52−74
@@ -1,30 +1,30 @@
1"use client"1"use client";
2 2
3import { useEffect, useId, useState, type RefObject } from "react"3import { useEffect, useId, useState, type RefObject } from "react";
4import { motion } from "motion/react"4import { motion } from "motion/react";
5 5
6import { cn } from "../../lib/utils"6import { cn } from "../../lib/utils";
7 7
8export interface AnimatedBeamProps {8export interface AnimatedBeamProps {
9 className?: string9 className?: string;
10 containerRef: RefObject<HTMLElement | null> // Container ref10 containerRef: RefObject<HTMLElement | null>; // Container ref
11 fromRef: RefObject<HTMLElement | null>11 fromRef: RefObject<HTMLElement | null>;
12 toRef: RefObject<HTMLElement | null>12 toRef: RefObject<HTMLElement | null>;
13 curvature?: number13 curvature?: number;
14 reverse?: boolean14 reverse?: boolean;
15 pathColor?: string15 pathColor?: string;
16 pathWidth?: number16 pathWidth?: number;
17 pathOpacity?: number17 pathOpacity?: number;
18 gradientStartColor?: string18 gradientStartColor?: string;
19 gradientStopColor?: string19 gradientStopColor?: string;
20 delay?: number20 delay?: number;
21 duration?: number21 duration?: number;
22 repeat?: number22 repeat?: number;
23 repeatDelay?: number23 repeatDelay?: number;
24 startXOffset?: number24 startXOffset?: number;
25 startYOffset?: number25 startYOffset?: number;
26 endXOffset?: number26 endXOffset?: number;
27 endYOffset?: number27 endYOffset?: number;
28}28}
29 29
30export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({30export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({
@@ -48,9 +48,9 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({ · 17 unmodified lines
48 endXOffset = 0,48 endXOffset = 0,
49 endYOffset = 0,49 endYOffset = 0,
50}) => {50}) => {
51 const id = useId()51 const id = useId();
52 const [pathD, setPathD] = useState("")52 const [pathD, setPathD] = useState("");
53 const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 })53 const [svgDimensions, setSvgDimensions] = useState({ width: 0, height: 0 });
54 54
55 // Calculate the gradient coordinates based on the reverse prop55 // Calculate the gradient coordinates based on the reverse prop
56 const gradientCoordinates = reverse56 const gradientCoordinates = reverse
@@ -65,63 +65,48 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({ · 8 unmodified lines
65 x2: ["0%", "100%"],65 x2: ["0%", "100%"],
66 y1: ["0%", "0%"],66 y1: ["0%", "0%"],
67 y2: ["0%", "0%"],67 y2: ["0%", "0%"],
68 }68 };
69 69
70 useEffect(() => {70 useEffect(() => {
71 const updatePath = () => {71 const updatePath = () => {
72 if (containerRef.current && fromRef.current && toRef.current) {72 if (containerRef.current && fromRef.current && toRef.current) {
73 const containerRect = containerRef.current.getBoundingClientRect()73 const containerRect = containerRef.current.getBoundingClientRect();
74 const rectA = fromRef.current.getBoundingClientRect()74 const rectA = fromRef.current.getBoundingClientRect();
75 const rectB = toRef.current.getBoundingClientRect()75 const rectB = toRef.current.getBoundingClientRect();
76 76
77 const svgWidth = containerRect.width77 const svgWidth = containerRect.width;
78 const svgHeight = containerRect.height78 const svgHeight = containerRect.height;
79 setSvgDimensions({ width: svgWidth, height: svgHeight })79 setSvgDimensions({ width: svgWidth, height: svgHeight });
80 80
81 const startX =81 const startX = rectA.left - containerRect.left + rectA.width / 2 + startXOffset;
82 rectA.left - containerRect.left + rectA.width / 2 + startXOffset82 const startY = rectA.top - containerRect.top + rectA.height / 2 + startYOffset;
83 const startY =83 const endX = rectB.left - containerRect.left + rectB.width / 2 + endXOffset;
84 rectA.top - containerRect.top + rectA.height / 2 + startYOffset84 const endY = rectB.top - containerRect.top + rectB.height / 2 + endYOffset;
85 const endX =
86 rectB.left - containerRect.left + rectB.width / 2 + endXOffset
87 const endY =
88 rectB.top - containerRect.top + rectB.height / 2 + endYOffset
89 85
90 const controlY = startY - curvature86 const controlY = startY - curvature;
91 const d = `M ${startX},${startY} Q ${87 const d = `M ${startX},${startY} Q ${(startX + endX) / 2},${controlY} ${endX},${endY}`;
92 (startX + endX) / 288 setPathD(d);
93 },${controlY} ${endX},${endY}`
94 setPathD(d)
95 }89 }
96 }90 };
97 91
98 // Initialize ResizeObserver92 // Initialize ResizeObserver
99 const resizeObserver = new ResizeObserver(() => {93 const resizeObserver = new ResizeObserver(() => {
100 updatePath()94 updatePath();
101 })95 });
102 96
103 // Observe the container element97 // Observe the container element
104 if (containerRef.current) {98 if (containerRef.current) {
105 resizeObserver.observe(containerRef.current)99 resizeObserver.observe(containerRef.current);
106 }100 }
107 101
108 // Call the updatePath initially to set the initial path102 // Call the updatePath initially to set the initial path
109 updatePath()103 updatePath();
110 104
111 // Clean up the observer on component unmount105 // Clean up the observer on component unmount
112 return () => {106 return () => {
113 resizeObserver.disconnect()107 resizeObserver.disconnect();
114 }108 };
115 }, [109 }, [containerRef, fromRef, toRef, curvature, startXOffset, startYOffset, endXOffset, endYOffset]);
116 containerRef,
117 fromRef,
118 toRef,
119 curvature,
120 startXOffset,
121 startYOffset,
122 endXOffset,
123 endYOffset,
124 ])
125 110
126 return (111 return (
127 <svg112 <svg
@@ -129,10 +114,7 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({ · 1 unmodified line
129 width={svgDimensions.width}114 width={svgDimensions.width}
130 height={svgDimensions.height}115 height={svgDimensions.height}
131 xmlns="http://www.w3.org/2000/svg"116 xmlns="http://www.w3.org/2000/svg"
132 className={cn(117 className={cn("pointer-events-none absolute top-0 left-0 transform-gpu stroke-2", className)}
133 "pointer-events-none absolute top-0 left-0 transform-gpu stroke-2",
134 className
135 )}
136 viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}118 viewBox={`0 0 ${svgDimensions.width} ${svgDimensions.height}`}
137 >119 >
138 <path120 <path
@@ -177,13 +159,9 @@ export const AnimatedBeam: React.FC<AnimatedBeamProps> = ({ · 38 unmodified lines
177 <stop stopColor={gradientStartColor} stopOpacity="0"></stop>159 <stop stopColor={gradientStartColor} stopOpacity="0"></stop>
178 <stop stopColor={gradientStartColor}></stop>160 <stop stopColor={gradientStartColor}></stop>
179 <stop offset="32.5%" stopColor={gradientStopColor}></stop>161 <stop offset="32.5%" stopColor={gradientStopColor}></stop>
180 <stop162 <stop offset="100%" stopColor={gradientStopColor} stopOpacity="0"></stop>
181 offset="100%"
182 stopColor={gradientStopColor}
183 stopOpacity="0"
184 ></stop>
185 </motion.linearGradient>163 </motion.linearGradient>
186 </defs>164 </defs>
187 </svg>165 </svg>
188 )166 );
189}167};
apps/marketing/src/lib/utils.ts+3−3
@@ -1,6 +1,6 @@
1import { type ClassValue, clsx } from "clsx"1import { type ClassValue, clsx } from "clsx";
2import { twMerge } from "tailwind-merge"2import { twMerge } from "tailwind-merge";
3 3
4export function cn(...inputs: ClassValue[]) {4export function cn(...inputs: ClassValue[]) {
5 return twMerge(clsx(inputs))5 return twMerge(clsx(inputs));
6}6}
apps/marketing/src/styles/global.css+62−17
@@ -54,7 +54,13 @@ body { · 53 unmodified lines
54 pointer-events: none;54 pointer-events: none;
55 z-index: 1;55 z-index: 1;
56}56}
57.pattern-graph { -webkit-mask-image: url("/pattern-graph-paper.svg"); mask-image: url("/pattern-graph-paper.svg"); -webkit-mask-size: 100px 100px; mask-size: 100px 100px; opacity: 0.08; }57.pattern-graph {
58 -webkit-mask-image: url("/pattern-graph-paper.svg");
59 mask-image: url("/pattern-graph-paper.svg");
60 -webkit-mask-size: 100px 100px;
61 mask-size: 100px 100px;
62 opacity: 0.08;
63}
58 64
59/* Soft fade overlay — paint the surface color back over the pattern's bottom */65/* Soft fade overlay — paint the surface color back over the pattern's bottom */
60.pattern-fade-overlay {66.pattern-fade-overlay {
@@ -76,14 +82,30 @@ body { · 15 unmodified lines
76 82
77/* Page-load reveals */83/* Page-load reveals */
78@keyframes rise {84@keyframes rise {
79 from { opacity: 0; transform: translateY(10px); }85 from {
80 to { opacity: 1; transform: translateY(0); }86 opacity: 0;
81}87 transform: translateY(10px);
82.rise { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; }88 }
83.rise-1 { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.05s both; }89 to {
84.rise-2 { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.13s both; }90 opacity: 1;
85.rise-3 { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.21s both; }91 transform: translateY(0);
86.rise-4 { animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.30s both; }92 }
93}
94.rise {
95 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both;
96}
97.rise-1 {
98 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.05s both;
99}
100.rise-2 {
101 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.13s both;
102}
103.rise-3 {
104 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.21s both;
105}
106.rise-4 {
107 animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) 0.3s both;
108}
87 109
88/* Hairline-bordered surface for the beam panel — minimal elevation */110/* Hairline-bordered surface for the beam panel — minimal elevation */
89.surface-card {111.surface-card {
@@ -106,7 +128,9 @@ body { · 16 unmodified lines
106 letter-spacing: -0.005em;128 letter-spacing: -0.005em;
107 transition: background 0.15s ease;129 transition: background 0.15s ease;
108}130}
109.btn-primary:hover { background: #1f1f24; }131.btn-primary:hover {
132 background: #1f1f24;
133}
110 134
111.btn-link {135.btn-link {
112 color: var(--color-ink-2);136 color: var(--color-ink-2);
@@ -118,7 +142,9 @@ body { · 5 unmodified lines
118 gap: 0.4rem;142 gap: 0.4rem;
119 transition: color 0.15s ease;143 transition: color 0.15s ease;
120}144}
121.btn-link:hover { color: var(--color-ink); }145.btn-link:hover {
146 color: var(--color-ink);
147}
122 148
123/* Legal pages */149/* Legal pages */
124.legal-prose {150.legal-prose {
@@ -126,7 +152,9 @@ body { · 1 unmodified line
126 line-height: 1.75;152 line-height: 1.75;
127 font-size: 15.5px;153 font-size: 15.5px;
128}154}
129.legal-prose > :first-child { margin-top: 0; }155.legal-prose > :first-child {
156 margin-top: 0;
157}
130.legal-prose h2 {158.legal-prose h2 {
131 margin-top: 3rem;159 margin-top: 3rem;
132 margin-bottom: 0.85rem;160 margin-bottom: 0.85rem;
@@ -136,10 +164,24 @@ body { · 3 unmodified lines
136 font-weight: 600;164 font-weight: 600;
137 border-top: 1px solid var(--color-rule);165 border-top: 1px solid var(--color-rule);
138}166}
139.legal-prose h3 { margin-top: 1.6rem; margin-bottom: 0.5rem; font-size: 0.95rem; font-weight: 600; }167.legal-prose h3 {
140.legal-prose p, .legal-prose ul { margin: 0.85rem 0; }168 margin-top: 1.6rem;
141.legal-prose ul { list-style: none; padding-left: 0; }169 margin-bottom: 0.5rem;
142.legal-prose li { position: relative; padding-left: 1.25rem; }170 font-size: 0.95rem;
171 font-weight: 600;
172}
173.legal-prose p,
174.legal-prose ul {
175 margin: 0.85rem 0;
176}
177.legal-prose ul {
178 list-style: none;
179 padding-left: 0;
180}
181.legal-prose li {
182 position: relative;
183 padding-left: 1.25rem;
184}
143.legal-prose li::before {185.legal-prose li::before {
144 content: "";186 content: "";
145 position: absolute;187 position: absolute;
@@ -157,4 +199,7 @@ body { · 11 unmodified lines
157 text-decoration-color: color-mix(in srgb, var(--color-accent) 35%, transparent);199 text-decoration-color: color-mix(in srgb, var(--color-accent) 35%, transparent);
158 text-underline-offset: 3px;200 text-underline-offset: 3px;
159}201}
160.legal-prose strong { color: var(--color-ink); font-weight: 600; }202.legal-prose strong {
203 color: var(--color-ink);
204 font-weight: 600;
205}