<div class="movo-container">
<!-- Background Layers -->
<div class="movo-bg">
</div>
<div class="movo-overlay-fade">
</div>
<div class="movo-halftone">
</div>
<!-- Header -->
<header class="movo-header">
<div class="movo-logo">
<svg fill="none" height="22" stroke="currentColor" stroke-width="2.5" viewbox="0 0 24 24" width="22">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">
</path>
<circle cx="9" cy="7" r="4">
</circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87">
</path>
<path d="M16 3.13a4 4 0 0 1 0 7.75">
</path>
</svg>
fami
</div>
<nav class="movo-nav">
<a href="#">
How it works
</a>
<a href="#">
Use cases
</a>
<a href="#">
Case studies
</a>
<a href="#">
Hear Fami
</a>
</nav>
<button class="movo-btn-dark desktop-only">
Book a Demo
</button>
<button class="movo-hamburger mobile-only" id="movoMenuBtn">
<span>
</span>
<span>
</span>
<span>
</span>
</button>
</header>
<!-- Mobile Menu Overlay -->
<div class="movo-mobile-menu" id="movoMobileMenu">
<nav>
<a href="#">
How it works
</a>
<a href="#">
Use cases
</a>
<a href="#">
Case studies
</a>
<a href="#">
Hear Fami
</a>
<button class="movo-btn-dark">
Book a Demo
</button>
</nav>
</div>
<!-- Main Content -->
<main class="movo-content">
<div class="movo-tag">
AI ENROLLMENT & RETENTION PLATFORM.
</div>
<h1 class="movo-title">
A personal journey
<br/>
for every family.
</h1>
<p class="movo-subtitle">
AI for the full family lifecycle.
</p>
<div class="movo-actions">
<button class="movo-btn-dark">
Book a Demo
</button>
<button class="movo-btn-light">
Speak to Fami
</button>
</div>
<!-- Chat Widget -->
<div class="movo-chat-wrapper">
<div class="movo-chat-box">
<div class="movo-chat-header">
<svg fill="none" height="18" stroke="currentColor" stroke-width="2.5" viewbox="0 0 24 24" width="18">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2">
</path>
<circle cx="9" cy="7" r="4">
</circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87">
</path>
<path d="M16 3.13a4 4 0 0 1 0 7.75">
</path>
</svg>
fami
</div>
<div class="movo-chat-messages" id="movoChatMessages">
<div class="chat-track" id="chatTrack">
<!-- JS handles messages -->
</div>
</div>
<div class="movo-chat-typing hidden" id="movoChatTyping">
<div class="dot">
</div>
<div class="dot">
</div>
<div class="dot">
</div>
</div>
</div>
</div>
</main>
<div class="movo-footer">
Used by teams serving millions of families
</div>
</div>
/* BIT_CSS_START */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Playfair+Display:wght@500;600&display=swap');
body {
margin: 0;
padding: 0;
background-color: #fff;
font-family: 'Inter', sans-serif;
color: #3b3228;
/* Dark brown text color matching screenshot */
}
/* Base Container */
.movo-container {
position: relative;
width: 100%;
min-height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Backgrounds */
.movo-bg {
position: absolute;
inset: 0;
/* High quality park placeholder */
background-image: url('https://storage.maquinaespacial.com/media/family/001.jpg');
background-size: cover;
background-position: center 70%;
z-index: 0;
}
.movo-overlay-fade {
position: absolute;
inset: 0;
/* Creates the strong white vignette effect around the image */
background: radial-gradient(circle at 50% 50%, transparent 0%, rgba(255, 255, 255, 0.8) 60%, #fff 90%);
background: linear-gradient(to bottom, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.2) 30%, rgba(255, 255, 255, 0.1) 70%, #fff 100%),
radial-gradient(ellipse at 50% 50%, transparent 20%, rgba(255, 255, 255, 0.6) 70%, #fff 100%);
z-index: 1;
pointer-events: none;
}
.movo-halftone {
position: absolute;
inset: 0;
/* Dotted texture */
background-image: radial-gradient(rgba(0, 0, 0, 0.06) 1px, transparent 1px);
background-size: 3px 3px;
z-index: 2;
pointer-events: none;
}
/* Header */
.movo-header {
position: relative;
z-index: 10;
margin: 20px auto;
width: 90%;
max-width: 1100px;
background: rgba(255, 255, 255, 0.95);
border: 1px solid rgba(0, 0, 0, 0.05);
border-radius: 40px;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.02);
}
.movo-logo {
display: flex;
align-items: center;
gap: 8px;
font-family: 'Playfair Display', serif;
font-weight: 600;
font-size: 1.5rem;
color: #3b3228;
}
.movo-nav {
display: flex;
gap: 30px;
}
.movo-nav a {
text-decoration: none;
color: #3b3228;
font-size: 0.9rem;
font-weight: 500;
transition: opacity 0.3s;
}
.movo-nav a:hover {
opacity: 0.7;
}
/* Buttons */
.movo-btn-dark {
background: #3b3228;
color: #fff;
border: none;
padding: 12px 24px;
border-radius: 30px;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
font-family: 'Inter', sans-serif;
transition: transform 0.2s, background 0.2s;
}
.movo-btn-dark:hover {
background: #2a231c;
transform: translateY(-1px);
}
.movo-btn-light {
background: #fff;
color: #3b3228;
border: 1px solid rgba(0, 0, 0, 0.1);
padding: 12px 24px;
border-radius: 30px;
font-size: 0.9rem;
font-weight: 500;
cursor: pointer;
font-family: 'Inter', sans-serif;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
transition: transform 0.2s;
}
.movo-btn-light:hover {
transform: translateY(-1px);
}
.desktop-only {
display: block;
}
.mobile-only {
display: none;
}
@media (min-width: 769px) {
.movo-hamburger {
display: none !important;
}
.movo-mobile-menu {
display: none !important;
}
}
/* Mobile Menu */
.movo-hamburger {
background: none;
border: none;
display: flex;
flex-direction: column;
gap: 5px;
cursor: pointer;
padding: 5px;
}
.movo-hamburger span {
display: block;
width: 20px;
height: 2px;
background: #3b3228;
border-radius: 2px;
transition: 0.3s;
}
.movo-mobile-menu {
position: fixed;
top: 80px;
left: 5%;
width: 90%;
background: rgba(255, 255, 255, 0.98);
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
z-index: 20;
padding: 20px;
box-sizing: border-box;
/* Animation states */
opacity: 0;
visibility: hidden;
transform: translateY(-10px) scale(0.95);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.movo-mobile-menu.open {
opacity: 1;
visibility: visible;
transform: translateY(0) scale(1);
}
.movo-mobile-menu nav {
display: flex;
flex-direction: column;
gap: 15px;
align-items: center;
}
.movo-mobile-menu a {
color: #3b3228;
text-decoration: none;
font-weight: 500;
font-size: 1.1rem;
}
/* Main Content */
.movo-content {
position: relative;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
padding: 60px 20px;
flex: 1;
}
.movo-tag {
background: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(0, 0, 0, 0.1);
color: #666;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 1px;
padding: 6px 16px;
border-radius: 20px;
margin-bottom: 20px;
}
.movo-title {
font-family: 'Playfair Display', serif;
font-size: clamp(3rem, 6vw, 4.5rem);
line-height: 1.1;
margin: 0 0 15px 0;
color: #3b3228;
font-weight: 500;
letter-spacing: -1px;
}
.movo-subtitle {
font-size: 1.1rem;
color: #555;
margin: 0 0 30px 0;
}
.movo-actions {
display: flex;
gap: 15px;
margin-bottom: 50px;
}
/* Chat Widget */
.movo-chat-wrapper {
margin-top: 20px;
perspective: 1000px;
}
.movo-chat-box {
width: 380px;
/* Fixed width so it never shrinks when empty */
max-width: 100%;
height: 420px;
/* Fixed height to prevent shrinking */
background: rgba(255, 255, 255, 0.4);
/* Transparente blanco glass */
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border-radius: 30px;
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.08);
padding: 20px 20px 24px;
display: flex;
flex-direction: column;
transform: rotateX(2deg);
/* slight 3D perspective to match the screenshot */
}
.movo-chat-header {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
font-family: 'Playfair Display', serif;
font-weight: 600;
font-size: 1.2rem;
color: #3b3228;
margin-bottom: 20px;
}
.movo-chat-messages {
display: flex;
flex-direction: column;
flex: 1;
/* Takes up remaining height inside the fixed 420px box */
margin-bottom: 15px;
overflow: hidden;
/* No scrollbars, pure GSAP movement */
}
.chat-track {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
}
.movo-chat-messages::-webkit-scrollbar {
display: none;
}
.chat-msg {
display: flex;
gap: 10px;
align-items: flex-end;
/* Native animations completely removed for GSAP */
}
.msg-right {
flex-direction: row-reverse;
}
.msg-bubble {
max-width: 75%;
padding: 12px 16px;
border-radius: 20px;
font-size: 0.85rem;
line-height: 1.4;
color: #3b3228;
}
.msg-right .msg-bubble {
background: #fff;
border-bottom-right-radius: 4px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.04);
}
.msg-left .msg-bubble {
background: #e6e0d8;
border-bottom-left-radius: 4px;
}
.msg-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
background-size: cover;
background-position: center;
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
}
/* Typing Indicator */
.movo-chat-typing {
background: #fff;
padding: 10px 16px;
border-radius: 20px;
border-bottom-right-radius: 4px;
width: fit-content;
display: flex;
gap: 4px;
align-self: flex-end;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.04);
opacity: 0;
transition: opacity 0.3s;
height: 10px;
align-items: center;
margin-top: 10px;
}
.movo-chat-typing.active {
opacity: 1;
}
.dot {
width: 5px;
height: 5px;
background: #888;
border-radius: 50%;
animation: type-bounce 1s infinite;
}
.dot:nth-child(2) {
animation-delay: 0.2s;
}
.dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes type-bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-4px);
}
}
.hidden {
display: none !important;
}
/* Footer */
.movo-footer {
position: relative;
z-index: 3;
text-align: center;
font-family: 'Playfair Display', serif;
font-size: 1.1rem;
color: #3b3228;
padding: 20px;
margin-top: auto;
}
/* Responsive */
@media (max-width: 768px) {
.movo-nav,
.desktop-only {
display: none;
}
.mobile-only {
display: flex;
}
.movo-header {
padding: 15px 20px;
margin: 10px auto;
}
.movo-actions {
flex-direction: column;
width: 100%;
max-width: 300px;
}
.movo-chat-box {
max-width: 90vw;
}
}
/* BIT_CSS_END */
// BIT_JS_START
document.addEventListener('DOMContentLoaded', () => {
// --- Mobile Menu Animation Logic ---
const menuBtn = document.getElementById('movoMenuBtn');
const mobileMenu = document.getElementById('movoMobileMenu');
let isMenuOpen = false;
menuBtn.addEventListener('click', () => {
isMenuOpen = !isMenuOpen;
if (isMenuOpen) {
mobileMenu.classList.add('open');
// Animate hamburger to X (optional enhancement)
menuBtn.children[0].style.transform = 'translateY(7px) rotate(45deg)';
menuBtn.children[1].style.opacity = '0';
menuBtn.children[2].style.transform = 'translateY(-7px) rotate(-45deg)';
} else {
mobileMenu.classList.remove('open');
// Reset hamburger
menuBtn.children[0].style.transform = 'none';
menuBtn.children[1].style.opacity = '1';
menuBtn.children[2].style.transform = 'none';
}
});
// --- Infinite Chat Animation Logic ---
const chatContainer = document.getElementById('movoChatMessages');
const chatTrack = document.getElementById('chatTrack');
const typingIndicator = document.getElementById('movoChatTyping');
// Assets
const movoIcon = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" width="14" height="14">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path>
<circle cx="9" cy="7" r="4"></circle>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"></path>
<path d="M16 3.13a4 4 0 0 1 0 7.75"></path>
</svg>`;
const userAvatarURL = 'https://storage.maquinaespacial.com/media/avatars/001.jpg'; // animated's face
// Sequence of messages
const sequence = [{
type: 'right',
text: "Hey Karen, Jake's session is Saturday at 10am. Still good?",
icon: movoIcon,
delay: 1000
}, {
type: 'left',
text: "Oh no, I forgot. We have a birthday party Saturday.",
avatar: userAvatarURL,
delay: 1500
}, {
type: 'right',
text: "All good. Want me to move him to a different day ?",
icon: movoIcon,
delay: 1500
}, {
type: 'left',
text: "Yes please. That would be amazing.",
avatar: userAvatarURL,
delay: 1200
}];
function createMessageHTML(msg) {
const isRight = msg.type === 'right';
const avatarHTML = isRight ?
`<div class="msg-avatar">${msg.icon}</div>` :
`<div class="msg-avatar" style="background-image: url('${msg.avatar}')"></div>`;
return `
<div class="chat-msg msg-${msg.type}">
${avatarHTML}
<div class="msg-bubble">${msg.text}</div>
</div>
`;
}
async function runContinuousChat() {
let currentIndex = 0;
let currentTrackY = 0;
while (true) {
const msg = sequence[currentIndex % sequence.length];
// Show typing indicator
typingIndicator.classList.remove('hidden');
typingIndicator.style.alignSelf = msg.type === 'right' ? 'flex-end' : 'flex-start';
typingIndicator.style.borderBottomRightRadius = msg.type === 'right' ? '4px' : '20px';
typingIndicator.style.borderBottomLeftRadius = msg.type === 'left' ? '4px' : '20px';
typingIndicator.style.background = msg.type === 'right' ? '#fff' : '#e6e0d8';
// GSAP Fade in typing indicator
gsap.fromTo(typingIndicator, {
opacity: 0,
y: 10
}, {
opacity: 1,
y: 0,
duration: 0.3
});
await new Promise(r => setTimeout(r, msg.delay));
// GSAP Fade out typing indicator
gsap.to(typingIndicator, {
opacity: 0,
y: 10,
duration: 0.3,
onComplete: () => typingIndicator.classList.add('hidden')
});
// Append message using DOM element to track it
const msgWrapper = document.createElement('div');
msgWrapper.innerHTML = createMessageHTML(msg);
const newMsg = msgWrapper.firstElementChild;
// Set initial GSAP state before appending
gsap.set(newMsg, {
opacity: 0,
y: 20
});
chatTrack.appendChild(newMsg);
// GSAP Entrance Animation
gsap.to(newMsg, {
opacity: 1,
y: 0,
duration: 0.5,
ease: "power2.out"
});
// Calculate flawless track slide
const containerHeight = chatContainer.clientHeight;
const trackHeight = chatTrack.scrollHeight;
if (trackHeight > containerHeight) {
const shiftAmount = trackHeight - containerHeight;
currentTrackY = -shiftAmount;
// Slide track up to reveal new message
gsap.to(chatTrack, {
y: currentTrackY,
duration: 0.6,
ease: "power2.inOut",
onComplete: () => {
// After slide finishes, remove oldest message cleanly
const msgs = chatTrack.querySelectorAll('.chat-msg');
if (msgs.length > 4) {
const oldest = msgs[0];
const removedHeight = oldest.offsetHeight + 12; // +12px for gap
oldest.remove();
// Shift the track down instantly to counter the removed element
currentTrackY += removedHeight;
gsap.set(chatTrack, {
y: currentTrackY
});
}
}
});
}
currentIndex++;
await new Promise(r => setTimeout(r, 800));
}
}
// Start infinite continuous loop
runContinuousChat();
});
// BIT_JS_END
Fami Infinite Chat Hero
This Bit is a high-conversion, cinematic hero section designed for modern AI and service platforms. It combines an immersive visual aesthetic with a highly engaging, infinitely scrolling chat widget.
Features
- Immersive Vignette Background: Uses a layered combination of CSS
radial-gradient and halftone textures to create a deep, glowing vignette effect that focuses attention on the central content.
- Infinite Chat Animation (GSAP): A flawless, buttery-smooth chat simulator that writes messages, scrolls automatically, and loops infinitely without any visual jumping or DOM thrashing.
- Glassmorphism Chat Widget: The chat box utilizes advanced CSS
backdrop-filter to create a stunning frosted glass effect over the background image.
- Fully Responsive: Includes a mobile-first hamburger menu that animates smoothly, and layout adjustments to keep the chat widget perfectly proportioned on smaller screens.
Usage
The chat logic is driven by GSAP and Vanilla JavaScript. You can easily modify the sequence of messages, avatars, and timing delays directly inside script.js in the sequence array.
Design Decisions
Visual Concept
The goal was to create an editorial, human-centric design that feels both warm and technologically advanced. The "Fami" brand aims for a friendly approach to AI, balancing a natural outdoor aesthetic with a sleek, translucent UI interface.
Color Palette
- Typography Base (
#3B3228): A very dark, earthy brown rather than pure black. This provides high contrast but keeps the tone natural and approachable.
- Chat Bubbles (
#E6E0D8 & #FFFFFF): The user chat bubbles use a warm beige tone, contrasting with the pure white brand messages, maintaining the soft aesthetic.
- Glassmorphism (
rgba(255,255,255,0.4)): The chat container uses a semi-transparent white base with a heavy blur(24px) to blend organically with the background.
Typography
- Playfair Display: A high-contrast, elegant serif font used for the main hero title ("A personal journey for every family.") and the brand logo, injecting authority and warmth.
- Inter: A clean, highly legible sans-serif font used for the chat messages, pill tags, and action buttons to ensure optimal readability.
Technical Skills & Techniques Used
1. Infinite Carousel Architecture via GSAP
To create a perfectly smooth, infinite scrolling chat without native browser "jank":
* GSAP Transform Animation: Instead of native scrollTop, GSAP animates the y transform of a .chat-track wrapper. This bypasses the browser's layout engine for buttery 60FPS movement.
* Flawless Element Recycling: Once the track moves up, the oldest message is completely removed from the DOM. Simultaneously, GSAP instantly readjusts the y coordinates to exactly offset the removed element's height. This mathematical trick allows the loop to run forever with zero visual stutter.
2. Advanced CSS Vignette
- Multi-layered Gradients: The hero uses
linear-gradient and radial-gradient layered over the background image. This creates a strong, non-destructive white fade at the bottom edges, simulating an expensive studio lighting setup purely through CSS.
- Halftone Textures: A subtle
radial-gradient pattern of tiny dots provides a premium print-like texture over the image.
3. Strict Glassmorphism Control
- Fixed Dimensions: The chat box uses strict
width and height properties to prevent the glassmorphism panel from shrinking or expanding when the chat track inside it alters its content volume.