Ocean-themed animation with water droplets, waves, and the word "zen" floating peacefully
Copy URL or Code
Paste to your AI coding assistant and customize:
Done. Your AI handles the rest.
Fully customizable. A calming ocean-themed Canvas animation with bouncing water droplets, rising bubbles, gentle waves, and the word "zen" floating peacefully. Perfect for meditation apps, wellness themes, or adding tranquil vibes.
/*
================================================================================
AI COMPONENT: BouncyZen (Ocean Animation)
================================================================================
SETUP:
1. Create file: components/BouncyLove.tsx
2. Copy this entire code block into that file
3. Import: import BouncyLove from "@/components/BouncyLove"
4. No external dependencies - pure Canvas API
================================================================================
QUICK CUSTOMIZATION (via props)
================================================================================
| Prop | Type | Default | Description |
|------------|--------|---------|--------------------------------|
| size | number | 400 | Width and height in pixels |
| className | string | "" | Additional CSS classes |
================================================================================
ANIMATION FEATURES
================================================================================
- Ocean gradient background (deep blue to teal)
- Bouncing water droplets with squash/stretch physics
- Rising bubbles with wobble effect
- Layered wave animation at bottom
- Floating "zen" text with gradient fill
- Wavy underline accent
- Continuous 60fps animation loop
================================================================================
COLOR PALETTE (easily customizable)
================================================================================
| Name | Hex | Usage |
|------------|-----------|--------------------------------|
| DEEP_BLUE | #1A3A5C | Background gradient top/bottom |
| OCEAN_BLUE | #2B7D9E | Background gradient middle |
| LIGHT_BLUE | #82CBE8 | Water drops, waves, text |
| AQUA | #00CCA8 | Water drops, waves, underline |
| WHITE | #FFFFFF | Highlights, bubbles |
| FOAM | #EAF6FA | Text gradient, water drops |
================================================================================
COMMON MODIFICATIONS
================================================================================
1. CHANGE SIZE:
<BouncyLove size={600} />
2. CHANGE TEXT (edit glyphs array in code):
const glyphs = [
{ char: "h", x: centerX - 54, y: centerY, fontSize: 56, waveOffset: 0, stagger: 0 },
{ char: "i", x: centerX - 18, y: centerY, fontSize: 56, waveOffset: 0.2, stagger: 100 },
// ... add more characters
];
3. CHANGE COLORS (edit COLORS object):
const COLORS = {
DEEP_BLUE: "#your-color",
OCEAN_BLUE: "#your-color",
// ...
};
4. ADJUST ANIMATION SPEED:
- Water drops: change `velocity` values (lower = slower)
- Text: change `0.028` in renderGlyph (lower = slower)
- Waves: change `waveSpeed` in drawWater
================================================================================
SOURCE CODE
================================================================================
*/
"use client";
import React, { useRef, useEffect } from "react";
export interface BouncyLoveProps {
size?: number;
className?: string;
}
export default function BouncyLove({
size = 400,
className = "",
}: BouncyLoveProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
// Load Montserrat font from Google Fonts
const link = document.createElement("link");
link.href = "https://fonts.googleapis.com/css2?family=Montserrat:wght@600&display=swap";
link.rel = "stylesheet";
if (!document.querySelector('link[href*="Montserrat"]')) {
document.head.appendChild(link);
}
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Handle high DPI displays
const dpr = window.devicePixelRatio || 1;
canvas.width = size * dpr;
canvas.height = size * dpr;
ctx.scale(dpr, dpr);
const centerX = size / 2;
const centerY = size / 2;
// Ocean/Water Palette - customize these colors
const COLORS = {
DEEP_BLUE: "#1A3A5C",
OCEAN_BLUE: "#2B7D9E",
LIGHT_BLUE: "#82CBE8",
AQUA: "#00CCA8",
WHITE: "#FFFFFF",
FOAM: "#EAF6FA",
};
let frame = 0;
let animationId: number;
// Water drop configuration
interface WaterDrop {
x: number; y: number; radius: number; tint: string;
offset: number; bounce: number; velocity: number;
}
// Text glyph configuration
interface TextGlyph {
char: string; x: number; y: number; fontSize: number;
waveOffset: number; stagger: number;
}
// Create floating water drops
const waterDrops: WaterDrop[] = [
{ x: centerX - 90, y: centerY - 50, radius: 12, tint: COLORS.LIGHT_BLUE, offset: 0, bounce: 35, velocity: 0.038 },
{ x: centerX + 90, y: centerY - 30, radius: 10, tint: COLORS.AQUA, offset: Math.PI * 0.5, bounce: 30, velocity: 0.048 },
{ x: centerX - 70, y: centerY + 60, radius: 14, tint: COLORS.WHITE, offset: Math.PI, bounce: 40, velocity: 0.033 },
{ x: centerX + 70, y: centerY + 40, radius: 8, tint: COLORS.FOAM, offset: Math.PI * 1.5, bounce: 25, velocity: 0.043 },
{ x: centerX, y: centerY - 70, radius: 11, tint: COLORS.LIGHT_BLUE, offset: Math.PI * 0.3, bounce: 32, velocity: 0.04 },
{ x: centerX - 40, y: centerY + 80, radius: 9, tint: COLORS.AQUA, offset: Math.PI * 0.7, bounce: 28, velocity: 0.046 },
{ x: centerX + 50, y: centerY - 60, radius: 13, tint: COLORS.WHITE, offset: Math.PI * 1.2, bounce: 36, velocity: 0.036 },
];
// Create text glyphs - change these to display different text
const glyphs: TextGlyph[] = [
{ char: "z", x: centerX - 36, y: centerY, fontSize: 56, waveOffset: 0, stagger: 0 },
{ char: "e", x: centerX, y: centerY, fontSize: 56, waveOffset: 0.2, stagger: 100 },
{ char: "n", x: centerX + 36, y: centerY, fontSize: 56, waveOffset: 0.4, stagger: 200 },
];
// Bubble particle system
interface Bubble { x: number; y: number; size: number; speed: number; wobble: number; opacity: number; }
let bubbles: Bubble[] = [];
function createBubble() {
bubbles.push({
x: Math.random() * size, y: size + 10,
size: 3 + Math.random() * 8, speed: 0.5 + Math.random() * 1.5,
wobble: Math.random() * Math.PI * 2, opacity: 0.3 + Math.random() * 0.5,
});
}
// Initialize bubbles
for (let i = 0; i < 15; i++) {
bubbles.push({
x: Math.random() * size, y: Math.random() * size,
size: 3 + Math.random() * 8, speed: 0.5 + Math.random() * 1.5,
wobble: Math.random() * Math.PI * 2, opacity: 0.3 + Math.random() * 0.5,
});
}
// Draw layered waves at bottom
function drawWater(time: number) {
for (let layer = 0; layer < 3; layer++) {
const waveHeight = 15 - layer * 3;
const waveSpeed = 0.02 + layer * 0.005;
const yOffset = size - 60 + layer * 20;
const alpha = 0.3 - layer * 0.08;
ctx.beginPath();
ctx.moveTo(0, size);
for (let x = 0; x <= size; x += 5) {
const y = yOffset + Math.sin(x * 0.02 + time * waveSpeed + layer) * waveHeight;
ctx.lineTo(x, y);
}
ctx.lineTo(size, size);
ctx.closePath();
ctx.fillStyle = layer === 0 ? COLORS.LIGHT_BLUE : layer === 1 ? COLORS.AQUA : COLORS.FOAM;
ctx.globalAlpha = alpha;
ctx.fill();
ctx.globalAlpha = 1;
}
}
// Draw water drop with squash/stretch
function renderWaterDrop(drop: WaterDrop, time: number) {
const wave = Math.sin(time * drop.velocity + drop.offset);
const posY = drop.y + wave * drop.bounce;
ctx.save();
ctx.translate(drop.x, posY);
const deform = 1 + wave * 0.18;
ctx.scale(1 / deform, deform);
// Drop body
ctx.beginPath();
ctx.moveTo(0, -drop.radius * 1.2);
ctx.bezierCurveTo(drop.radius * 0.8, -drop.radius * 0.5, drop.radius * 0.8, drop.radius * 0.8, 0, drop.radius);
ctx.bezierCurveTo(-drop.radius * 0.8, drop.radius * 0.8, -drop.radius * 0.8, -drop.radius * 0.5, 0, -drop.radius * 1.2);
ctx.fillStyle = drop.tint;
ctx.globalAlpha = 0.82;
ctx.fill();
// Highlight
ctx.beginPath();
ctx.arc(-drop.radius * 0.3, -drop.radius * 0.3, drop.radius * 0.25, 0, Math.PI * 2);
ctx.fillStyle = COLORS.WHITE;
ctx.globalAlpha = 0.55;
ctx.fill();
ctx.restore();
ctx.globalAlpha = 1;
}
// Draw floating text glyph
function renderGlyph(glyph: TextGlyph, time: number) {
const t = Math.max(0, time - glyph.stagger);
const float = Math.sin(t * 0.028 + glyph.waveOffset * Math.PI * 2);
const scaleFactor = 1 + float * 0.08;
const posY = glyph.y + float * 14;
const tilt = float * 0.06;
ctx.save();
ctx.translate(glyph.x, posY);
ctx.rotate(tilt);
ctx.scale(scaleFactor, scaleFactor);
ctx.font = \`600 \${glyph.fontSize}px 'Montserrat', sans-serif\`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.shadowColor = COLORS.DEEP_BLUE;
ctx.shadowBlur = 12;
ctx.shadowOffsetY = 3;
const grad = ctx.createLinearGradient(0, -glyph.fontSize / 2, 0, glyph.fontSize / 2);
grad.addColorStop(0, COLORS.WHITE);
grad.addColorStop(0.5, COLORS.FOAM);
grad.addColorStop(1, COLORS.LIGHT_BLUE);
ctx.fillStyle = grad;
ctx.fillText(glyph.char, 0, 0);
ctx.restore();
}
// Draw bubble
function drawBubble(bubble: Bubble, time: number) {
const wobbleX = Math.sin(time * 0.05 + bubble.wobble) * 3;
ctx.save();
ctx.globalAlpha = bubble.opacity;
ctx.beginPath();
ctx.arc(bubble.x + wobbleX, bubble.y, bubble.size, 0, Math.PI * 2);
ctx.strokeStyle = COLORS.WHITE;
ctx.lineWidth = 1;
ctx.stroke();
ctx.beginPath();
ctx.arc(bubble.x + wobbleX - bubble.size * 0.3, bubble.y - bubble.size * 0.3, bubble.size * 0.3, 0, Math.PI * 2);
ctx.fillStyle = COLORS.WHITE;
ctx.globalAlpha = bubble.opacity * 0.8;
ctx.fill();
ctx.restore();
}
// Draw wavy underline
function drawWavyLine(time: number) {
ctx.beginPath();
ctx.strokeStyle = COLORS.AQUA;
ctx.lineWidth = 3;
ctx.lineCap = "round";
ctx.globalAlpha = 0.7;
const lineY = centerY + 45;
ctx.moveTo(centerX - 80, lineY);
for (let i = 0; i <= 160; i += 4) {
const x = centerX - 80 + i;
const waveY = lineY + Math.sin(i * 0.08 + time * 0.08) * 5;
ctx.lineTo(x, waveY);
}
ctx.stroke();
ctx.globalAlpha = 1;
}
// Main animation loop
function animate() {
frame++;
// Background gradient
const bgGradient = ctx.createLinearGradient(0, 0, 0, size);
bgGradient.addColorStop(0, COLORS.DEEP_BLUE);
bgGradient.addColorStop(0.5, COLORS.OCEAN_BLUE);
bgGradient.addColorStop(1, COLORS.DEEP_BLUE);
ctx.fillStyle = bgGradient;
ctx.fillRect(0, 0, size, size);
drawWater(frame);
if (frame % 30 === 0) createBubble();
bubbles = bubbles.filter((b) => {
b.y -= b.speed;
b.wobble += 0.02;
if (b.y > -20) { drawBubble(b, frame); return true; }
return false;
});
waterDrops.forEach((drop) => renderWaterDrop(drop, frame));
glyphs.forEach((glyph) => renderGlyph(glyph, frame));
drawWavyLine(frame);
animationId = requestAnimationFrame(animate);
}
animate();
return () => cancelAnimationFrame(animationId);
}, [size]);
return (
<div className={`relative ${className}`}>
<canvas ref={canvasRef} style={{ width: size, height: size, borderRadius: "8px" }} />
</div>
);
}
export { BouncyLove };import BouncyLove from "@/components/BouncyLove";
// Basic usage - continuous animation
export default function ZenAnimation() {
return (
<div className="flex items-center justify-center min-h-screen bg-gray-900">
<BouncyLove size={400} />
</div>
);
}
// Different sizes
<BouncyLove size={200} /> // Small
<BouncyLove size={400} /> // Medium (default)
<BouncyLove size={600} /> // Large
// With custom styling
<BouncyLove size={400} className="rounded-2xl shadow-2xl" />
// Full-screen hero section
export function HeroSection() {
return (
<div className="w-full h-screen flex items-center justify-center bg-slate-900">
<BouncyLove size={Math.min(window.innerWidth, window.innerHeight) * 0.8} />
</div>
);
}