Overview
The skills section displays technical proficiencies in two formats: circular progress indicators using SVG, and traditional horizontal bar charts. Users can toggle between views with a button.
HTML Structure
Section Container
index.html (lines 108-111)
< section id = "skills" class = "py-20 bg-black bg-opacity-50" >
< div class = "container mx-auto px-6" >
< h2 class = "text-3xl md:text-4xl font-bold mb-16 text-center" >
Mis < span class = "red-accent" > Habilidades </ span >
</ h2 >
index.html (lines 113-118)
< div class = "flex justify-center mb-8" >
< button id = "viewToggle"
class = "bg-gray-800 text-white px-4 py-2 rounded-md hover:bg-gray-700 transition" >
Cambiar a vista de barras
</ button >
</ div >
Circular View
The default view shows skills as animated circular progress indicators.
Single Skill Card
index.html (lines 122-134)
< div class = "skill-item text-center p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< div class = "relative w-32 h-32 mx-auto mb-4" >
< svg width = "120" height = "120" viewBox = "0 0 120 120" class = "mx-auto" >
<!-- Background circle -->
< circle cx = "60" cy = "60" r = "50" fill = "none" stroke = "#333" stroke-width = "8" />
<!-- Progress circle -->
< circle class = "skill-circle" cx = "60" cy = "60" r = "50"
style = "--dash-offset: 94.2;" data-value = "70" />
<!-- Percentage text -->
< text x = "60" y = "65" text-anchor = "middle" fill = "#fff" font-size = "20" > 70% </ text >
</ svg >
</ div >
< h3 class = "text-xl font-semibold mb-2" > HTML5 & CSS3 </ h3 >
< p class = "text-gray-400" > Maquetación web semántica y estilos avanzados </ p >
</ div >
SVG Circle Animation
The circular progress is created using SVG stroke-dasharray and stroke-dashoffset:
estilos.css (lines 30-43)
.skill-circle {
stroke : #E50914 ;
stroke-width : 10 ;
fill : transparent ;
stroke-dasharray : 314 ; /* Total circle circumference (2πr) */
stroke-dashoffset : 314 ; /* Start hidden */
animation : fillCircle 1.5 s forwards ;
}
@keyframes fillCircle {
to {
stroke-dashoffset : var ( --dash-offset ); /* Animate to percentage */
}
}
Understanding the Circle Math
Circle Circumference:
Radius (r) = 50
Circumference = 2πr = 2 × 3.14 × 50 = 314
Calculating Dash Offset:
For 70%: 314 - (314 × 0.70) = 314 - 219.8 = 94.2
For 80%: 314 - (314 × 0.80) = 314 - 251.2 = 62.8
For 50%: 314 - (314 × 0.50) = 314 - 157 = 157
The stroke-dashoffset determines how much of the circle is visible.
All Skills (Circular View)
HTML5 & CSS3
JavaScript
Node.js
React
UI/UX Design
< div class = "skill-item text-center p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< div class = "relative w-32 h-32 mx-auto mb-4" >
< svg width = "120" height = "120" viewBox = "0 0 120 120" class = "mx-auto" >
< circle cx = "60" cy = "60" r = "50" fill = "none" stroke = "#333" stroke-width = "8" />
< circle class = "skill-circle" cx = "60" cy = "60" r = "50"
style = "--dash-offset: 94.2;" data-value = "70" />
< text x = "60" y = "65" text-anchor = "middle" fill = "#fff" font-size = "20" > 70% </ text >
</ svg >
</ div >
< h3 class = "text-xl font-semibold mb-2" > HTML5 & CSS3 </ h3 >
< p class = "text-gray-400" > Maquetación web semántica y estilos avanzados </ p >
</ div >
< div class = "skill-item text-center p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< div class = "relative w-32 h-32 mx-auto mb-4" >
< svg width = "120" height = "120" viewBox = "0 0 120 120" class = "mx-auto" >
< circle cx = "60" cy = "60" r = "50" fill = "none" stroke = "#333" stroke-width = "8" />
< circle class = "skill-circle" cx = "60" cy = "60" r = "50"
style = "--dash-offset: 62.8;" data-value = "80" />
< text x = "60" y = "65" text-anchor = "middle" fill = "#fff" font-size = "20" > 80% </ text >
</ svg >
</ div >
< h3 class = "text-xl font-semibold mb-2" > JavaScript </ h3 >
< p class = "text-gray-400" > Interactividad y lógica del lado del cliente </ p >
</ div >
< div class = "skill-item text-center p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< div class = "relative w-32 h-32 mx-auto mb-4" >
< svg width = "120" height = "120" viewBox = "0 0 120 120" class = "mx-auto" >
< circle cx = "60" cy = "60" r = "50" fill = "none" stroke = "#333" stroke-width = "8" />
< circle class = "skill-circle" cx = "60" cy = "60" r = "50"
style = "--dash-offset: 125.6;" data-value = "60" />
< text x = "60" y = "65" text-anchor = "middle" fill = "#fff" font-size = "20" > 60% </ text >
</ svg >
</ div >
< h3 class = "text-xl font-semibold mb-2" > Node.js </ h3 >
< p class = "text-gray-400" > Desarrollo backend con JavaScript </ p >
</ div >
< div class = "skill-item text-center p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< div class = "relative w-32 h-32 mx-auto mb-4" >
< svg width = "120" height = "120" viewBox = "0 0 120 120" class = "mx-auto" >
< circle cx = "60" cy = "60" r = "50" fill = "none" stroke = "#333" stroke-width = "8" />
< circle class = "skill-circle" cx = "60" cy = "60" r = "50"
style = "--dash-offset: 157;" data-value = "50" />
< text x = "60" y = "65" text-anchor = "middle" fill = "#fff" font-size = "20" > 50% </ text >
</ svg >
</ div >
< h3 class = "text-xl font-semibold mb-2" > React </ h3 >
< p class = "text-gray-400" > Desarrollo de interfaces modernas </ p >
</ div >
< div class = "skill-item text-center p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< div class = "relative w-32 h-32 mx-auto mb-4" >
< svg width = "120" height = "120" viewBox = "0 0 120 120" class = "mx-auto" >
< circle cx = "60" cy = "60" r = "50" fill = "none" stroke = "#333" stroke-width = "8" />
< circle class = "skill-circle" cx = "60" cy = "60" r = "50"
style = "--dash-offset: 31.4;" data-value = "90" />
< text x = "60" y = "65" text-anchor = "middle" fill = "#fff" font-size = "20" > 90% </ text >
</ svg >
</ div >
< h3 class = "text-xl font-semibold mb-2" > UI/UX Design </ h3 >
< p class = "text-gray-400" > Diseño de interfaces y experiencia de usuario </ p >
</ div >
Bar Chart View
The alternative view displays skills as horizontal progress bars.
Bar Chart HTML
index.html (lines 194-243)
< div id = "barView" class = "hidden grid grid-cols-1 gap-8 mt-10 max-w-4xl mx-auto" >
<!-- Single Skill Bar -->
< div class = "skill-bar-item p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< h3 class = "text-xl font-semibold mb-2" > HTML5 & CSS3 </ h3 >
< p class = "text-gray-400 mb-4" > Maquetación web semántica y estilos avanzados </ p >
< div class = "w-full bg-gray-800 rounded-full h-4" >
< div class = "bg-red-accent h-4 rounded-full" style = "width: 70%" ></ div >
</ div >
< p class = "text-right mt-2 text-gray-400" > 70% </ p >
</ div >
</ div >
Toggle Functionality
The JavaScript dynamically switches between circular and bar views.
Toggle Implementation
const viewToggle = document . getElementById ( 'viewToggle' );
const circularView = document . getElementById ( 'circularView' );
const barView = document . getElementById ( 'barView' );
viewToggle ?. addEventListener ( 'click' , () => {
if ( ! circularView || ! barView ) return ;
const isCircularHidden = circularView . classList . contains ( 'hidden' );
// Toggle visibility
circularView . classList . toggle ( 'hidden' , ! isCircularHidden );
barView . classList . toggle ( 'hidden' , isCircularHidden );
// Update button text
viewToggle . textContent = isCircularHidden
? 'Cambiar a vista de barras'
: 'Cambiar a vista circular' ;
if ( ! isCircularHidden ) {
// Switching to bar view - dynamically generate bars
barView . innerHTML = '' ;
document . querySelectorAll ( '.skill-item' ). forEach ( item => {
const title = item . querySelector ( 'h3' )?. textContent || '' ;
const desc = item . querySelector ( 'p' )?. textContent || '' ;
const val = parseInt ( item . querySelector ( 'text' )?. textContent || '0' );
barView . innerHTML += `
<div class="skill-bar-item p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer">
<h3 class="text-xl font-semibold mb-2"> ${ title } </h3>
<p class="text-gray-400 mb-4"> ${ desc } </p>
<div class="w-full bg-gray-800 rounded-full h-4">
<div class="bg-red-accent h-4 rounded-full" style="width: ${ val } %"></div>
</div>
<p class="text-right mt-2 text-gray-400"> ${ val } %</p>
</div>` ;
});
} else {
// Switching to circular view - re-animate circles
document . querySelectorAll ( '.skill-circle' ). forEach ( circle => {
const val = parseInt ( circle . getAttribute ( 'data-value' ));
const dash = 314 - ( 314 * val / 100 );
circle . style . setProperty ( '--dash-offset' , dash );
});
}
});
The bar view is generated dynamically by reading data from the circular view, ensuring both views always stay in sync.
Adding a New Skill
Calculate Dash Offset
For a new skill at 75%: dash-offset = 314 - (314 × 0.75) = 314 - 235.5 = 78.5
Add to Circular View
< div class = "skill-item text-center p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< div class = "relative w-32 h-32 mx-auto mb-4" >
< svg width = "120" height = "120" viewBox = "0 0 120 120" class = "mx-auto" >
< circle cx = "60" cy = "60" r = "50" fill = "none" stroke = "#333" stroke-width = "8" />
< circle class = "skill-circle" cx = "60" cy = "60" r = "50"
style = "--dash-offset: 78.5;" data-value = "75" />
< text x = "60" y = "65" text-anchor = "middle" fill = "#fff" font-size = "20" > 75% </ text >
</ svg >
</ div >
< h3 class = "text-xl font-semibold mb-2" > Python </ h3 >
< p class = "text-gray-400" > Backend development and data analysis </ p >
</ div >
Add to Bar View
< div class = "skill-bar-item p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
< h3 class = "text-xl font-semibold mb-2" > Python </ h3 >
< p class = "text-gray-400 mb-4" > Backend development and data analysis </ p >
< div class = "w-full bg-gray-800 rounded-full h-4" >
< div class = "bg-red-accent h-4 rounded-full" style = "width: 75%" ></ div >
</ div >
< p class = "text-right mt-2 text-gray-400" > 75% </ p >
</ div >
Dash Offset Calculator
Use this formula to calculate the correct --dash-offset value for any percentage:
function calculateDashOffset ( percentage ) {
const circumference = 314 ; // 2 × π × radius (2 × 3.14 × 50)
return circumference - ( circumference * percentage / 100 );
}
// Examples:
consoleLog ( calculateDashOffset ( 70 )); // 94.2
consoleLog ( calculateDashOffset ( 80 )); // 62.8
consoleLog ( calculateDashOffset ( 90 )); // 31.4
Customization Examples
Change Circle Color
.skill-circle {
stroke : #3B82F6 ; /* Blue instead of red */
stroke-width : 10 ;
fill : transparent ;
stroke-dasharray : 314 ;
stroke-dashoffset : 314 ;
animation : fillCircle 1.5 s forwards ;
}
Adjust Animation Speed
@keyframes fillCircle {
to {
stroke-dashoffset : var ( --dash-offset );
}
}
.skill-circle {
animation : fillCircle 2.5 s forwards ; /* Slower: 2.5s instead of 1.5s */
}
Change Grid Layout
<!-- Change from 5 columns to 3 columns on large screens -->
< div id = "circularView" class = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8" >
Add Skill Icons
< div class = "skill-item text-center p-6 rounded-lg hover:bg-gray-900 transition cursor-pointer" >
<!-- Add icon before SVG -->
< i class = "fab fa-html5 text-4xl text-red-accent mb-4" ></ i >
< div class = "relative w-32 h-32 mx-auto mb-4" >
<!-- SVG circle -->
</ div >
< h3 class = "text-xl font-semibold mb-2" > HTML5 & CSS3 </ h3 >
< p class = "text-gray-400" > Maquetación web semántica y estilos avanzados </ p >
</ div >
Responsive Grid
The skills grid adapts to screen sizes:
Screen Size Columns Tailwind Class Mobile (< 768px) 1 grid-cols-1Tablet (≥ 768px) 2 md:grid-cols-2Desktop (≥ 1024px) 5 lg:grid-cols-5
Accessibility Enhancements
Recommended : Add ARIA labels to SVG circles for screen readers:< svg aria-label = "JavaScript skill: 80%" width = "120" height = "120" viewBox = "0 0 120 120" >
< circle class = "skill-circle" cx = "60" cy = "60" r = "50" style = "--dash-offset: 62.8;" data-value = "80" />
< text x = "60" y = "65" text-anchor = "middle" fill = "#fff" font-size = "20" > 80% </ text >
</ svg >
Alternative: Animated Bar Charts
You can also animate the bar chart view:
.skill-bar-item .bg-red-accent {
width : 0 !important ; /* Start at 0 */
animation : fillBar 1.5 s forwards ;
}
@keyframes fillBar {
to {
width : var ( --bar-width ) !important ;
}
}
// Set CSS variable when creating bars
barView . innerHTML += `
<div class="w-full bg-gray-800 rounded-full h-4">
<div class="bg-red-accent h-4 rounded-full"
style="--bar-width: ${ val } %; width: 0;"></div>
</div>` ;
Troubleshooting
Ensure the CSS animation is properly defined and the --dash-offset CSS variable is set: .skill-circle {
animation : fillCircle 1.5 s forwards ;
}
Check that the style="--dash-offset: 94.2;" attribute is present on each circle.
Toggle button not working
The bar view is dynamically generated from the circular view. Ensure you’re updating the circular view first, then toggle to bar view to see changes.
Incorrect dash offset calculation
Double-check the formula: const circumference = 2 * Math . PI * 50 ; // Should be ~314
const dashOffset = circumference - ( circumference * percentage / 100 );
CSS Animations Use CSS animations instead of JavaScript for better performance
Reflow Optimization Toggle classes instead of modifying inline styles when possible
Dynamic Generation Bar view HTML is generated on-demand to reduce initial page size
Lazy Animation Consider using IntersectionObserver to animate only when visible