From 21ac410780d36e29c6e50f17aa1d9aa4728e825c Mon Sep 17 00:00:00 2001
From: Oleg Proskurin
Date: Wed, 3 Dec 2025 15:08:36 +0700
Subject: [PATCH] fix: page
---
apps/landing/package.json | 13 +-
apps/landing/src/app/globals.css | 9 +
.../components/AnimatedGradientBorder.tsx | 55 ++++
apps/landing/src/app/homepage/page.tsx | 259 +++++++++++-------
pnpm-lock.yaml | 7 +
5 files changed, 235 insertions(+), 108 deletions(-)
create mode 100644 apps/landing/src/app/homepage/components/AnimatedGradientBorder.tsx
diff --git a/apps/landing/package.json b/apps/landing/package.json
index 0f92f17..6b93aea 100644
--- a/apps/landing/package.json
+++ b/apps/landing/package.json
@@ -10,17 +10,18 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
- "react": "19.1.0",
- "react-dom": "19.1.0",
+ "@banatie/database": "workspace:*",
+ "lucide-react": "^0.400.0",
"next": "15.5.4",
- "@banatie/database": "workspace:*"
+ "react": "19.1.0",
+ "react-dom": "19.1.0"
},
"devDependencies": {
- "typescript": "^5",
+ "@tailwindcss/postcss": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "@tailwindcss/postcss": "^4",
- "tailwindcss": "^4"
+ "tailwindcss": "^4",
+ "typescript": "^5"
}
}
diff --git a/apps/landing/src/app/globals.css b/apps/landing/src/app/globals.css
index bafa22b..63d1204 100644
--- a/apps/landing/src/app/globals.css
+++ b/apps/landing/src/app/globals.css
@@ -45,6 +45,15 @@ body {
}
}
+@keyframes gradient-rotate {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
.animate-gradient {
background-size: 200% 200%;
animation: gradient-shift 3s ease infinite;
diff --git a/apps/landing/src/app/homepage/components/AnimatedGradientBorder.tsx b/apps/landing/src/app/homepage/components/AnimatedGradientBorder.tsx
new file mode 100644
index 0000000..3a9e12d
--- /dev/null
+++ b/apps/landing/src/app/homepage/components/AnimatedGradientBorder.tsx
@@ -0,0 +1,55 @@
+interface AnimatedGradientBorderProps {
+ children: React.ReactNode;
+ className?: string;
+}
+
+const gradientStyle: React.CSSProperties = {
+ background: `conic-gradient(
+ from 0deg,
+ rgba(99, 102, 241, 0.3),
+ rgba(139, 92, 246, 0.8),
+ rgba(236, 72, 153, 0.6),
+ rgba(34, 211, 238, 0.8),
+ rgba(99, 102, 241, 0.3)
+ )`,
+ filter: 'blur(40px)',
+};
+
+const glowStyle: React.CSSProperties = {
+ background: `conic-gradient(
+ from 0deg,
+ rgba(99, 102, 241, 0.1),
+ rgba(139, 92, 246, 0.4),
+ rgba(236, 72, 153, 0.3),
+ rgba(34, 211, 238, 0.4),
+ rgba(99, 102, 241, 0.1)
+ )`,
+ filter: 'blur(15px)',
+};
+
+export const AnimatedGradientBorder = ({
+ children,
+ className = '',
+}: AnimatedGradientBorderProps) => {
+ return (
+
+ );
+};
diff --git a/apps/landing/src/app/homepage/page.tsx b/apps/landing/src/app/homepage/page.tsx
index 9d7668d..566aefc 100644
--- a/apps/landing/src/app/homepage/page.tsx
+++ b/apps/landing/src/app/homepage/page.tsx
@@ -1,6 +1,7 @@
'use client';
import { useState } from 'react';
+import { AnimatedGradientBorder } from './components/AnimatedGradientBorder';
import {
Zap,
Globe,
@@ -42,12 +43,6 @@ import {
const customStyles = `
@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@500;600;700&display=swap');
- @property --form-angle {
- syntax: "";
- initial-value: 0deg;
- inherits: false;
- }
-
.gradient-text {
background: linear-gradient(90deg, #818cf8 0%, #c084fc 25%, #f472b6 50%, #c084fc 75%, #818cf8 100%);
background-size: 200% 100%;
@@ -63,47 +58,6 @@ const customStyles = `
100% { background-position: 100% 50%; }
}
- .email-form-wrapper {
- position: relative;
- max-width: 28rem;
- margin: 0 auto;
- padding: 2px;
- border-radius: 12px;
- background: linear-gradient(#0a0612, #0a0612) padding-box,
- conic-gradient(from var(--form-angle),
- rgba(99, 102, 241, 0.3),
- rgba(139, 92, 246, 0.8),
- rgba(236, 72, 153, 0.6),
- rgba(34, 211, 238, 0.8),
- rgba(99, 102, 241, 0.3)
- ) border-box;
- animation: form-glow-rotate 4s linear infinite;
- }
-
- .email-form-wrapper::before {
- content: '';
- position: absolute;
- inset: -2px;
- border-radius: 14px;
- background: conic-gradient(from var(--form-angle),
- rgba(99, 102, 241, 0.1),
- rgba(139, 92, 246, 0.4),
- rgba(236, 72, 153, 0.3),
- rgba(34, 211, 238, 0.4),
- rgba(99, 102, 241, 0.1)
- );
- filter: blur(15px);
- opacity: 0.6;
- z-index: -1;
- animation: form-glow-rotate 4s linear infinite;
- }
-
- @keyframes form-glow-rotate {
- to {
- --form-angle: 360deg;
- }
- }
-
.beta-dot {
animation: beta-dot-delay 20s linear forwards, beta-dot-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) 20s infinite;
}
@@ -138,12 +92,30 @@ const customStyles = `
function BackgroundBlobs() {
const blobs = [
- { className: 'w-[600px] h-[600px] top-[-200px] right-[-100px]', gradient: 'rgba(139, 92, 246, 0.3)' },
- { className: 'w-[500px] h-[500px] top-[800px] left-[-150px]', gradient: 'rgba(99, 102, 241, 0.25)' },
- { className: 'w-[400px] h-[400px] top-[1600px] right-[-100px]', gradient: 'rgba(236, 72, 153, 0.2)' },
- { className: 'w-[550px] h-[550px] top-[2400px] left-[-200px]', gradient: 'rgba(34, 211, 238, 0.15)' },
- { className: 'w-[450px] h-[450px] top-[3200px] right-[-150px]', gradient: 'rgba(139, 92, 246, 0.25)' },
- { className: 'w-[500px] h-[500px] top-[4000px] left-[-100px]', gradient: 'rgba(99, 102, 241, 0.2)' },
+ {
+ className: 'w-[600px] h-[600px] top-[-200px] right-[-100px]',
+ gradient: 'rgba(139, 92, 246, 0.3)',
+ },
+ {
+ className: 'w-[500px] h-[500px] top-[800px] left-[-150px]',
+ gradient: 'rgba(99, 102, 241, 0.25)',
+ },
+ {
+ className: 'w-[400px] h-[400px] top-[1600px] right-[-100px]',
+ gradient: 'rgba(236, 72, 153, 0.2)',
+ },
+ {
+ className: 'w-[550px] h-[550px] top-[2400px] left-[-200px]',
+ gradient: 'rgba(34, 211, 238, 0.15)',
+ },
+ {
+ className: 'w-[450px] h-[450px] top-[3200px] right-[-150px]',
+ gradient: 'rgba(139, 92, 246, 0.25)',
+ },
+ {
+ className: 'w-[500px] h-[500px] top-[4000px] left-[-100px]',
+ gradient: 'rgba(99, 102, 241, 0.2)',
+ },
];
return (
@@ -164,7 +136,8 @@ function HeroGlow() {
);
@@ -215,7 +188,7 @@ function HeroSection() {
{/* Email Form */}
-
+
@@ -438,8 +435,12 @@ function HowItWorksSection() {
return (
-
Your prompt. Your control. Production-ready.
-
We handle the complexity so you can focus on building.
+
+ Your prompt. Your control. Production-ready.
+
+
+ We handle the complexity so you can focus on building.
+
@@ -491,42 +492,48 @@ function KeyFeaturesSection() {
icon: AtSign,
iconColor: 'text-pink-400',
title: 'Reference Images',
- description: 'Use @aliases to maintain style consistency across your project. Reference up to 3 images per generation.',
+ description:
+ 'Use @aliases to maintain style consistency across your project. Reference up to 3 images per generation.',
isUnique: false,
},
{
icon: GitBranch,
iconColor: 'text-purple-400',
title: 'Flows',
- description: 'Chain generations, iterate on results, build image sequences with @last and @first references.',
+ description:
+ 'Chain generations, iterate on results, build image sequences with @last and @first references.',
isUnique: false,
},
{
icon: Palette,
iconColor: 'text-yellow-400',
title: '7 Style Templates',
- description: 'Same prompt, different styles. Photorealistic, illustration, minimalist, product, comic, sticker, and more.',
+ description:
+ 'Same prompt, different styles. Photorealistic, illustration, minimalist, product, comic, sticker, and more.',
isUnique: false,
},
{
icon: Globe,
iconColor: 'text-green-400',
title: 'Instant CDN Delivery',
- description: 'Every image gets production-ready URL. No upload, no optimization, no hosting setup needed.',
+ description:
+ 'Every image gets production-ready URL. No upload, no optimization, no hosting setup needed.',
isUnique: false,
},
{
icon: SlidersHorizontal,
iconColor: 'text-blue-400',
title: 'Output Control',
- description: 'Control aspect ratio, dimensions, and format. From square thumbnails to ultra-wide banners.',
+ description:
+ 'Control aspect ratio, dimensions, and format. From square thumbnails to ultra-wide banners.',
isUnique: false,
},
{
icon: Link,
iconColor: 'text-cyan-400',
title: 'Prompt URLs',
- description: 'Generate images via URL parameters. Put prompt in img src, get real image. Built-in caching.',
+ description:
+ 'Generate images via URL parameters. Put prompt in img src, get real image. Built-in caching.',
isUnique: true,
},
];
@@ -534,8 +541,12 @@ function KeyFeaturesSection() {
return (
-
Built for real development workflows
-
Everything you need to integrate AI images into your projects.
+
+ Built for real development workflows
+
+
+ Everything you need to integrate AI images into your projects.
+
{features.map((feature, i) => (
@@ -552,7 +563,11 @@ function KeyFeaturesSection() {
{feature.title}
- {feature.isUnique && Unique}
+ {feature.isUnique && (
+
+ Unique
+
+ )}
{feature.description}
@@ -581,7 +596,9 @@ function IntegrationsSection() {
Works with your tools
-
Use what fits your workflow. All methods, same capabilities.
+
+ Use what fits your workflow. All methods, same capabilities.
+
{tools.map((tool, i) => (
@@ -599,12 +616,15 @@ function IntegrationsSection() {
- Banatie Lab — Official web interface for Banatie API. Generate images, build flows, browse your
- gallery, and explore all capabilities with ready-to-use code snippets.
+ Banatie Lab — Official web interface for Banatie
+ API. Generate images, build flows, browse your gallery, and explore all capabilities
+ with ready-to-use code snippets.
-
Perfect for Claude Code, Cursor, and any AI-powered workflow.
+
+ Perfect for Claude Code, Cursor, and any AI-powered workflow.
+
);
@@ -625,11 +645,14 @@ function ShapeTheFutureSection() {
-
Shape the future of Banatie
+
+ Shape the future of Banatie
+
- We're building this for developers like you. Early adopters get direct influence on our roadmap — suggest features, vote on
- priorities, and help us build exactly what you need.
+ We're building this for developers like you. Early adopters get direct influence on
+ our roadmap — suggest features, vote on priorities, and help us build exactly what you
+ need.
@@ -666,10 +689,30 @@ function GeminiSection() {
];
const capabilities = [
- { icon: Type, title: 'Perfect Text Rendering', description: 'Legible text in images — logos, diagrams, posters. What other models still struggle with.' },
- { icon: Brain, title: 'Native Multimodal', description: 'Understands text AND images in one model. Not a text model + image model bolted together.' },
- { icon: Target, title: 'Precise Prompt Following', description: 'What you ask is what you get. No artistic "interpretation" that ignores your instructions.' },
- { icon: Image, title: 'Professional Realism', description: 'Photorealistic output that replaces stock photos. Not fantasy art — real, usable images.' },
+ {
+ icon: Type,
+ title: 'Perfect Text Rendering',
+ description:
+ 'Legible text in images — logos, diagrams, posters. What other models still struggle with.',
+ },
+ {
+ icon: Brain,
+ title: 'Native Multimodal',
+ description:
+ 'Understands text AND images in one model. Not a text model + image model bolted together.',
+ },
+ {
+ icon: Target,
+ title: 'Precise Prompt Following',
+ description:
+ 'What you ask is what you get. No artistic "interpretation" that ignores your instructions.',
+ },
+ {
+ icon: Image,
+ title: 'Professional Realism',
+ description:
+ 'Photorealistic output that replaces stock photos. Not fantasy art — real, usable images.',
+ },
];
return (
@@ -683,8 +726,9 @@ function GeminiSection() {
Powered by Google Gemini
- We chose Gemini because it's the only model family that combines native multimodal understanding with production-grade image
- generation. Two models, optimized for different needs.
+ We chose Gemini because it's the only model family that combines native
+ multimodal understanding with production-grade image generation. Two models, optimized
+ for different needs.
@@ -702,7 +746,8 @@ function GeminiSection() {
- Optimized for speed and iteration. Perfect for rapid prototyping and high-volume generation.
+ Optimized for speed and iteration. Perfect for rapid prototyping and high-volume
+ generation.
{flashFeatures.map((feature, i) => (
@@ -728,7 +773,8 @@ function GeminiSection() {
- Maximum quality and creative control. For production assets and professional workflows.
+ Maximum quality and creative control. For production assets and professional
+ workflows.
{proFeatures.map((feature, i) => (
@@ -745,7 +791,9 @@ function GeminiSection() {
{/* Shared Capabilities */}
-
Why Gemini outperforms competitors
+
+ Why Gemini outperforms competitors
+
{capabilities.map((cap, i) => (
@@ -762,7 +810,8 @@ function GeminiSection() {
{/* #1 Ranking Note */}
- Gemini 2.5 Flash Image ranked #1 on LMArena for both text-to-image and image editing (August 2025)
+ Gemini 2.5 Flash Image ranked #1 on LMArena for both text-to-image and image editing
+ (August 2025)
@@ -796,7 +845,8 @@ function FinalCtaSection() {
@@ -804,12 +854,15 @@ function FinalCtaSection() {
-
Ready to build?
+
+ Ready to build?
+
Join developers waiting for early access. We'll notify you when your spot is ready.
@@ -822,7 +875,9 @@ function FinalCtaSection() {
-
No credit card required • Free to start • Cancel anytime
+
+ No credit card required • Free to start • Cancel anytime
+
);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 86b1387..b590f78 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -196,6 +196,9 @@ importers:
'@banatie/database':
specifier: workspace:*
version: link:../../packages/database
+ lucide-react:
+ specifier: ^0.400.0
+ version: 0.400.0(react@19.1.0)
next:
specifier: 15.5.4
version: 15.5.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -9192,6 +9195,10 @@ snapshots:
dependencies:
react: 18.3.1
+ lucide-react@0.400.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
magic-string@0.30.19:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5