mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-24 22:54:46 +08:00
feat: implement the first section of landing page
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
},
|
},
|
||||||
"registries": {
|
"registries": {
|
||||||
"@ai-elements": "https://registry.ai-sdk.dev/{name}.json",
|
"@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-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-hover-card": "^1.1.15",
|
"@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-progress": "^1.1.8",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-select": "^2.2.6",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
"next": "^16.1.4",
|
"next": "^16.1.4",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"nuxt-og-image": "^5.1.13",
|
"nuxt-og-image": "^5.1.13",
|
||||||
|
"ogl": "^1.0.11",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-resizable-panels": "^4.4.1",
|
"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':
|
'@radix-ui/react-hover-card':
|
||||||
specifier: ^1.1.15
|
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)
|
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':
|
'@radix-ui/react-progress':
|
||||||
specifier: ^1.1.8
|
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)
|
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:
|
nuxt-og-image:
|
||||||
specifier: ^5.1.13
|
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))
|
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:
|
react:
|
||||||
specifier: ^19.0.0
|
specifier: ^19.0.0
|
||||||
version: 19.2.3
|
version: 19.2.3
|
||||||
@@ -1163,6 +1169,11 @@ packages:
|
|||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
optional: true
|
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':
|
'@radix-ui/react-id@1.1.1':
|
||||||
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -4127,6 +4138,9 @@ packages:
|
|||||||
ofetch@1.5.1:
|
ofetch@1.5.1:
|
||||||
resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
|
resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==}
|
||||||
|
|
||||||
|
ogl@1.0.11:
|
||||||
|
resolution: {integrity: sha512-kUpC154AFfxi16pmZUK4jk3J+8zxwTWGPo03EoYA8QPbzikHoaC82n6pNTbd+oEaJonaE8aPWBlX7ad9zrqLsA==}
|
||||||
|
|
||||||
ohash@2.0.11:
|
ohash@2.0.11:
|
||||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||||
|
|
||||||
@@ -6137,6 +6151,10 @@ snapshots:
|
|||||||
'@types/react': 19.2.8
|
'@types/react': 19.2.8
|
||||||
'@types/react-dom': 19.2.3(@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)':
|
'@radix-ui/react-id@1.1.1(@types/react@19.2.8)(react@19.2.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.8)(react@19.2.3)
|
'@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
|
node-fetch-native: 1.6.7
|
||||||
ufo: 1.6.2
|
ufo: 1.6.2
|
||||||
|
|
||||||
|
ogl@1.0.11: {}
|
||||||
|
|
||||||
ohash@2.0.11: {}
|
ohash@2.0.11: {}
|
||||||
|
|
||||||
onetime@6.0.0:
|
onetime@6.0.0:
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { detectLocaleServer } from "@/core/i18n/server";
|
|||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Welcome to DeerFlow",
|
title: "Welcome to DeerFlow",
|
||||||
description: "A LangChain-based framework for building super agents.",
|
description: "A LangChain-based framework for building super agents.",
|
||||||
icons: [{ rel: "icon", url: "/favicon.ico" }],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const geist = Geist({
|
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 (
|
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";
|
"use client";
|
||||||
|
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
||||||
|
|
||||||
export function ThemeProvider({
|
export function ThemeProvider({
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof NextThemesProvider>) {
|
}: 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(),
|
: z.string().optional(),
|
||||||
BETTER_AUTH_GITHUB_CLIENT_ID: z.string().optional(),
|
BETTER_AUTH_GITHUB_CLIENT_ID: z.string().optional(),
|
||||||
BETTER_AUTH_GITHUB_CLIENT_SECRET: z.string().optional(),
|
BETTER_AUTH_GITHUB_CLIENT_SECRET: z.string().optional(),
|
||||||
|
GITHUB_OAUTH_TOKEN: z.string().optional(),
|
||||||
NODE_ENV: z
|
NODE_ENV: z
|
||||||
.enum(["development", "test", "production"])
|
.enum(["development", "test", "production"])
|
||||||
.default("development"),
|
.default("development"),
|
||||||
@@ -26,6 +27,7 @@ export const env = createEnv({
|
|||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_BACKEND_BASE_URL: z.string().optional(),
|
NEXT_PUBLIC_BACKEND_BASE_URL: z.string().optional(),
|
||||||
NEXT_PUBLIC_LANGGRAPH_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_BACKEND_BASE_URL: process.env.NEXT_PUBLIC_BACKEND_BASE_URL,
|
||||||
NEXT_PUBLIC_LANGGRAPH_BASE_URL: process.env.NEXT_PUBLIC_LANGGRAPH_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
|
* 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-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--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 {
|
:root {
|
||||||
--radius: 0.625rem;
|
--radius: 0.625rem;
|
||||||
@@ -212,6 +262,22 @@
|
|||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@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 {
|
:root {
|
||||||
|
|||||||
Reference in New Issue
Block a user