Overview
The hero section is the first visual element visitors see. It features an animated particle background, an interactive SVG character that responds to mouse movements, and a drawing canvas where users can create their own greetings.
HTML Structure
index.html (lines 60-105)
< section id = "home" class = "relative min-h-screen flex items-center justify-center overflow-hidden" >
<!-- Canvas for particle background -->
< canvas id = "particlesCanvas" class = "absolute inset-0 z-0 pointer-events-none max-w-screen-xl mx-auto" ></ canvas >
< div class = "container relative z-10 mx-auto px-6 py-24 flex flex-col md:flex-row items-center gap-12" >
<!-- Hero Text -->
< div class = "md:w-1/2 text-center md:text-left" >
< h2 class = "text-4xl md:text-6xl font-bold mb-6" >
Hola, soy < span class = "text-red-accent" > NILVER T.I </ span >
</ h2 >
< p class = "text-xl md:text-2xl text-gray-300 mb-8" >
Desarrollador de Tecnologías de la Información especializado en soluciones web modernas.
</ p >
< div class = "flex flex-col sm:flex-row sm:justify-start justify-center gap-4" >
< a href = "#projects" class = "bg-red-accent hover:bg-red-700 text-white px-6 py-3 rounded-md font-medium transition hover:scale-105" >
Ver proyectos
</ a >
< a href = "#contact" class = "border border-red-accent text-red-accent hover:bg-red-900 hover:bg-opacity-20 px-6 py-3 rounded-md font-medium transition hover:scale-105" >
Contactar
</ a >
</ div >
</ div >
<!-- Animated SVG Character -->
< div class = "md:w-1/2 flex justify-center" >
< svg id = "profileSvg" width = "400" height = "400" viewBox = "0 0 200 200" class = "w-full max-w-xs" >
< circle cx = "100" cy = "100" r = "80" fill = "none" stroke = "#333" stroke-width = "2" />
< circle cx = "100" cy = "100" r = "60" fill = "none" stroke = "#333" stroke-width = "2" />
< circle cx = "100" cy = "100" r = "40" fill = "none" stroke = "#333" stroke-width = "2" />
< path id = "faceOutline" fill = "none" stroke = "#E50914" stroke-width = "3" stroke-linecap = "round"
d = "M60,100 Q100,150 140,100" />
< circle id = "leftEye" cx = "80" cy = "80" r = "10" fill = "#E50914" />
< circle id = "rightEye" cx = "120" cy = "80" r = "10" fill = "#E50914" />
</ svg >
</ div >
<!-- Drawing Canvas -->
< div class = "mt-8 text-center" >
< p class = "text-gray-400 mb-2" > Dibuja tu saludo: </ p >
< canvas id = "drawingCanvas" width = "300" height = "150" class = "drawing-canvas bg-black rounded-md" ></ canvas >
< button id = "clearCanvas" class = "mt-2 text-sm text-gray-400 hover:text-red-accent" > Limpiar </ button >
</ div >
</ div >
</ section >
Particle Background Animation
The particle background creates a dynamic visual effect using HTML5 Canvas.
Implementation
const canvas = document . getElementById ( 'particlesCanvas' );
const ctx = canvas . getContext ( '2d' );
let particles = [];
// Resize canvas to match section height
function resizeCanvas () {
canvas . width = window . innerWidth ;
canvas . height = document . getElementById ( 'home' ). offsetHeight ;
}
window . addEventListener ( 'resize' , resizeCanvas );
resizeCanvas ();
// Create 100 particles with random properties
for ( let i = 0 ; i < 100 ; i ++ ) {
particles . push ({
x: Math . random () * canvas . width ,
y: Math . random () * canvas . height ,
radius: Math . random () * 2 + 1 ,
speedX: Math . random () * 0.6 - 0.3 ,
speedY: Math . random () * 0.6 - 0.3
});
}
// Animation loop
function animateParticles () {
ctx . clearRect ( 0 , 0 , canvas . width , canvas . height );
for ( let p of particles ) {
// Update position
p . x += p . speedX ;
p . y += p . speedY ;
// Bounce off edges
if ( p . x < 0 || p . x > canvas . width ) p . speedX *= - 1 ;
if ( p . y < 0 || p . y > canvas . height ) p . speedY *= - 1 ;
// Draw particle
ctx . beginPath ();
ctx . arc ( p . x , p . y , p . radius , 0 , 2 * Math . PI );
ctx . fillStyle = 'rgba(229, 9, 20, 0.4)' ;
ctx . fill ();
}
requestAnimationFrame ( animateParticles );
}
animateParticles ();
The animation uses requestAnimationFrame for smooth 60fps rendering. Particles bounce off canvas edges by reversing their velocity when reaching boundaries.
Customizing Particles
Particle Count
Particle Speed
Particle Size
Particle Color
Change the number of particles: for ( let i = 0 ; i < 150 ; i ++ ) { // Increase from 100 to 150
particles . push ({ ... });
}
Adjust speed range: speedX : Math . random () * 1.2 - 0.6 , // Faster movement
speedY : Math . random () * 1.2 - 0.6
Change radius range: radius : Math . random () * 4 + 2 , // Larger particles (2-6px)
Modify color and opacity: ctx . fillStyle = 'rgba(59, 130, 246, 0.6)' ; // Blue particles
Animated SVG Character
The SVG character’s eyes follow the mouse cursor, and the mouth expression changes based on mouse position.
SVG Structure
< svg id = "profileSvg" width = "400" height = "400" viewBox = "0 0 200 200" >
<!-- Concentric circles background -->
< circle cx = "100" cy = "100" r = "80" fill = "none" stroke = "#333" stroke-width = "2" />
< circle cx = "100" cy = "100" r = "60" fill = "none" stroke = "#333" stroke-width = "2" />
< circle cx = "100" cy = "100" r = "40" fill = "none" stroke = "#333" stroke-width = "2" />
<!-- Mouth (smile curve) -->
< path id = "faceOutline" fill = "none" stroke = "#E50914" stroke-width = "3"
stroke-linecap = "round" d = "M60,100 Q100,150 140,100" />
<!-- Eyes -->
< circle id = "leftEye" cx = "80" cy = "80" r = "10" fill = "#E50914" />
< circle id = "rightEye" cx = "120" cy = "80" r = "10" fill = "#E50914" />
</ svg >
Mouse Tracking Animation
const profileSvg = document . getElementById ( 'profileSvg' );
if ( profileSvg ) {
const leftEye = document . getElementById ( 'leftEye' );
const rightEye = document . getElementById ( 'rightEye' );
const faceOutline = document . getElementById ( 'faceOutline' );
profileSvg . addEventListener ( 'mousemove' , e => {
const rect = profileSvg . getBoundingClientRect ();
// Convert mouse position to SVG coordinates
const x = ( e . clientX - rect . left ) / rect . width * 200 ;
const y = ( e . clientY - rect . top ) / rect . height * 200 ;
// Move eyes toward mouse (5% of distance)
leftEye . setAttribute ( 'cx' , 80 + ( x - 80 ) * 0.05 );
leftEye . setAttribute ( 'cy' , 80 + ( y - 80 ) * 0.05 );
rightEye . setAttribute ( 'cx' , 120 + ( x - 120 ) * 0.05 );
rightEye . setAttribute ( 'cy' , 80 + ( y - 80 ) * 0.05 );
// Adjust mouth curve based on mouse Y position
faceOutline . setAttribute ( 'd' , `M60,100 Q100, ${ 130 + ( y - 100 ) * 0.2 } 140,100` );
});
// Reset to default position when mouse leaves
profileSvg . addEventListener ( 'mouseleave' , () => {
leftEye . setAttribute ( 'cx' , '80' );
leftEye . setAttribute ( 'cy' , '80' );
rightEye . setAttribute ( 'cx' , '120' );
rightEye . setAttribute ( 'cy' , '80' );
faceOutline . setAttribute ( 'd' , 'M60,100 Q100,150 140,100' );
});
}
Coordinate Conversion: const x = ( e . clientX - rect . left ) / rect . width * 200 ;
Converts browser coordinates to SVG viewBox coordinates (0-200) Eye Movement: leftEye . setAttribute ( 'cx' , 80 + ( x - 80 ) * 0.05 );
Moves the eye 5% of the distance between current position (80) and mouse position (x) Mouth Curve: faceOutline . setAttribute ( 'd' , `M60,100 Q100, ${ 130 + ( y - 100 ) * 0.2 } 140,100` );
Uses SVG quadratic curve (Q) where control point Y changes based on mouse Y position
Interactive Drawing Canvas
Users can draw directly on the canvas with their mouse.
Canvas Implementation
const canvas = document . getElementById ( 'drawingCanvas' );
if ( canvas ) {
const ctx = canvas . getContext ( '2d' );
let isDrawing = false ;
// Mouse event listeners
canvas . addEventListener ( 'mousedown' , e => {
isDrawing = true ;
draw ( e );
});
canvas . addEventListener ( 'mousemove' , draw );
canvas . addEventListener ( 'mouseup' , () => isDrawing = false );
canvas . addEventListener ( 'mouseout' , () => isDrawing = false );
function draw ( e ) {
if ( ! isDrawing ) return ;
// Configure brush
ctx . lineWidth = 2 ;
ctx . lineCap = 'round' ;
ctx . strokeStyle = '#E50914' ;
// Get mouse position relative to canvas
const rect = canvas . getBoundingClientRect ();
const x = e . clientX - rect . left ;
const y = e . clientY - rect . top ;
// Draw line from last position to current
if ( ctx . lastX && ctx . lastY ) {
ctx . beginPath ();
ctx . moveTo ( ctx . lastX , ctx . lastY );
ctx . lineTo ( x , y );
ctx . stroke ();
}
// Store current position for next frame
ctx . lastX = x ;
ctx . lastY = y ;
}
// Clear canvas button
document . getElementById ( 'clearCanvas' )?. addEventListener ( 'click' , () => {
ctx . clearRect ( 0 , 0 , canvas . width , canvas . height );
});
}
Canvas Styling
estilos.css (lines 56-59)
.drawing-canvas {
border : 1 px solid #E50914 ;
cursor : crosshair ;
}
Hero Text and CTAs
The hero text includes responsive typography and call-to-action buttons.
< h2 class = "text-4xl md:text-6xl font-bold mb-6" >
Hola, soy < span class = "text-red-accent" > NILVER T.I </ span >
</ h2 >
estilos.css (lines 22-28)
.hover-scale {
transition : transform 0.3 s ease ;
}
.hover-scale:hover {
transform : scale ( 1.05 );
}
Responsive Layout
The hero section adapts to different screen sizes:
Element Mobile Desktop Layout flex-col (stacked)md:flex-row (side-by-side)Heading text-4xlmd:text-6xlText align text-centermd:text-leftSVG size max-w-xsw-full max-w-xsButtons flex-colsm:flex-row
Customization Examples
Change Hero Background Color
< section id = "home" class = "relative min-h-screen flex items-center justify-center overflow-hidden bg-gradient-to-br from-gray-900 to-black" >
Add Touch Support for Drawing Canvas
// Add touch event listeners
canvas . addEventListener ( 'touchstart' , e => {
isDrawing = true ;
const touch = e . touches [ 0 ];
draw ({ clientX: touch . clientX , clientY: touch . clientY });
e . preventDefault ();
});
canvas . addEventListener ( 'touchmove' , e => {
if ( ! isDrawing ) return ;
const touch = e . touches [ 0 ];
draw ({ clientX: touch . clientX , clientY: touch . clientY });
e . preventDefault ();
});
canvas . addEventListener ( 'touchend' , () => {
isDrawing = false ;
ctx . lastX = null ;
ctx . lastY = null ;
});
Modify SVG Character Colors
<!-- Change eyes to blue -->
< circle id = "leftEye" cx = "80" cy = "80" r = "10" fill = "#3B82F6" />
< circle id = "rightEye" cx = "120" cy = "80" r = "10" fill = "#3B82F6" />
<!-- Change mouth to green -->
< path id = "faceOutline" fill = "none" stroke = "#10B981" stroke-width = "3" stroke-linecap = "round" d = "M60,100 Q100,150 140,100" />
The particle animation runs continuously. Consider pausing when the hero section is not visible: let animationId ;
function animateParticles () {
// ... animation code ...
animationId = requestAnimationFrame ( animateParticles );
}
// Pause when out of view
const observer = new IntersectionObserver ( entries => {
if ( entries [ 0 ]. isIntersecting ) {
animateParticles ();
} else {
cancelAnimationFrame ( animationId );
}
});
observer . observe ( document . getElementById ( 'home' ));
Reduce Particles Lower particle count from 100 to 50 for better mobile performance
Optimize Canvas Clear drawing canvas area only instead of full canvas
Throttle Mouse Events Use requestAnimationFrame to throttle SVG mouse tracking
Lazy Load Initialize animations only when hero section is visible
Troubleshooting
Ensure canvas has proper z-index and dimensions: #particlesCanvas {
position : absolute ;
z-index : 0 ;
pointer-events : none ;
}
SVG not responding to mouse
Check that SVG has proper event listeners and IDs match: console . log ( document . getElementById ( 'profileSvg' )); // Should not be null
console . log ( document . getElementById ( 'leftEye' )); // Should not be null
Drawing canvas not working
Verify canvas context is 2D and dimensions are set: const ctx = canvas . getContext ( '2d' );
console . log ( canvas . width , canvas . height ); // Should show 300, 150