mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-03 06:12:14 +08:00
feat: implement the first section of landing page
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
20
frontend/pnpm-lock.yaml
generated
20
frontend/pnpm-lock.yaml
generated
@@ -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:
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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 (
|
||||
<main className="flex min-h-screen flex-col">DeerFlow Landing Page</main>
|
||||
<div className="min-h-screen w-full bg-[#0a0a0a]">
|
||||
<header className="container-md absolute top-0 right-0 left-0 z-10 mx-auto flex h-16 items-center justify-between backdrop-blur-xs">
|
||||
<div className="font-serif text-xl">
|
||||
<h1>DeerFlow</h1>
|
||||
</div>
|
||||
<div className="relative">
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 z-0 h-full w-full rounded-full opacity-30 blur-2xl"
|
||||
style={{
|
||||
background: "linear-gradient(90deg, #ff80b5 0%, #9089fc 100%)",
|
||||
filter: "blur(16px)",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
asChild
|
||||
className="group relative z-10"
|
||||
>
|
||||
<Link href="https://github.com/bytedance/deer-flow" target="_blank">
|
||||
<GitHubLogoIcon className="size-4" />
|
||||
Star on GitHub
|
||||
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" &&
|
||||
env.GITHUB_OAUTH_TOKEN && <StarCounter />}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<hr className="from-border/0 via-border/70 to-border/0 absolute top-16 right-0 left-0 z-10 m-0 h-px w-full border-none bg-linear-to-r" />
|
||||
</header>
|
||||
<main className="w-full">
|
||||
<Jumbotron />
|
||||
</main>
|
||||
<footer className="container-md mx-auto"></footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<>
|
||||
<StarFilledIcon className="size-4 transition-colors duration-300 group-hover:text-yellow-500" />
|
||||
{stars && (
|
||||
<NumberTicker className="font-mono tabular-nums" value={stars} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
5
frontend/src/components/Galaxy.css
Normal file
5
frontend/src/components/Galaxy.css
Normal file
@@ -0,0 +1,5 @@
|
||||
.galaxy-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
333
frontend/src/components/Galaxy.jsx
Normal file
333
frontend/src/components/Galaxy.jsx
Normal file
@@ -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 <div ref={ctnDom} className="galaxy-container" {...rest} />;
|
||||
}
|
||||
72
frontend/src/components/landing/jumbotron.tsx
Normal file
72
frontend/src/components/landing/jumbotron.tsx
Normal file
@@ -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 (
|
||||
<div
|
||||
className={cn(
|
||||
"flex size-full flex-col items-center justify-center",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="absolute inset-0 z-0 bg-black">
|
||||
<Galaxy
|
||||
mouseRepulsion={false}
|
||||
starSpeed={0.2}
|
||||
density={0.6}
|
||||
glowIntensity={0.3}
|
||||
twinkleIntensity={0.3}
|
||||
speed={0.5}
|
||||
/>
|
||||
</div>
|
||||
<FlickeringGrid
|
||||
className="absolute inset-0 z-0 translate-y-[2vh] mask-[url(/images/deer.svg)] mask-size-[100vw] mask-center mask-no-repeat md:mask-size-[72vh]"
|
||||
squareSize={4}
|
||||
gridGap={4}
|
||||
color={"white"}
|
||||
maxOpacity={0.2}
|
||||
flickerChance={0.25}
|
||||
/>
|
||||
<div className="container-md relative z-10 mx-auto flex h-screen flex-col items-center justify-center">
|
||||
<h1 className="flex items-center gap-2 text-4xl font-bold md:text-6xl">
|
||||
<WordRotate
|
||||
words={[
|
||||
"Do Anything",
|
||||
"Learn Anything",
|
||||
"Deep Research",
|
||||
"Collect Data",
|
||||
"Analyze Data",
|
||||
"Generate Webpages",
|
||||
"Vibe Coding",
|
||||
"Generate Slides",
|
||||
"Generate Images",
|
||||
"Generate Podcasts",
|
||||
"Generate Videos",
|
||||
"Generate Songs",
|
||||
"Organize Emails",
|
||||
]}
|
||||
/>{" "}
|
||||
<div>with DeerFlow</div>
|
||||
</h1>
|
||||
<p className="mt-8 scale-105 text-center text-2xl opacity-70">
|
||||
DeerFlow is an open-source SuperAgent that researches, codes, and
|
||||
<br />
|
||||
creates. With the help of sandboxes, tools and skills, it handles
|
||||
<br />
|
||||
different levels of tasks that could take minutes to hours.
|
||||
</p>
|
||||
<Button className="size-lg mt-8 scale-108" size="lg">
|
||||
<span className="text-md">Get Started with 2.0</span>
|
||||
<ChevronRightIcon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<typeof NextThemesProvider>) {
|
||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
||||
const pathname = usePathname();
|
||||
return (
|
||||
<NextThemesProvider
|
||||
{...props}
|
||||
forcedTheme={pathname === "/" ? "dark" : undefined}
|
||||
>
|
||||
{children}
|
||||
</NextThemesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
43
frontend/src/components/ui/aurora-text.tsx
Normal file
43
frontend/src/components/ui/aurora-text.tsx
Normal file
@@ -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 (
|
||||
<span className={`relative inline-block ${className}`}>
|
||||
<span className="sr-only">{children}</span>
|
||||
<span
|
||||
className="animate-aurora relative bg-size-[200%_auto] bg-clip-text text-transparent"
|
||||
style={gradientStyle}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
AuroraText.displayName = "AuroraText"
|
||||
67
frontend/src/components/ui/number-ticker.tsx
Normal file
67
frontend/src/components/ui/number-ticker.tsx
Normal file
@@ -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<HTMLSpanElement>(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 (
|
||||
<span
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-block tracking-wider text-black tabular-nums dark:text-white",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{startValue}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
51
frontend/src/components/ui/word-rotate.tsx
Normal file
51
frontend/src/components/ui/word-rotate.tsx
Normal file
@@ -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 (
|
||||
<div className="overflow-hidden py-2">
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.h1
|
||||
key={words[index]}
|
||||
className={cn(className)}
|
||||
{...motionProps}
|
||||
>
|
||||
<AuroraText>{words[index]}</AuroraText>
|
||||
</motion.h1>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user