diff --git a/freebuff/web/src/app/home-client.tsx b/freebuff/web/src/app/home-client.tsx index 373cc2d4a..f337ee4ef 100644 --- a/freebuff/web/src/app/home-client.tsx +++ b/freebuff/web/src/app/home-client.tsx @@ -2,11 +2,13 @@ import { AnimatePresence, motion } from 'framer-motion' import { + Check, ChevronDown, + Copy, } from 'lucide-react' import Image from 'next/image' import Link from 'next/link' -import { useState } from 'react' +import { useMemo, useState } from 'react' import { BackgroundBeams } from '@/components/background-beams' import { CopyButton } from '@/components/copy-button' @@ -120,21 +122,107 @@ function SetupGuide() { ) } +const PARTICLE_COUNT = 14 + function InstallCommand({ className }: { className?: string }) { + const [copied, setCopied] = useState(false) + const [copyCount, setCopyCount] = useState(0) + + const particles = useMemo(() => + Array.from({ length: PARTICLE_COUNT }).map((_, i) => ({ + angle: (i / PARTICLE_COUNT) * 360 + (Math.random() - 0.5) * 25, + distance: 35 + Math.random() * 35, + size: 3 + Math.random() * 4, + durationExtra: Math.random() * 0.3, + })), + [copyCount], + ) + + const handleCopy = () => { + navigator.clipboard.writeText(INSTALL_COMMAND) + setCopied(true) + setCopyCount(c => c + 1) + setTimeout(() => setCopied(false), 1800) + } + return ( -
- $ - - {INSTALL_COMMAND} - - +
+
+ $ + + {INSTALL_COMMAND} + + +
+ + {/* Celebration particles */} + + {copied && + particles.map((p, i) => { + const rad = (p.angle * Math.PI) / 180 + return ( + + ) + })} +
) } @@ -143,28 +231,50 @@ function FAQList() { const [openIndex, setOpenIndex] = useState(null) return ( -
+
{faqs.map((faq, i) => { const isOpen = openIndex === i return ( @@ -176,9 +286,14 @@ function FAQList() { transition={{ duration: 0.25, ease: 'easeInOut' }} className="overflow-hidden" > -

- {faq.answer} -

+
+ +
+

+ {faq.answer} +

+
+
)} @@ -190,9 +305,9 @@ function FAQList() { } const PHILOSOPHY_WORDS = [ - { word: 'SIMPLE', description: 'No modes. No config. Just code.' }, + { word: 'SIMPLE', description: 'No modes. No config. Just works.' }, { word: 'FAST', description: 'Up to 3× the speed of Claude Code' }, - { word: 'LOADED', description: 'Built in web research, browser use, and more' }, + { word: 'LOADED', description: 'Built-in web research, browser use, and more' }, ] function PhilosophySection() { @@ -215,34 +330,32 @@ function PhilosophySection() { } return ( -
-
- {PHILOSOPHY_WORDS.map((item, i) => ( +
+ {PHILOSOPHY_WORDS.map((item, i) => ( + lightUp(i)} + onViewportLeave={() => dimDown(i)} + viewport={{ margin: '0px 0px -50% 0px' }} + className={cn( + 'font-dm-mono text-7xl md:text-[8rem] lg:text-[6rem] xl:text-[8rem] font-medium leading-[0.85] tracking-tighter select-none transition-all duration-500', + litWords.has(i) ? 'keyword-filled' : 'keyword-hollow', + )} > - lightUp(i)} - onViewportLeave={() => dimDown(i)} - viewport={{ margin: '0px 0px -55% 0px' }} - className={cn( - 'font-dm-mono text-7xl md:text-[8rem] lg:text-[10rem] font-medium leading-[0.85] tracking-tighter select-none transition-all duration-500', - litWords.has(i) ? 'keyword-filled' : 'keyword-hollow', - )} - > - {item.word} - -

- {item.description} -

+ {item.word}
- ))} -
+

+ {item.description} +

+ + ))}
) } @@ -282,7 +395,7 @@ export default function HomeClient() { > GitHub @@ -327,7 +440,7 @@ export default function HomeClient() { {word} @@ -365,25 +478,30 @@ export default function HomeClient() {
- {/* Philosophy content — same background, continuous flow */} - - - {/* ─── FAQ Section ─── */} -
-
- -

- Frequently asked questions -

-
+ {/* ─── Philosophy + FAQ: side-by-side on large screens ─── */} +
+
+ {/* Philosophy — left side */} +
+ +
+ + {/* FAQ — right side (sticky on lg) */} +
+ +

+ FAQ +

+
- + +
diff --git a/freebuff/web/src/styles/globals.css b/freebuff/web/src/styles/globals.css index c9cde579c..60fecaf96 100644 --- a/freebuff/web/src/styles/globals.css +++ b/freebuff/web/src/styles/globals.css @@ -60,7 +60,34 @@ 0 0 80px rgba(124, 255, 63, 0.1); } +.hover-glow-flare { + transition: text-shadow 0.5s ease, filter 0.5s ease; +} + +.hover-glow-flare:hover { + animation: none !important; + text-shadow: + 0 0 30px rgba(124, 255, 63, 0.9), + 0 0 60px rgba(124, 255, 63, 0.6), + 0 0 120px rgba(124, 255, 63, 0.35), + 0 0 200px rgba(124, 255, 63, 0.15); + filter: brightness(1.2); +} + /* Gradient border shine effect */ +.install-box-glow { + animation: install-glow-breathe 3s ease-in-out infinite; +} + +@keyframes install-glow-breathe { + 0%, 100% { + box-shadow: 0 0 20px rgba(124, 255, 63, 0.25), 0 0 40px rgba(124, 255, 63, 0.1); + } + 50% { + box-shadow: 0 0 25px rgba(124, 255, 63, 0.35), 0 0 50px rgba(124, 255, 63, 0.15); + } +} + .gradient-border-shine { position: relative; } @@ -101,6 +128,26 @@ 0 0 40px rgba(124, 255, 63, 0.3), 0 0 80px rgba(124, 255, 63, 0.1); transition: text-shadow 0.5s ease; + animation: keyword-glow-enter 0.8s ease-out; +} + +@keyframes keyword-glow-enter { + 0% { + text-shadow: + 0 0 40px rgba(124, 255, 63, 0.3), + 0 0 80px rgba(124, 255, 63, 0.1); + } + 40% { + text-shadow: + 0 0 60px rgba(124, 255, 63, 0.6), + 0 0 120px rgba(124, 255, 63, 0.3), + 0 0 200px rgba(124, 255, 63, 0.15); + } + 100% { + text-shadow: + 0 0 40px rgba(124, 255, 63, 0.3), + 0 0 80px rgba(124, 255, 63, 0.1); + } } diff --git a/web/src/app/api/v1/chat/completions/_post.ts b/web/src/app/api/v1/chat/completions/_post.ts index ad0eb4f7a..77a2ab901 100644 --- a/web/src/app/api/v1/chat/completions/_post.ts +++ b/web/src/app/api/v1/chat/completions/_post.ts @@ -367,7 +367,6 @@ export async function postChatCompletions(params: { try { if (bodyStream) { // Streaming request — route to SiliconFlow/CanopyWave/Fireworks for supported models - // SiliconFlow, CanopyWave, and Fireworks TEMPORARILY DISABLED: route through OpenRouter const useSiliconFlow = false // isSiliconFlowModel(typedBody.model) const useCanopyWave = false // isCanopyWaveModel(typedBody.model) const useFireworks = isFireworksModel(typedBody.model) @@ -432,7 +431,7 @@ export async function postChatCompletions(params: { }) } else { // Non-streaming request — route to SiliconFlow/CanopyWave/Fireworks for supported models - // SiliconFlow, CanopyWave, and Fireworks TEMPORARILY DISABLED: route through OpenRouter + // TEMPORARILY DISABLED: route through OpenRouter const model = typedBody.model const useSiliconFlow = false // isSiliconFlowModel(model) const useCanopyWave = false // isCanopyWaveModel(model) diff --git a/web/src/llm-api/fireworks.ts b/web/src/llm-api/fireworks.ts index 42217cb52..4df557af0 100644 --- a/web/src/llm-api/fireworks.ts +++ b/web/src/llm-api/fireworks.ts @@ -28,7 +28,7 @@ const fireworksAgent = new Agent({ /** Map from OpenRouter model IDs to Fireworks model IDs */ const FIREWORKS_MODEL_MAP: Record = { - // 'minimax/minimax-m2.5': 'accounts/fireworks/models/minimax-m2p5', + 'minimax/minimax-m2.5': 'accounts/fireworks/models/minimax-m2p5', } export function isFireworksModel(model: string): boolean {