8 Commits

Author SHA1 Message Date
6d80983360 Merge version_7_1776773032685 into main
Merge version_7_1776773032685 into main
2026-04-21 12:04:55 +00:00
41c93b1d4f Bob AI: Modify the NavbarCentered component to be a floating navigat 2026-04-21 15:04:47 +03:00
1034ff2459 Merge version_6_1776768625786 into main
Merge version_6_1776768625786 into main
2026-04-21 10:52:14 +00:00
536fc821cd Bob AI: Add an 'Active Now' tag with a green pulsing light effect to 2026-04-21 13:52:02 +03:00
9fd8e43953 Merge version_5_1776768529458 into main
Merge version_5_1776768529458 into main
2026-04-21 10:49:54 +00:00
eb62fd34cc Bob AI: Modify the NavbarCentered component to be a floating navigat 2026-04-21 13:49:45 +03:00
410fd1e658 Merge version_4_1776717966773 into main
Merge version_4_1776717966773 into main
2026-04-20 20:48:48 +00:00
2d98c8e419 Bob AI: to each section add glassmorphic badge with random funny fac 2026-04-20 23:48:40 +03:00
15 changed files with 48 additions and 222 deletions

View File

@@ -1,9 +1,7 @@
import { motion } from "motion/react";
import { useState } from "react";
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import { getRandomFact } from "@/lib/facts";
type AboutMediaOverlayProps = {
tag: string;
@@ -22,7 +20,6 @@ const AboutMediaOverlay = ({
imageSrc,
videoSrc,
}: AboutMediaOverlayProps) => {
const [fact] = useState(getRandomFact);
return (
<section aria-label="About section" className="py-20">
<div className="relative flex items-center justify-center py-8 md:py-12 mx-auto w-content-width rounded overflow-hidden">
@@ -33,6 +30,9 @@ const AboutMediaOverlay = ({
<div className="relative z-10 flex items-center justify-center px-5 py-10 mx-auto min-h-100 md:min-h-120 md:w-1/2 w-content-width">
<div className="flex flex-col items-center gap-3 md:gap-1 text-center">
<div className="mb-2">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-white/20 backdrop-blur-sm border border-white/30 text-foreground">We have a rubber duck for debugging. His name is Sir Quacks-A-Lot.</div>
</div>
<motion.span
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
@@ -50,16 +50,6 @@ const AboutMediaOverlay = ({
className="text-6xl font-medium text-balance"
/>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.4, ease: "easeOut", delay: 0.2 }}
className="backdrop-blur-md bg-white/30 border border-white/20 rounded-lg p-4 my-6 text-center max-w-md"
>
<p className="text-sm italic text-foreground">"{fact}"</p>
</motion.div>
<TextAnimation
text={description}
variant="slide-up"

View File

@@ -1,9 +1,8 @@
import { useState, useEffect } from "react";
import { useState } from "react";
import { motion } from "motion/react";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import { sendContactEmail } from "@/lib/api/email";
import { companyFacts } from "../../../data/facts";
type InputField = {
name: string;
@@ -40,12 +39,6 @@ const ContactSplitForm = ({
imageSrc,
videoSrc,
}: ContactSplitFormProps) => {
const [currentFact, setCurrentFact] = useState("");
useEffect(() => {
setCurrentFact(companyFacts[Math.floor(Math.random() * companyFacts.length)]);
}, []);
const [formData, setFormData] = useState<Record<string, string>>(() => {
const initial: Record<string, string> = {};
inputs.forEach((input) => {
@@ -86,6 +79,9 @@ const ContactSplitForm = ({
<div className="p-5 md:p-10 card rounded">
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
<div className="flex flex-col items-center gap-1 text-center">
<div className="mb-2">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-card/20 backdrop-blur-sm border border-card/30 text-foreground">Our contact form is powered by a hamster on a wheel. Please be patient.</div>
</div>
<span className="card rounded px-3 py-1 text-sm">{tag}</span>
<TextAnimation
@@ -95,18 +91,6 @@ const ContactSplitForm = ({
className="text-4xl font-medium text-balance"
/>
{currentFact && (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.4, ease: "easeOut", delay: 0.2 }}
className="backdrop-blur-md bg-white/30 border border-white/20 rounded-lg p-4 my-6 text-center max-w-md"
>
<p className="text-sm italic text-foreground">"{currentFact}"</p>
</motion.div>
)}
<TextAnimation
text={description}
variant="slide-up"

View File

@@ -5,7 +5,6 @@ import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import { cls } from "@/lib/utils";
import GlassmorphicBadge from "@/components/ui/GlassmorphicBadge";
type FaqItem = {
question: string;
@@ -41,6 +40,9 @@ const FaqSplitMedia = ({
<section aria-label="FAQ section" className="py-20">
<div className="w-content-width mx-auto flex flex-col gap-8">
<div className="flex flex-col items-center gap-3 md:gap-2">
<div className="mb-2">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-card/20 backdrop-blur-sm border border-card/30 text-foreground">FAQ: Do you sleep? Answer: Rarely.</div>
</div>
<span className="card rounded px-3 py-1 text-sm">{tag}</span>
<TextAnimation
@@ -57,8 +59,6 @@ const FaqSplitMedia = ({
className="md:max-w-6/10 text-lg leading-tight text-center"
/>
<GlassmorphicBadge />
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-1 md:mt-2">
{primaryButton && <Button text={primaryButton.text} href={primaryButton.href} variant="primary" animate />}

View File

@@ -3,7 +3,6 @@ import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import GridOrCarousel from "@/components/ui/GridOrCarousel";
import FunnyFactBadge from "@/components/ui/FunnyFactBadge";
type FeatureItem = {
title: string;
@@ -31,6 +30,9 @@ const FeaturesMediaCards = ({
<section aria-label="Features section" className="py-20">
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center w-content-width mx-auto gap-3 md:gap-2">
<div className="mb-2">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-card/20 backdrop-blur-sm border border-card/30 text-foreground">Our secret feature is a button that orders pizza. It's still in beta.</div>
</div>
<span className="px-3 py-1 text-sm card rounded">{tag}</span>
<TextAnimation
@@ -47,8 +49,6 @@ const FeaturesMediaCards = ({
className="md:max-w-6/10 text-lg leading-tight text-center"
/>
<FunnyFactBadge />
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-1 md:mt-2">
{primaryButton && <Button text={primaryButton.text} href={primaryButton.href} variant="primary" animate />}

View File

@@ -1,5 +1,4 @@
import { useButtonClick } from "@/hooks/useButtonClick";
import { useState, useEffect } from "react";
type FooterLink = {
label: string;
@@ -34,28 +33,12 @@ const FooterBasic = ({
leftText: string;
rightText: string;
}) => {
const funnyFacts = [
"Our office coffee machine is sentient and demands tribute in the form of spare USB cables.",
"We once solved a major server outage by turning it off and on again. The client thinks we're wizards.",
"The company's first server was a modified gaming console. It ran surprisingly well.",
"Our AI assistant has developed a passion for writing poetry about database schemas.",
"Legend says our lead developer codes in binary. We haven't disproven it."
];
const [randomFact, setRandomFact] = useState<string | null>(null);
useEffect(() => {
setRandomFact(funnyFacts[Math.floor(Math.random() * funnyFacts.length)]);
}, []);
return (
<footer aria-label="Site footer" className="w-full pt-20 pb-10">
<div className="w-content-width mx-auto">
{randomFact && (
<div className="backdrop-blur-md bg-white/30 border border-white/20 rounded-xl shadow-lg p-4 text-foreground mb-8 text-center">
<p className="italic">"{randomFact}"</p>
</div>
)}
<div className="w-full flex justify-center mb-8">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-card/20 backdrop-blur-sm border border-card/30 text-foreground">Made with and too many energy drinks.</div>
</div>
<div className="w-full flex flex-wrap justify-between gap-y-10 mb-10">
{columns.map((column) => (
<div key={column.title} className="w-1/2 md:w-auto flex flex-col items-start gap-3">

View File

@@ -2,8 +2,6 @@ import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import AutoFillText from "@/components/ui/AutoFillText";
import GlassmorphicBadge from "@/components/ui/GlassmorphicBadge";
import { motion } from "motion/react";
type HeroBrandProps = {
brand: string;
@@ -37,14 +35,10 @@ const HeroBrand = ({
/>
<div className="relative z-10 w-content-width mx-auto pb-5">
<div className="flex flex-col gap-4">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, ease: "easeOut" }}
>
<GlassmorphicBadge />
</motion.div>
<div className="flex flex-col">
<div className="mb-4">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-white/20 backdrop-blur-sm border border-white/30 text-primary-cta-text">Powered by caffeine and the occasional reboot.</div>
</div>
<div className="w-full flex flex-col md:flex-row md:justify-between items-start md:items-end gap-3 md:gap-5">
<TextAnimation
text={description}

View File

@@ -1,13 +1,9 @@
"use client";
import { useState } from "react";
import { motion } from "motion/react";
import type { LucideIcon } from "lucide-react";
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import GridOrCarousel from "@/components/ui/GridOrCarousel";
import { resolveIcon } from "@/utils/resolve-icon";
import { getRandomFact } from "@/lib/facts";
type Metric = {
value: string;
@@ -30,12 +26,13 @@ const MetricsGradientCards = ({
primaryButton?: { text: string; href: string };
secondaryButton?: { text: string; href: string };
metrics: Metric[];
}) => {
const [fact] = useState(getRandomFact);
return (
}) => (
<section aria-label="Metrics section" className="py-20">
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-3 md:gap-2 w-content-width mx-auto">
<div className="mb-2">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-card/20 backdrop-blur-sm border border-card/30 text-foreground">Our uptime is 99.9%, but our coffee machine's is closer to 50%.</div>
</div>
<span className="px-3 py-1 text-sm card rounded">{tag}</span>
<TextAnimation
@@ -52,16 +49,6 @@ const MetricsGradientCards = ({
className="md:max-w-6/10 text-lg leading-tight text-center"
/>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.4, ease: "easeOut", delay: 0.2 }}
className="backdrop-blur-md bg-white/30 border border-white/20 rounded-lg p-4 my-6 text-center max-w-md"
>
<p className="text-sm italic text-foreground">"{fact}"</p>
</motion.div>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-1 md:mt-2">
{primaryButton && <Button text={primaryButton.text} href={primaryButton.href} variant="primary" animate />}
@@ -104,7 +91,6 @@ const MetricsGradientCards = ({
</motion.div>
</div>
</section>
);
};
);
export default MetricsGradientCards;

View File

@@ -1,8 +1,6 @@
import { motion } from "motion/react";
import { useState } from "react";
import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import { getRandomFact } from "@/lib/facts";
const SocialProofMarquee = ({
tag,
@@ -19,12 +17,13 @@ const SocialProofMarquee = ({
secondaryButton?: { text: string; href: string };
names: string[];
}) => {
const [fact] = useState(getRandomFact);
return (
<section aria-label="Social proof section" className="py-20">
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-3 md:gap-2 w-content-width mx-auto">
<div className="mb-2">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-card/20 backdrop-blur-sm border border-card/30 text-foreground">Even our CEO's mom thinks we're a big deal.</div>
</div>
<span className="px-3 py-1 text-sm rounded card">{tag}</span>
<TextAnimation
@@ -41,16 +40,6 @@ const SocialProofMarquee = ({
className="md:max-w-6/10 text-lg leading-tight text-center"
/>
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.4, ease: "easeOut", delay: 0.2 }}
className="backdrop-blur-md bg-white/30 border border-white/20 rounded-lg p-4 my-4 text-center max-w-md mx-auto"
>
<p className="text-sm italic text-foreground">"{fact}"</p>
</motion.div>
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-1 md:mt-2">
{primaryButton && <Button text={primaryButton.text} href={primaryButton.href} variant="primary" animate />}

View File

@@ -3,7 +3,6 @@ import Button from "@/components/ui/Button";
import TextAnimation from "@/components/ui/TextAnimation";
import ImageOrVideo from "@/components/ui/ImageOrVideo";
import GridOrCarousel from "@/components/ui/GridOrCarousel";
import GlassmorphicBadge from "@/components/ui/GlassmorphicBadge";
type Testimonial = {
name: string;
@@ -29,6 +28,9 @@ const TestimonialQuoteCards = ({
<section aria-label="Testimonials section" className="py-20">
<div className="flex flex-col gap-8">
<div className="flex flex-col items-center gap-3 md:gap-2 w-content-width mx-auto">
<div className="mb-2">
<div className="inline-block px-3 py-1 text-sm rounded-full bg-card/20 backdrop-blur-sm border border-card/30 text-foreground">Our clients love us. We have the thank-you cards to prove it.</div>
</div>
<span className="px-3 py-1 text-sm card rounded">{tag}</span>
<TextAnimation
@@ -45,8 +47,6 @@ const TestimonialQuoteCards = ({
className="md:max-w-6/10 text-lg leading-tight text-center"
/>
<GlassmorphicBadge />
{(primaryButton || secondaryButton) && (
<div className="flex flex-wrap justify-center gap-3 mt-1 md:mt-2">
{primaryButton && <Button text={primaryButton.text} href={primaryButton.href} variant="primary" animate />}

View File

@@ -1,32 +0,0 @@
"use client";
import { useState } from "react";
import { motion } from "motion/react";
const funnyFacts = [
"Our office coffee machine is sentient and occasionally offers career advice.",
"We once solved a major bug by turning the server off and on again. It's now our official policy.",
"The company mascot is a rubber duck named 'Sir Quacks-a-Lot'. He has his own email address.",
"All our code is written in Comic Sans for 'improved readability'.",
"Our CTO's cat is listed as a senior developer on our internal directory.",
];
const getRandomFact = () => funnyFacts[Math.floor(Math.random() * funnyFacts.length)];
const FunnyFactBadge = () => {
const [fact] = useState(getRandomFact);
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.4, ease: "easeOut", delay: 0.2 }}
className="backdrop-blur-md bg-white/30 border border-white/20 rounded-lg p-4 my-4 text-center max-w-md mx-auto"
>
<p className="text-sm italic text-foreground">"{fact}"</p>
</motion.div>
);
};
export default FunnyFactBadge;

View File

@@ -1,37 +0,0 @@
"use client";
import { useState, useEffect } from "react";
import { motion } from "motion/react";
const funnyFacts = [
"Our office coffee machine is rumored to be sentient. It only makes good coffee for its favorites.",
"We once named a server 'Zeus' and it was promptly struck by a lightning-induced power surge. We stick to boring names now.",
"The office plant has been promoted more times than some of our interns.",
"Our first 'office' was a garage that we shared with a surprisingly musical raccoon.",
"We settled a heated debate on tabs vs. spaces with a company-wide rock-paper-scissors tournament.",
"The company mascot is a rubber duck named 'Kernel Panic'. He oversees all code deployments.",
];
const GlassmorphicBadge = () => {
const [fact, setFact] = useState("");
useEffect(() => {
setFact(funnyFacts[Math.floor(Math.random() * funnyFacts.length)]);
}, []);
if (!fact) return null;
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-15%" }}
transition={{ duration: 0.4, ease: "easeOut", delay: 0.2 }}
className="backdrop-blur-md bg-white/30 border border-white/20 rounded-lg p-4 my-6 text-center max-w-md mx-auto"
>
<p className="text-sm italic text-foreground">"{fact}"</p>
</motion.div>
);
};
export default GlassmorphicBadge;

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState } from "react";
import { Link } from "react-router-dom";
import { motion, AnimatePresence } from "motion/react";
import { Plus, ArrowRight } from "lucide-react";
@@ -45,45 +45,23 @@ const NavLink = ({
);
};
const funnyFacts = [
"Our office coffee is powered by pure optimism.",
"We once named a server 'Titanic'. It was unsinkable.",
"Our first 'office' was a garage. We still have oil stains.",
"The office plant is our longest-serving employee.",
"Our mascot is a rubber duck named 'Sir Quacks-a-lot'.",
];
const NavbarCentered = ({ logo, navItems, ctaButton }: NavbarCenteredProps) => {
const [isScrolled, setIsScrolled] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const [fact, setFact] = useState("");
useEffect(() => {
setFact(funnyFacts[Math.floor(Math.random() * funnyFacts.length)]);
}, []);
useEffect(() => {
const handleScroll = () => setIsScrolled(window.scrollY > 50);
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<>
<nav
className={cls(
"fixed z-1000 top-0 left-0 w-full transition-all duration-500 ease-in-out",
isScrolled ? "h-15 bg-background/80 backdrop-blur-sm" : "h-20 bg-background/0 backdrop-blur-0"
"sticky top-0 z-50 w-full transition-all duration-500 ease-in-out h-20 bg-background/80 backdrop-blur-md border-b border-foreground/10"
)}
>
<div className="relative flex items-center justify-between h-full w-content-width mx-auto">
<div className="flex items-center gap-4">
<Link to="/" className="text-xl font-medium text-foreground">{logo}</Link>
{fact && (
<div className="hidden lg:block backdrop-blur-md bg-white/20 border border-white/10 rounded-full px-3 py-1 text-xs shadow-lg text-foreground whitespace-nowrap">
{fact}
</div>
)}
<div className="flex items-center gap-1 px-2 py-1 rounded-full bg-green-100 text-green-800 text-xs font-medium">
<span className="w-2 h-2 rounded-full bg-green-500 animate-[pulse-green_2s_infinite]"></span>
Active Now
</div>
</div>
<div className="hidden md:flex absolute left-1/2 items-center gap-6 -translate-x-1/2">

View File

@@ -1,7 +0,0 @@
export const companyFacts = [
"Our office coffee machine is rumored to be sentient. We haven't confirmed it, but it makes a great latte.",
"We once held a meeting entirely in pirate speak. Productivity was questionable, but morale was high.",
"The office plant, 'Fern-ando', has been promoted to Senior Vice President of Photosynthesis.",
"Our first server was a potato. It was surprisingly reliable.",
"We have a rubber duck for debugging. It's our most senior developer.",
];

View File

@@ -1,13 +0,0 @@
export const facts = [
"Our office coffee machine is rumored to be sentient. It only makes good coffee for its favorites.",
"We once held a meeting entirely in emojis. It was surprisingly productive.",
"The company's first server was a modified gaming console. It still holds the high score in Tetris.",
"Our CEO's dog is an official board member with the title 'Chief Morale Officer'.",
"We have a rubber duck for debugging. It's been promoted twice.",
"The office plant has more followers on Instagram than most of our employees.",
"Our team-building exercise involved assembling IKEA furniture without instructions. We're still not talking about it.",
];
export const getRandomFact = () => {
return facts[Math.floor(Math.random() * facts.length)];
};

View File

@@ -169,3 +169,14 @@
.animate-progress {
animation: progress linear forwards;
}
@keyframes pulse-green {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.7;
}
}