Skip to main content

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>

View Toggle Button

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.5s forwards;
}

@keyframes fillCircle {
  to {
    stroke-dashoffset: var(--dash-offset); /* Animate to percentage */
  }
}
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)

<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>

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

main.js (lines 114-151)
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

1

Calculate Dash Offset

For a new skill at 75%:
dash-offset = 314 - (314 × 0.75) = 314 - 235.5 = 78.5
2

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>
3

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

estilos.css
.skill-circle {
  stroke: #3B82F6; /* Blue instead of red */
  stroke-width: 10;
  fill: transparent;
  stroke-dasharray: 314;
  stroke-dashoffset: 314;
  animation: fillCircle 1.5s forwards;
}

Adjust Animation Speed

estilos.css
@keyframes fillCircle {
  to {
    stroke-dashoffset: var(--dash-offset);
  }
}

.skill-circle {
  animation: fillCircle 2.5s 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 SizeColumnsTailwind Class
Mobile (< 768px)1grid-cols-1
Tablet (≥ 768px)2md:grid-cols-2
Desktop (≥ 1024px)5lg: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.5s 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.5s forwards;
}
Check that the style="--dash-offset: 94.2;" attribute is present on each circle.
Verify that the IDs match:
console.log(document.getElementById('viewToggle')); // Should not be null
console.log(document.getElementById('circularView')); // Should not be null
console.log(document.getElementById('barView')); // Should not be null
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.
Double-check the formula:
const circumference = 2 * Math.PI * 50; // Should be ~314
const dashOffset = circumference - (circumference * percentage / 100);

Performance Considerations

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

Build docs developers (and LLMs) love