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
+
+
+
+
+
+
+ Star on GitHub
+ {env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" &&
+ env.GITHUB_OAUTH_TOKEN && }
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+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.
+
+
+ Get Started with 2.0
+
+
+
+
+ );
+}
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}
+
+ {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