From 307972f93ed05d5e8ddd9e1e8b3fbdc662614574 Mon Sep 17 00:00:00 2001 From: Henry Li Date: Fri, 23 Jan 2026 00:15:21 +0800 Subject: [PATCH] feat: implement the first section of landing page --- frontend/components.json | 3 +- frontend/package.json | 2 + frontend/pnpm-lock.yaml | 20 ++ frontend/src/app/layout.tsx | 1 - frontend/src/app/page.tsx | 81 ++++- frontend/src/components/Galaxy.css | 5 + frontend/src/components/Galaxy.jsx | 333 ++++++++++++++++++ frontend/src/components/landing/jumbotron.tsx | 72 ++++ frontend/src/components/theme-provider.tsx | 11 +- frontend/src/components/ui/aurora-text.tsx | 43 +++ frontend/src/components/ui/number-ticker.tsx | 67 ++++ frontend/src/components/ui/word-rotate.tsx | 51 +++ frontend/src/env.js | 5 + frontend/src/styles/globals.css | 70 +++- 14 files changed, 757 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/Galaxy.css create mode 100644 frontend/src/components/Galaxy.jsx create mode 100644 frontend/src/components/landing/jumbotron.tsx create mode 100644 frontend/src/components/ui/aurora-text.tsx create mode 100644 frontend/src/components/ui/number-ticker.tsx create mode 100644 frontend/src/components/ui/word-rotate.tsx diff --git a/frontend/components.json b/frontend/components.json index 63b833f..11cabd4 100644 --- a/frontend/components.json +++ b/frontend/components.json @@ -20,6 +20,7 @@ }, "registries": { "@ai-elements": "https://registry.ai-sdk.dev/{name}.json", - "@magicui": "https://magicui.design/r/{name}" + "@magicui": "https://magicui.design/r/{name}", + "@react-bits": "https://reactbits.dev/r/{name}.json" } } diff --git a/frontend/package.json b/frontend/package.json index af28994..d0dd49e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,6 +30,7 @@ "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-hover-card": "^1.1.15", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-scroll-area": "^1.2.10", "@radix-ui/react-select": "^2.2.6", @@ -63,6 +64,7 @@ "next": "^16.1.4", "next-themes": "^0.4.6", "nuxt-og-image": "^5.1.13", + "ogl": "^1.0.11", "react": "^19.0.0", "react-dom": "^19.0.0", "react-resizable-panels": "^4.4.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 9869369..c888254 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -50,6 +50,9 @@ importers: '@radix-ui/react-hover-card': specifier: ^1.1.15 version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-icons': + specifier: ^1.3.2 + version: 1.3.2(react@19.2.3) '@radix-ui/react-progress': specifier: ^1.1.8 version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -149,6 +152,9 @@ importers: nuxt-og-image: specifier: ^5.1.13 version: 5.1.13(@unhead/vue@2.1.2(vue@3.5.26(typescript@5.9.3)))(unstorage@1.17.4)(vite@7.3.1(@types/node@20.19.29)(jiti@2.6.1)(lightningcss@1.30.2))(vue@3.5.26(typescript@5.9.3)) + ogl: + specifier: ^1.0.11 + version: 1.0.11 react: specifier: ^19.0.0 version: 19.2.3 @@ -1163,6 +1169,11 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-icons@1.3.2': + resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: @@ -4127,6 +4138,9 @@ packages: ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + ogl@1.0.11: + resolution: {integrity: sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -6137,6 +6151,10 @@ snapshots: '@types/react': 19.2.8 '@types/react-dom': 19.2.3(@types/react@19.2.8) + '@radix-ui/react-icons@1.3.2(react@19.2.3)': + dependencies: + react: 19.2.3 + '@radix-ui/react-id@1.1.1(@types/react@19.2.8)(react@19.2.3)': dependencies: '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.8)(react@19.2.3) @@ -9541,6 +9559,8 @@ snapshots: node-fetch-native: 1.6.7 ufo: 1.6.2 + ogl@1.0.11: {} + ohash@2.0.11: {} onetime@6.0.0: diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 4d4b144..56ad4b4 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -10,7 +10,6 @@ import { detectLocaleServer } from "@/core/i18n/server"; export const metadata: Metadata = { title: "Welcome to DeerFlow", description: "A LangChain-based framework for building super agents.", - icons: [{ rel: "icon", url: "/favicon.ico" }], }; const geist = Geist({ diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 75d7e6f..50c4615 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,5 +1,82 @@ -export default async function LandingPage() { +import { StarFilledIcon, GitHubLogoIcon } from "@radix-ui/react-icons"; +import Link from "next/link"; + +import { Jumbotron } from "@/components/landing/jumbotron"; +import { Button } from "@/components/ui/button"; +import { NumberTicker } from "@/components/ui/number-ticker"; +import { env } from "@/env"; + +export default function LandingPage() { return ( -
DeerFlow Landing Page
+
+
+
+

DeerFlow

+
+
+
+ +
+
+
+
+ +
+
+
+ ); +} + +export async function StarCounter() { + let stars = 10000; // Default value + + try { + const response = await fetch( + "https://api.github.com/repos/bytedance/deer-flow", + { + headers: env.GITHUB_OAUTH_TOKEN + ? { + Authorization: `Bearer ${env.GITHUB_OAUTH_TOKEN}`, + "Content-Type": "application/json", + } + : {}, + next: { + revalidate: 3600, + }, + }, + ); + + if (response.ok) { + const data = await response.json(); + stars = data.stargazers_count ?? stars; // Update stars if API response is valid + } + } catch (error) { + console.error("Error fetching GitHub stars:", error); + } + return ( + <> + + {stars && ( + + )} + ); } diff --git a/frontend/src/components/Galaxy.css b/frontend/src/components/Galaxy.css new file mode 100644 index 0000000..0638097 --- /dev/null +++ b/frontend/src/components/Galaxy.css @@ -0,0 +1,5 @@ +.galaxy-container { + width: 100%; + height: 100%; + position: relative; +} diff --git a/frontend/src/components/Galaxy.jsx b/frontend/src/components/Galaxy.jsx new file mode 100644 index 0000000..53b01cd --- /dev/null +++ b/frontend/src/components/Galaxy.jsx @@ -0,0 +1,333 @@ +import { Renderer, Program, Mesh, Color, Triangle } from 'ogl'; +import { useEffect, useRef } from 'react'; +import './Galaxy.css'; + +const vertexShader = ` +attribute vec2 uv; +attribute vec2 position; + +varying vec2 vUv; + +void main() { + vUv = uv; + gl_Position = vec4(position, 0, 1); +} +`; + +const fragmentShader = ` +precision highp float; + +uniform float uTime; +uniform vec3 uResolution; +uniform vec2 uFocal; +uniform vec2 uRotation; +uniform float uStarSpeed; +uniform float uDensity; +uniform float uHueShift; +uniform float uSpeed; +uniform vec2 uMouse; +uniform float uGlowIntensity; +uniform float uSaturation; +uniform bool uMouseRepulsion; +uniform float uTwinkleIntensity; +uniform float uRotationSpeed; +uniform float uRepulsionStrength; +uniform float uMouseActiveFactor; +uniform float uAutoCenterRepulsion; +uniform bool uTransparent; + +varying vec2 vUv; + +#define NUM_LAYER 4.0 +#define STAR_COLOR_CUTOFF 0.2 +#define MAT45 mat2(0.7071, -0.7071, 0.7071, 0.7071) +#define PERIOD 3.0 + +float Hash21(vec2 p) { + p = fract(p * vec2(123.34, 456.21)); + p += dot(p, p + 45.32); + return fract(p.x * p.y); +} + +float tri(float x) { + return abs(fract(x) * 2.0 - 1.0); +} + +float tris(float x) { + float t = fract(x); + return 1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0)); +} + +float trisn(float x) { + float t = fract(x); + return 2.0 * (1.0 - smoothstep(0.0, 1.0, abs(2.0 * t - 1.0))) - 1.0; +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +float Star(vec2 uv, float flare) { + float d = length(uv); + float m = (0.05 * uGlowIntensity) / d; + float rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0)); + m += rays * flare * uGlowIntensity; + uv *= MAT45; + rays = smoothstep(0.0, 1.0, 1.0 - abs(uv.x * uv.y * 1000.0)); + m += rays * 0.3 * flare * uGlowIntensity; + m *= smoothstep(1.0, 0.2, d); + return m; +} + +vec3 StarLayer(vec2 uv) { + vec3 col = vec3(0.0); + + vec2 gv = fract(uv) - 0.5; + vec2 id = floor(uv); + + for (int y = -1; y <= 1; y++) { + for (int x = -1; x <= 1; x++) { + vec2 offset = vec2(float(x), float(y)); + vec2 si = id + vec2(float(x), float(y)); + float seed = Hash21(si); + float size = fract(seed * 345.32); + float glossLocal = tri(uStarSpeed / (PERIOD * seed + 1.0)); + float flareSize = smoothstep(0.9, 1.0, size) * glossLocal; + + float red = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 1.0)) + STAR_COLOR_CUTOFF; + float blu = smoothstep(STAR_COLOR_CUTOFF, 1.0, Hash21(si + 3.0)) + STAR_COLOR_CUTOFF; + float grn = min(red, blu) * seed; + vec3 base = vec3(red, grn, blu); + + float hue = atan(base.g - base.r, base.b - base.r) / (2.0 * 3.14159) + 0.5; + hue = fract(hue + uHueShift / 360.0); + float sat = length(base - vec3(dot(base, vec3(0.299, 0.587, 0.114)))) * uSaturation; + float val = max(max(base.r, base.g), base.b); + base = hsv2rgb(vec3(hue, sat, val)); + + vec2 pad = vec2(tris(seed * 34.0 + uTime * uSpeed / 10.0), tris(seed * 38.0 + uTime * uSpeed / 30.0)) - 0.5; + + float star = Star(gv - offset - pad, flareSize); + vec3 color = base; + + float twinkle = trisn(uTime * uSpeed + seed * 6.2831) * 0.5 + 1.0; + twinkle = mix(1.0, twinkle, uTwinkleIntensity); + star *= twinkle; + + col += star * size * color; + } + } + + return col; +} + +void main() { + vec2 focalPx = uFocal * uResolution.xy; + vec2 uv = (vUv * uResolution.xy - focalPx) / uResolution.y; + + vec2 mouseNorm = uMouse - vec2(0.5); + + if (uAutoCenterRepulsion > 0.0) { + vec2 centerUV = vec2(0.0, 0.0); + float centerDist = length(uv - centerUV); + vec2 repulsion = normalize(uv - centerUV) * (uAutoCenterRepulsion / (centerDist + 0.1)); + uv += repulsion * 0.05; + } else if (uMouseRepulsion) { + vec2 mousePosUV = (uMouse * uResolution.xy - focalPx) / uResolution.y; + float mouseDist = length(uv - mousePosUV); + vec2 repulsion = normalize(uv - mousePosUV) * (uRepulsionStrength / (mouseDist + 0.1)); + uv += repulsion * 0.05 * uMouseActiveFactor; + } else { + vec2 mouseOffset = mouseNorm * 0.1 * uMouseActiveFactor; + uv += mouseOffset; + } + + float autoRotAngle = uTime * uRotationSpeed; + mat2 autoRot = mat2(cos(autoRotAngle), -sin(autoRotAngle), sin(autoRotAngle), cos(autoRotAngle)); + uv = autoRot * uv; + + uv = mat2(uRotation.x, -uRotation.y, uRotation.y, uRotation.x) * uv; + + vec3 col = vec3(0.0); + + for (float i = 0.0; i < 1.0; i += 1.0 / NUM_LAYER) { + float depth = fract(i + uStarSpeed * uSpeed); + float scale = mix(20.0 * uDensity, 0.5 * uDensity, depth); + float fade = depth * smoothstep(1.0, 0.9, depth); + col += StarLayer(uv * scale + i * 453.32) * fade; + } + + if (uTransparent) { + float alpha = length(col); + alpha = smoothstep(0.0, 0.3, alpha); + alpha = min(alpha, 1.0); + gl_FragColor = vec4(col, alpha); + } else { + gl_FragColor = vec4(col, 1.0); + } +} +`; + +export default function Galaxy({ + focal = [0.5, 0.5], + rotation = [1.0, 0.0], + starSpeed = 0.5, + density = 1, + hueShift = 140, + disableAnimation = false, + speed = 1.0, + mouseInteraction = true, + glowIntensity = 0.3, + saturation = 0.0, + mouseRepulsion = true, + repulsionStrength = 2, + twinkleIntensity = 0.3, + rotationSpeed = 0.1, + autoCenterRepulsion = 0, + transparent = true, + ...rest +}) { + const ctnDom = useRef(null); + const targetMousePos = useRef({ x: 0.5, y: 0.5 }); + const smoothMousePos = useRef({ x: 0.5, y: 0.5 }); + const targetMouseActive = useRef(0.0); + const smoothMouseActive = useRef(0.0); + + useEffect(() => { + if (!ctnDom.current) return; + const ctn = ctnDom.current; + const renderer = new Renderer({ + alpha: transparent, + premultipliedAlpha: false + }); + const gl = renderer.gl; + + if (transparent) { + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.clearColor(0, 0, 0, 0); + } else { + gl.clearColor(0, 0, 0, 1); + } + + let program; + + function resize() { + const scale = 1; + renderer.setSize(ctn.offsetWidth * scale, ctn.offsetHeight * scale); + if (program) { + program.uniforms.uResolution.value = new Color( + gl.canvas.width, + gl.canvas.height, + gl.canvas.width / gl.canvas.height + ); + } + } + window.addEventListener('resize', resize, false); + resize(); + + const geometry = new Triangle(gl); + program = new Program(gl, { + vertex: vertexShader, + fragment: fragmentShader, + uniforms: { + uTime: { value: 0 }, + uResolution: { + value: new Color(gl.canvas.width, gl.canvas.height, gl.canvas.width / gl.canvas.height) + }, + uFocal: { value: new Float32Array(focal) }, + uRotation: { value: new Float32Array(rotation) }, + uStarSpeed: { value: starSpeed }, + uDensity: { value: density }, + uHueShift: { value: hueShift }, + uSpeed: { value: speed }, + uMouse: { + value: new Float32Array([smoothMousePos.current.x, smoothMousePos.current.y]) + }, + uGlowIntensity: { value: glowIntensity }, + uSaturation: { value: saturation }, + uMouseRepulsion: { value: mouseRepulsion }, + uTwinkleIntensity: { value: twinkleIntensity }, + uRotationSpeed: { value: rotationSpeed }, + uRepulsionStrength: { value: repulsionStrength }, + uMouseActiveFactor: { value: 0.0 }, + uAutoCenterRepulsion: { value: autoCenterRepulsion }, + uTransparent: { value: transparent } + } + }); + + const mesh = new Mesh(gl, { geometry, program }); + let animateId; + + function update(t) { + animateId = requestAnimationFrame(update); + if (!disableAnimation) { + program.uniforms.uTime.value = t * 0.001; + program.uniforms.uStarSpeed.value = (t * 0.001 * starSpeed) / 10.0; + } + + const lerpFactor = 0.05; + smoothMousePos.current.x += (targetMousePos.current.x - smoothMousePos.current.x) * lerpFactor; + smoothMousePos.current.y += (targetMousePos.current.y - smoothMousePos.current.y) * lerpFactor; + + smoothMouseActive.current += (targetMouseActive.current - smoothMouseActive.current) * lerpFactor; + + program.uniforms.uMouse.value[0] = smoothMousePos.current.x; + program.uniforms.uMouse.value[1] = smoothMousePos.current.y; + program.uniforms.uMouseActiveFactor.value = smoothMouseActive.current; + + renderer.render({ scene: mesh }); + } + animateId = requestAnimationFrame(update); + ctn.appendChild(gl.canvas); + + function handleMouseMove(e) { + const rect = ctn.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = 1.0 - (e.clientY - rect.top) / rect.height; + targetMousePos.current = { x, y }; + targetMouseActive.current = 1.0; + } + + function handleMouseLeave() { + targetMouseActive.current = 0.0; + } + + if (mouseInteraction) { + ctn.addEventListener('mousemove', handleMouseMove); + ctn.addEventListener('mouseleave', handleMouseLeave); + } + + return () => { + cancelAnimationFrame(animateId); + window.removeEventListener('resize', resize); + if (mouseInteraction) { + ctn.removeEventListener('mousemove', handleMouseMove); + ctn.removeEventListener('mouseleave', handleMouseLeave); + } + ctn.removeChild(gl.canvas); + gl.getExtension('WEBGL_lose_context')?.loseContext(); + }; + }, [ + focal, + rotation, + starSpeed, + density, + hueShift, + disableAnimation, + speed, + mouseInteraction, + glowIntensity, + saturation, + mouseRepulsion, + twinkleIntensity, + rotationSpeed, + repulsionStrength, + autoCenterRepulsion, + transparent + ]); + + return
; +} diff --git a/frontend/src/components/landing/jumbotron.tsx b/frontend/src/components/landing/jumbotron.tsx new file mode 100644 index 0000000..f978e65 --- /dev/null +++ b/frontend/src/components/landing/jumbotron.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { ChevronRightIcon } from "lucide-react"; + +import Galaxy from "@/components/Galaxy"; +import { Button } from "@/components/ui/button"; +import { FlickeringGrid } from "@/components/ui/flickering-grid"; +import { WordRotate } from "@/components/ui/word-rotate"; +import { cn } from "@/lib/utils"; + +export function Jumbotron({ className }: { className?: string }) { + return ( +
+
+ +
+ +
+

+ {" "} +
with DeerFlow
+

+

+ DeerFlow is an open-source SuperAgent that researches, codes, and +
+ creates. With the help of sandboxes, tools and skills, it handles +
+ different levels of tasks that could take minutes to hours. +

+ +
+
+ ); +} diff --git a/frontend/src/components/theme-provider.tsx b/frontend/src/components/theme-provider.tsx index f2df7ce..52dd2bb 100644 --- a/frontend/src/components/theme-provider.tsx +++ b/frontend/src/components/theme-provider.tsx @@ -1,10 +1,19 @@ "use client"; +import { usePathname } from "next/navigation"; import { ThemeProvider as NextThemesProvider } from "next-themes"; export function ThemeProvider({ children, ...props }: React.ComponentProps) { - return {children}; + const pathname = usePathname(); + return ( + + {children} + + ); } diff --git a/frontend/src/components/ui/aurora-text.tsx b/frontend/src/components/ui/aurora-text.tsx new file mode 100644 index 0000000..85857c1 --- /dev/null +++ b/frontend/src/components/ui/aurora-text.tsx @@ -0,0 +1,43 @@ +"use client" + +import React, { memo } from "react" + +interface AuroraTextProps { + children: React.ReactNode + className?: string + colors?: string[] + speed?: number +} + +export const AuroraText = memo( + ({ + children, + className = "", + colors = ["#FF0080", "#7928CA", "#0070F3", "#38bdf8"], + speed = 1, + }: AuroraTextProps) => { + const gradientStyle = { + backgroundImage: `linear-gradient(135deg, ${colors.join(", ")}, ${ + colors[0] + })`, + WebkitBackgroundClip: "text", + WebkitTextFillColor: "transparent", + animationDuration: `${10 / speed}s`, + } + + return ( + + {children} + + + ) + } +) + +AuroraText.displayName = "AuroraText" diff --git a/frontend/src/components/ui/number-ticker.tsx b/frontend/src/components/ui/number-ticker.tsx new file mode 100644 index 0000000..0f12b4f --- /dev/null +++ b/frontend/src/components/ui/number-ticker.tsx @@ -0,0 +1,67 @@ +"use client" + +import { ComponentPropsWithoutRef, useEffect, useRef } from "react" +import { useInView, useMotionValue, useSpring } from "motion/react" + +import { cn } from "@/lib/utils" + +interface NumberTickerProps extends ComponentPropsWithoutRef<"span"> { + value: number + startValue?: number + direction?: "up" | "down" + delay?: number + decimalPlaces?: number +} + +export function NumberTicker({ + value, + startValue = 0, + direction = "up", + delay = 0, + className, + decimalPlaces = 0, + ...props +}: NumberTickerProps) { + const ref = useRef(null) + const motionValue = useMotionValue(direction === "down" ? value : startValue) + const springValue = useSpring(motionValue, { + damping: 60, + stiffness: 100, + }) + const isInView = useInView(ref, { once: true, margin: "0px" }) + + useEffect(() => { + if (isInView) { + const timer = setTimeout(() => { + motionValue.set(direction === "down" ? startValue : value) + }, delay * 1000) + return () => clearTimeout(timer) + } + }, [motionValue, isInView, delay, value, direction, startValue]) + + useEffect( + () => + springValue.on("change", (latest) => { + if (ref.current) { + ref.current.textContent = Intl.NumberFormat("en-US", { + minimumFractionDigits: decimalPlaces, + maximumFractionDigits: decimalPlaces, + }).format(Number(latest.toFixed(decimalPlaces))) + } + }), + [springValue, decimalPlaces] + ) + + return ( + + {startValue} + + ) +} diff --git a/frontend/src/components/ui/word-rotate.tsx b/frontend/src/components/ui/word-rotate.tsx new file mode 100644 index 0000000..4796ca0 --- /dev/null +++ b/frontend/src/components/ui/word-rotate.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { AnimatePresence, motion, MotionProps } from "motion/react"; + +import { cn } from "@/lib/utils"; +import { AuroraText } from "./aurora-text"; + +interface WordRotateProps { + words: string[]; + duration?: number; + motionProps?: MotionProps; + className?: string; +} + +export function WordRotate({ + words, + duration = 2500, + motionProps = { + initial: { opacity: 0, y: -50, filter: "blur(16px)" }, + animate: { opacity: 1, y: 0, filter: "blur(0px)" }, + exit: { opacity: 0, y: 50, filter: "blur(16px)" }, + transition: { duration: 0.25, ease: "easeOut" }, + }, + className, +}: WordRotateProps) { + const [index, setIndex] = useState(0); + + useEffect(() => { + const interval = setInterval(() => { + setIndex((prevIndex) => (prevIndex + 1) % words.length); + }, duration); + + // Clean up interval on unmount + return () => clearInterval(interval); + }, [words, duration]); + + return ( +
+ + + {words[index]} + + +
+ ); +} diff --git a/frontend/src/env.js b/frontend/src/env.js index 80d9165..f00fa7a 100644 --- a/frontend/src/env.js +++ b/frontend/src/env.js @@ -13,6 +13,7 @@ export const env = createEnv({ : z.string().optional(), BETTER_AUTH_GITHUB_CLIENT_ID: z.string().optional(), BETTER_AUTH_GITHUB_CLIENT_SECRET: z.string().optional(), + GITHUB_OAUTH_TOKEN: z.string().optional(), NODE_ENV: z .enum(["development", "test", "production"]) .default("development"), @@ -26,6 +27,7 @@ export const env = createEnv({ client: { NEXT_PUBLIC_BACKEND_BASE_URL: z.string().optional(), NEXT_PUBLIC_LANGGRAPH_BASE_URL: z.string().optional(), + NEXT_PUBLIC_STATIC_WEBSITE_ONLY: z.string().optional(), }, /** @@ -41,6 +43,9 @@ export const env = createEnv({ NEXT_PUBLIC_BACKEND_BASE_URL: process.env.NEXT_PUBLIC_BACKEND_BASE_URL, NEXT_PUBLIC_LANGGRAPH_BASE_URL: process.env.NEXT_PUBLIC_LANGGRAPH_BASE_URL, + NEXT_PUBLIC_STATIC_WEBSITE_ONLY: + process.env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY, + GITHUB_OAUTH_TOKEN: process.env.GITHUB_OAUTH_TOKEN, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index 15cd207..1fb06f3 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -133,7 +133,57 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); -} + --animate-aurora: + aurora 8s ease-in-out infinite alternate; + @keyframes aurora { + 0% { + background-position: + 0% 50%; + transform: + rotate(-5deg) scale(0.9); + } + 25% { + background-position: + 50% 100%; + transform: + rotate(5deg) scale(1.1); + } + 50% { + background-position: + 100% 50%; + transform: + rotate(-3deg) scale(0.95); + } + 75% { + background-position: + 50% 0%; + transform: + rotate(3deg) scale(1.05); + } + 100% { + background-position: + 0% 50%; + transform: + rotate(-5deg) scale(0.9); + } + } + --animate-shine: + shine var(--duration) infinite linear +; + @keyframes shine { + 0% { + background-position: + 0% 0%; + } + 50% { + background-position: + 100% 100%; + } + to { + background-position: + 0% 0%; + } + }} :root { --radius: 0.625rem; @@ -212,6 +262,22 @@ body { @apply bg-background text-foreground; } + + .container-md { + width: 100%; + @media (width >= 40rem) { + max-width: 40rem; + } + @media (width >= 48rem) { + max-width: 48rem; + } + @media (width >= 64rem) { + max-width: 64rem; + } + @media (width >= 80rem) { + max-width: 80rem; + } + } } :root { @@ -219,4 +285,4 @@ --container-width-sm: calc(var(--spacing) * 144); --container-width-md: calc(var(--spacing) * 204); --container-width-lg: calc(var(--spacing) * 256); -} +} \ No newline at end of file