Bocci HQ
modern
infinity
unpredictable
conceptual
Tally Co
computation
hacking
quantum
Alterier Contruction.
arabian
desert
vancouver
Bocci HQ
3d
3d animation
blender
Bocci Hardware Development
complicated
computation
dev
Bocci Interior Architecture
colorful
infinity
artificial
Bocci Interior Architecture
nature
3d view
artisitic
Alterier Contruction.
dark
off white
conceptual
power
Alterier Contruction.
futuritic
elegance
conceptual
cozy
Supporting Components
This design is built using TypeScript, Tailwind, and Framer Motion for animations. It also includes extra components to enhance and complete the hero section.
To enhance the user experience, I implemented floating navigation across the website using position: fixed. This design choice ensures seamless accessibility and adds a modern, interactive feel to the interface.
// navbar.tsx
import React from "react";
import { PersonStanding, Mail, Compass } from "lucide-react";
import { Button, buttonVariants } from "@/components/ui/button";
const BottomNavigation = () => {
const Tools = [
{
name: "frankgoodman",
icon: <Compass className="text-black dark:text-white" size={20} />,
link: "/",
},
{
name: "index",
icon: <Mail className="text-black dark:text-white" size={20} />,
action: "toggle",
},
{
name: "about",
icon: <PersonStanding className="text-black dark:text-white" size={20} />,
link: "/about",
},
];
return (
<>
<nav className=" static left-1/2 top-96 z-30 mb-4 flex gap-2 md:absolute md:mb-0 md:-translate-x-1/2 ">
<div className="relative flex items-center justify-center rounded-full bg-white/60 p-1 backdrop-blur-sm ">
{Tools.map((menu, index) => (
<Button key={index} className="h-6 cursor-pointer rounded-full py-0 text-center">
{menu.name}
</Button>
))}
</div>
</nav>
</>
);
};
export default BottomNavigation;
I created a wrapper that animates all items from opacity 0 to 1 on reload, with a brief delay between each item’s appearance for a smooth transition effect.
// animated-group.tsx
"use client";
import { ReactNode } from "react";
import { motion, Variants } from "framer-motion";
import React from "react";
type PresetType =
| "fade"
| "slide"
| "scale"
| "blur"
| "blur-slide"
| "zoom"
| "flip"
| "bounce"
| "rotate"
| "swing";
type AnimatedGroupProps = {
children: ReactNode;
className?: string;
variants?: {
container?: Variants;
item?: Variants;
};
preset?: PresetType;
};
const defaultContainerVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
},
},
};
const defaultItemVariants: Variants = {
hidden: { opacity: 0 },
visible: { opacity: 1 },
};
const presetVariants: Record<
PresetType,
{ container: Variants; item: Variants }
> = {
fade: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0 },
visible: { opacity: 1 },
},
},
slide: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
},
},
scale: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, scale: 0.8 },
visible: { opacity: 1, scale: 1 },
},
},
blur: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, filter: "blur(4px)" },
visible: { opacity: 1, filter: "blur(0px)" },
},
},
"blur-slide": {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, filter: "blur(4px)", y: 20 },
visible: { opacity: 1, filter: "blur(0px)", y: 0 },
},
},
zoom: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, scale: 0.5 },
visible: {
opacity: 1,
scale: 1,
transition: { type: "spring", stiffness: 300, damping: 20 },
},
},
},
flip: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, rotateX: -90 },
visible: {
opacity: 1,
rotateX: 0,
transition: { type: "spring", stiffness: 300, damping: 20 },
},
},
},
bounce: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, y: -50 },
visible: {
opacity: 1,
y: 0,
transition: { type: "spring", stiffness: 400, damping: 10 },
},
},
},
rotate: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, rotate: -180 },
visible: {
opacity: 1,
rotate: 0,
transition: { type: "spring", stiffness: 200, damping: 15 },
},
},
},
swing: {
container: defaultContainerVariants,
item: {
hidden: { opacity: 0, rotate: -10 },
visible: {
opacity: 1,
rotate: 0,
transition: { type: "spring", stiffness: 300, damping: 8 },
},
},
},
};
function AnimatedGroup({
children,
className,
variants,
preset,
}: AnimatedGroupProps) {
const selectedVariants = preset
? presetVariants[preset]
: { container: defaultContainerVariants, item: defaultItemVariants };
const containerVariants = variants?.container || selectedVariants.container;
const itemVariants = variants?.item || selectedVariants.item;
return (
<motion.div
initial="hidden"
animate="visible"
variants={containerVariants}
className={className}
>
{React.Children.map(children, (child, index) => (
<motion.div key={index} variants={itemVariants}>
{child}
</motion.div>
))}
</motion.div>
);
}
export { AnimatedGroup };
The card component showcases our main content with animations like zoom-in and entrance effects for each card tag. It also uses images and videos as backgrounds, sourced from Unsplash for high-quality visuals.
// work-card.tsx
import { ArrowUpRight } from "lucide-react";
import { useState } from "react";
import { motion } from "framer-motion";
import {
MorphingDialog,
MorphingDialogTrigger,
MorphingDialogContent,
MorphingDialogTitle,
MorphingDialogImage,
MorphingDialogSubtitle,
MorphingDialogClose,
MorphingDialogDescription,
MorphingDialogContainer,
} from "./morphic-dialog";
interface Media {
type: "image" | "video";
src: string;
}
interface WorkCardProps {
title: string;
category: string[];
media: Media;
}
export function WorkCard({ title, media, category }: WorkCardProps) {
const [isHovered, setIsHovered] = useState(false);
return (
<>
<MorphingDialog
transition={{
type: "spring",
bounce: 0.05,
duration: 0.25,
}}
>
<MorphingDialogTrigger
className="group relative block aspect-square h-[32rem] w-full overflow-hidden rounded-lg bg-black"
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{media.type === "video" ? (
<video
src={media.src}
autoPlay
muted
loop
playsInline
className={`h-full w-full object-cover transition-transform duration-500 ${isHovered ? "scale-110" : "scale-100"}`}
/>
) : (
<img
src={media.src}
alt={title}
className={`h-full w-full object-cover transition-transform duration-500 ${isHovered ? "scale-110" : "scale-100"}`}
/>
)}
<div className="absolute inset-0 bg-black/20" />
<div className="absolute inset-0 flex items-start justify-between p-4">
<h3 className=" rounded-full bg-slate-50 px-4 py-0 text-left font-sans text-sm font-medium text-black transition-opacity group-hover:bg-black group-hover:text-white md:text-base">
{title}
</h3>
<ArrowUpRight className="h-8 w-8 rounded-full bg-gray-300 p-1 text-black group-hover:bg-[hsla(0,0%,100%,.1)]" />
</div>
<motion.div
className={`absolute bottom-3 z-40 flex w-full gap-1 p-8 `}
variants={{
visible: {
transition: {
delayChildren: 0.3,
staggerChildren: 0.2,
},
},
}}
initial={{ opacity: 0 }}
animate={{ opacity: isHovered ? 1 : 0 }}
transition={{ duration: 0.3 }}
>
{category.map((cat, index) => (
<motion.p
key={index}
initial={{ opacity: 0, x: -20 }}
animate={{
opacity: isHovered ? 1 : 0,
x: isHovered ? 0 : -20,
}}
transition={{
delay: isHovered ? index * 0.05 : 0,
stiffness: 300,
}}
className="rounded-full bg-white px-4 py-0 text-left font-sans text-sm font-medium text-black opacity-0 transition-opacity group-hover:opacity-100 md:text-base"
>
{cat}
</motion.p>
))}
</motion.div>
</MorphingDialogTrigger>
<MorphingDialogContainer>
<MorphingDialogContent
style={{
borderRadius: "8px",
}}
className="pointer-events-auto relative h-auto w-full bg-white dark:border-zinc-50/10 dark:bg-zinc-900 md:h-[32rem] md:w-[800px]"
>
{media.type === "video" ? (
<video
src={media.src}
autoPlay
muted
loop
playsInline
className={`h-full w-full object-cover transition-transform duration-500 ${isHovered ? "scale-110" : "scale-100"}`}
/>
) : (
<MorphingDialogImage
src={media.src}
alt={title}
className={`h-full w-full object-cover transition-transform duration-500 ${isHovered ? "scale-110" : "scale-100"}`}
/>
)}
<div className="absolute bottom-2 flex flex-col px-6 py-8">
<MorphingDialogTitle className="text-2xl text-white dark:text-zinc-50">
{title}
</MorphingDialogTitle>
<motion.div className={` z-40 flex w-full gap-1 py-4 `}>
{category.map((cat, index) => (
<p
key={index}
className="rounded-full bg-white px-4 py-0 text-left font-sans text-sm font-medium text-black md:text-base "
>
{cat}
</p>
))}
</motion.div>
<MorphingDialogDescription
disableLayoutAnimation
variants={{
initial: { opacity: 0, scale: 0.8, y: 100 },
animate: { opacity: 1, scale: 1, y: 0 },
exit: { opacity: 0, scale: 0.8, y: 100 },
}}
>
<p className="mt-2 text-[12px] text-white md:text-base">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed
do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Ut enim ad minim veniam, quis nostrud exercitation ullamco
laboris nisi ut aliquip ex ea commodo consequat. Duis aute
irure dolor in reprehenderit in voluptate velit esse cillum
dolore eu fugiat nulla pariatur..
</p>
</MorphingDialogDescription>
</div>
<MorphingDialogClose className="text-zinc-50" />
</MorphingDialogContent>
</MorphingDialogContainer>
</MorphingDialog>
</>
);
}
The MorphingDialog
reveals detailed content from a card when clicked, using layout animations for a smooth transition into a focused view. It includes click-outside detection and escape key support for easy closing.
// morphic-dialog.tsx
"use client";
import React, {
useCallback,
useContext,
useEffect,
useId,
useMemo,
useRef,
useState,
} from "react";
import {
motion,
AnimatePresence,
MotionConfig,
Transition,
Variant,
} from "framer-motion";
import { createPortal } from "react-dom";
import { cn } from "@/lib/utils";
import useClickOutside from "@/hooks/use-click-outside";
import { XIcon } from "lucide-react";
interface MorphingDialogContextType {
isOpen: boolean;
// setIsHovered?: React.Dispatch<React.SetStateAction<boolean>>;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
uniqueId: string;
triggerRef: React.RefObject<HTMLDivElement>;
}
const MorphingDialogContext =
React.createContext<MorphingDialogContextType | null>(null);
function useMorphingDialog() {
const context = useContext(MorphingDialogContext);
if (!context) {
throw new Error(
"useMorphingDialog must be used within a MorphingDialogProvider",
);
}
return context;
}
type MorphingDialogProviderProps = {
children: React.ReactNode;
transition?: Transition;
};
function MorphingDialogProvider({
children,
transition,
}: MorphingDialogProviderProps) {
const [isOpen, setIsOpen] = useState(false);
const uniqueId = useId();
const triggerRef = useRef<HTMLDivElement>(null);
const contextValue = useMemo(
() => ({ isOpen, setIsOpen, uniqueId, triggerRef }),
[isOpen, uniqueId],
);
return (
<MorphingDialogContext.Provider value={contextValue}>
<MotionConfig transition={transition}>{children}</MotionConfig>
</MorphingDialogContext.Provider>
);
}
type MorphingDialogProps = {
children: React.ReactNode;
transition?: Transition;
};
function MorphingDialog({ children, transition }: MorphingDialogProps) {
return (
<MorphingDialogProvider>
<MotionConfig transition={transition}>{children}</MotionConfig>
</MorphingDialogProvider>
);
}
type MorphingDialogTriggerProps = {
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
triggerRef?: React.RefObject<HTMLDivElement>;
};
function MorphingDialogTrigger({
children,
className,
style,
triggerRef,
onMouseEnter,
onMouseLeave,
}: MorphingDialogTriggerProps & {
onMouseEnter?: () => void;
onMouseLeave?: () => void;
}) {
const { setIsOpen, isOpen, uniqueId } = useMorphingDialog();
const handleClick = useCallback(() => {
setIsOpen(!isOpen);
}, [isOpen, setIsOpen]);
const handleKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
setIsOpen(!isOpen);
}
},
[isOpen, setIsOpen],
);
return (
<motion.div
ref={triggerRef}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
layoutId={`dialog-${uniqueId}`}
className={cn("relative cursor-pointer", className)}
onClick={handleClick}
onKeyDown={handleKeyDown}
style={style}
role="button"
aria-haspopup="dialog"
aria-expanded={isOpen}
aria-controls={`motion-ui-morphing-dialog-content-${uniqueId}`}
>
{children}
</motion.div>
);
}
type MorphingDialogContent = {
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
};
function MorphingDialogContent({
children,
className,
style,
}: MorphingDialogContent) {
const { setIsOpen, isOpen, uniqueId, triggerRef } = useMorphingDialog();
const containerRef = useRef<HTMLDivElement>(null);
const [firstFocusableElement, setFirstFocusableElement] =
useState<HTMLElement | null>(null);
const [lastFocusableElement, setLastFocusableElement] =
useState<HTMLElement | null>(null);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
setIsOpen(false);
}
if (event.key === "Tab") {
if (!firstFocusableElement || !lastFocusableElement) return;
if (event.shiftKey) {
if (document.activeElement === firstFocusableElement) {
event.preventDefault();
lastFocusableElement.focus();
}
} else {
if (document.activeElement === lastFocusableElement) {
event.preventDefault();
firstFocusableElement.focus();
}
}
}
};
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [setIsOpen, firstFocusableElement, lastFocusableElement]);
useEffect(() => {
if (isOpen) {
document.body.classList.add("overflow-hidden");
const focusableElements = containerRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
);
if (focusableElements && focusableElements.length > 0) {
setFirstFocusableElement(focusableElements[0] as HTMLElement);
setLastFocusableElement(
focusableElements[focusableElements.length - 1] as HTMLElement,
);
(focusableElements[0] as HTMLElement).focus();
}
} else {
document.body.classList.remove("overflow-hidden");
triggerRef.current?.focus();
}
}, [isOpen, triggerRef]);
useClickOutside(containerRef, () => {
if (isOpen) {
setIsOpen(false);
}
});
return (
<motion.div
ref={containerRef}
layoutId={`dialog-${uniqueId}`}
className={cn("overflow-hidden", className)}
style={style}
role="dialog"
aria-modal="true"
aria-labelledby={`motion-ui-morphing-dialog-title-${uniqueId}`}
aria-describedby={`motion-ui-morphing-dialog-description-${uniqueId}`}
>
{children}
</motion.div>
);
}
type MorphingDialogContainerProps = {
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
};
function MorphingDialogContainer({ children }: MorphingDialogContainerProps) {
const { isOpen, uniqueId } = useMorphingDialog();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
if (!mounted) return null;
return createPortal(
<AnimatePresence initial={false} mode="sync">
{isOpen && (
<>
<motion.div
key={`backdrop-${uniqueId}`}
className="fixed inset-0 h-full w-full bg-white/40 backdrop-blur-sm dark:bg-black/40"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
/>
<div className="fixed inset-0 z-50 flex items-center justify-center">
{children}
</div>
</>
)}
</AnimatePresence>,
document.body,
);
}
type MorphingDialogTitleProps = {
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
};
function MorphingDialogTitle({
children,
className,
style,
}: MorphingDialogTitleProps) {
const { uniqueId } = useMorphingDialog();
return (
<motion.div
layoutId={`dialog-title-container-${uniqueId}`}
className={className}
style={style}
layout
>
{children}
</motion.div>
);
}
type MorphingDialogSubtitleProps = {
children: React.ReactNode;
className?: string;
style?: React.CSSProperties;
};
function MorphingDialogSubtitle({
children,
className,
style,
}: MorphingDialogSubtitleProps) {
const { uniqueId } = useMorphingDialog();
return (
<motion.div
layoutId={`dialog-subtitle-container-${uniqueId}`}
className={className}
style={style}
>
{children}
</motion.div>
);
}
type MorphingDialogDescriptionProps = {
children: React.ReactNode;
className?: string;
disableLayoutAnimation?: boolean;
variants?: {
initial: Variant;
animate: Variant;
exit: Variant;
};
};
function MorphingDialogDescription({
children,
className,
variants,
disableLayoutAnimation,
}: MorphingDialogDescriptionProps) {
const { uniqueId } = useMorphingDialog();
return (
<motion.div
key={`dialog-description-${uniqueId}`}
layoutId={
disableLayoutAnimation
? undefined
: `dialog-description-content-${uniqueId}`
}
variants={variants}
className={className}
initial="initial"
animate="animate"
exit="exit"
id={`dialog-description-${uniqueId}`}
>
{children}
</motion.div>
);
}
type MorphingDialogImageProps = {
src: string;
alt: string;
className?: string;
style?: React.CSSProperties;
};
function MorphingDialogImage({
src,
alt,
className,
style,
}: MorphingDialogImageProps) {
const { uniqueId } = useMorphingDialog();
return (
<motion.img
src={src}
alt={alt}
className={cn(className)}
layoutId={`dialog-img-${uniqueId}`}
style={style}
/>
);
}
type MorphingDialogCloseProps = {
children?: React.ReactNode;
className?: string;
variants?: {
initial: Variant;
animate: Variant;
exit: Variant;
};
};
function MorphingDialogClose({
children,
className,
variants,
}: MorphingDialogCloseProps) {
const { setIsOpen, uniqueId } = useMorphingDialog();
const handleClose = useCallback(() => {
setIsOpen(false);
}, [setIsOpen]);
return (
<motion.button
onClick={handleClose}
type="button"
aria-label="Close dialog"
key={`dialog-close-${uniqueId}`}
className={cn("absolute right-6 top-6", className)}
initial="initial"
animate="animate"
exit="exit"
variants={variants}
>
{children || <XIcon size={24} />}
</motion.button>
);
}
export {
MorphingDialog,
MorphingDialogTrigger,
MorphingDialogContainer,
MorphingDialogContent,
MorphingDialogClose,
MorphingDialogTitle,
MorphingDialogSubtitle,
MorphingDialogDescription,
MorphingDialogImage,
};
Now the hero section ready to use
Stay tune for more🚀