Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/iwinser117/react-portafolio/llms.txt

Use this file to discover all available pages before exploring further.

The Habilidades component showcases Iwinser Sanchez’s technology stack through two continuously scrolling logo carousels — one for front-end technologies and one for back-end technologies. Each carousel loops seamlessly by duplicating every logo, so the animation runs indefinitely without a visible reset jump. The section is anchored to #habilidades so the Nav bar’s Skills link scrolls directly to it.

Source File

src/components/Habilidades.jsx

Section Anchor

The skills article uses id="habilidades", which is the target of the #habilidades anchor link in the Nav component:
<article id="habilidades" className="pt-3">
  <h3>{t("skills.title")}</h3>
</article>

Skill Tracks

Front-end Track

Seven unique logos, each duplicated once for a total of 14 slides:
#TechnologyAsset
1Reactreact.svg
2HTMLhtml.svg
3Bootstrapbootstrap.svg
4SAP UI5ui5.svg
5CSScss.svg
6XMLxml.svg
7JSONjson.svg

Back-end Track

Seven unique logos, each duplicated once for a total of 14 slides:
#TechnologyAsset
1JWTwjt.svg
2Express.jsexpress.svg
3MySQLmysql.svg
4MongoDBmongodb.svg
5Node.jsnodejs.svg
6SAPsap.svg
7PostgreSQLpostgresql.svg

Animation — How the Infinite Scroll Works

The seamless loop is achieved entirely in CSS using the slider, slide-track, and slide classes defined in @styles/habilidades.css.
┌─── .slider (overflow: hidden) ───────────────────────────────┐
│  ┌─── .slide-track (animation: scroll 40s linear infinite) ──┤
│  │ [slide][slide][slide]...[7 unique]...[7 duplicates]        │
│  └────────────────────────────────────────────────────────────┘
└───────────────────────────────────────────────────────────────┘

Key CSS Rules

/* Visible window — clips overflowing content */
.slider {
  height: 60px;
  width: 70%;
  margin: 40px auto;
  overflow: hidden;
  position: relative;

  /* The scrolling strip — total width = 150px × 14 slides */
  .slide-track {
    animation: scroll 40s linear infinite;
    display: flex;
    width: calc(150px * 14);
  }

  /* Individual logo slot */
  .slide {
    height: 60px;
    width: 200px;
  }
}

/* The animation scrolls exactly the width of the 7 unique logos */
@keyframes scroll {
  0%   { transform: translateX(0); }
  100% { transform: translateX(calc(-150px * 7)); }
}
When the @keyframes scroll animation reaches 100%, the track has moved left by exactly 150px × 7 (the width of the 7 unique slides). At that point the duplicated slides fill the viewport identically to the starting position, so the browser loops back to 0% invisibly.
On mobile viewports (≤799 px), the .slider width expands to 100% (overriding the default 70%) so the carousel fills the screen edge-to-edge.

JSX Structure

Habilidades.jsx contains import Carousel from "react-bootstrap/Carousel" at the top of the file, but Carousel is not used anywhere in the component’s JSX. The infinite-scroll effect is implemented with a plain CSS animation on .slide-track, not a Bootstrap carousel. The unused import can safely be removed without affecting behaviour.
Both carousels share the same markup pattern. Here is the complete front-end track:
import react    from "../assets/react.svg";
import htmlimg  from "../assets/html.svg";
import bootstrap from "../assets/bootstrap.svg";
import ui5      from "../assets/ui5.svg";
import css      from "../assets/css.svg";
import xml      from "../assets/xml.svg";
import json     from "../assets/json.svg";

<div className="slider">
  <div className="slide-track">
    {/* 7 unique logos */}
    <div className="slide"><img src={react}     height="60" width="150" alt="" /></div>
    <div className="slide"><img src={htmlimg}   height="60" width="150" alt="" /></div>
    <div className="slide"><img src={bootstrap} height="60" width="150" alt="" /></div>
    <div className="slide"><img src={ui5}       height="60" width="150" alt="" /></div>
    <div className="slide"><img src={css}       height="60" width="150" alt="" /></div>
    <div className="slide"><img src={xml}       height="60" width="150" alt="" /></div>
    <div className="slide"><img src={json}      height="60" width="150" alt="" /></div>

    {/* Duplicates for seamless loop */}
    <div className="slide"><img src={react}     height="60" width="150" alt="" /></div>
    <div className="slide"><img src={htmlimg}   height="60" width="150" alt="" /></div>
    <div className="slide"><img src={bootstrap} height="60" width="150" alt="" /></div>
    <div className="slide"><img src={ui5}       height="60" width="150" alt="" /></div>
    <div className="slide"><img src={css}       height="60" width="150" alt="" /></div>
    <div className="slide"><img src={xml}       height="60" width="150" alt="" /></div>
    <div className="slide"><img src={json}      height="60" width="150" alt="" /></div>
  </div>
</div>
And the back-end track:
import wjt        from "../assets/wjt.svg";
import express    from "../assets/express.svg";
import sql        from "../assets/mysql.svg";
import mongodb    from "../assets/mongodb.svg";
import node       from "../assets/nodejs.svg";
import sap        from "../assets/sap.svg";
import postgresql from "../assets/postgresql.svg";

<div className="slider">
  <div className="slide-track">
    {/* 7 unique logos */}
    <div className="slide"><img src={wjt}        height="60" width="150" alt="" /></div>
    <div className="slide"><img src={express}    height="60" width="150" alt="" /></div>
    <div className="slide"><img src={sql}        height="60" width="150" alt="" /></div>
    <div className="slide"><img src={mongodb}    height="60" width="150" alt="" /></div>
    <div className="slide"><img src={node}       height="60" width="150" alt="" /></div>
    <div className="slide"><img src={sap}        height="60" width="150" alt="" /></div>
    <div className="slide"><img src={postgresql} height="60" width="150" alt="" /></div>

    {/* Duplicates for seamless loop */}
    <div className="slide"><img src={wjt}        height="60" width="150" alt="" /></div>
    <div className="slide"><img src={express}    height="60" width="150" alt="" /></div>
    <div className="slide"><img src={sql}        height="60" width="150" alt="" /></div>
    <div className="slide"><img src={mongodb}    height="60" width="150" alt="" /></div>
    <div className="slide"><img src={node}       height="60" width="150" alt="" /></div>
    <div className="slide"><img src={sap}        height="60" width="150" alt="" /></div>
    <div className="slide"><img src={postgresql} height="60" width="150" alt="" /></div>
  </div>
</div>

Section Descriptions — i18n

Section headings and descriptions are rendered via dangerouslySetInnerHTML because the frontendDescription and backendDescription values contain HTML <strong> tags:
<p dangerouslySetInnerHTML={{ __html: t("skills.frontendDescription") }} />
The English values from src/locales/en.json:
{
  "skills": {
    "title": "Skills",
    "frontend": "Front-end",
    "frontendDescription": "Specialized in creating high-performance and scalable user interfaces. My main focus is the <strong>React and Next.js</strong> ecosystem...",
    "backend": "Back-end",
    "backendDescription": "Robust and scalable development with Node.js and Express.js. Design of high-performance RESTful APIs..."
  }
}
dangerouslySetInnerHTML is safe here because the content comes from the controlled en.json / es.json locale files — never from user input or an external API.

CSS — @styles/habilidades.css

Class / RulePurpose
.sliderVisible window; overflow: hidden clips slides outside the frame
.slide-trackFull-width flex strip; animation: scroll 40s linear infinite
.slideIndividual logo slot: 60 × 200 px
@keyframes scrollTranslates the track left by 150px × 7 to create the loop
@media (max-width: 799px)Sets .slider { width: 100% } for mobile

Adding a New Skill

1

Place the asset

Add your SVG or PNG logo to src/assets/. Use a short, lowercase filename — for example tailwind.svg.
2

Import the asset

At the top of Habilidades.jsx, add:
import tailwind from "../assets/tailwind.svg";
3

Add the original slide

Inside the correct .slide-track (front-end or back-end), append a new slide before the duplicate block:
<div className="slide">
  <img src={tailwind} height="60" width="150" alt="" />
</div>
4

Add the duplicate slide

Append the exact same <div className="slide">...</div> a second time, after the last duplicate, so the total count remains N unique + N duplicates:
<div className="slide">
  <img src={tailwind} height="60" width="150" alt="" />
</div>
5

Update the slide-track width

In habilidades.css, update the calc() values to reflect the new total number of slides (N unique logos):
/* If you now have 8 unique logos (16 total slides) */
.slide-track {
  width: calc(150px * 16);
}

@keyframes scroll {
  100% { transform: translateX(calc(-150px * 8)); }
}
All logo images use alt="" (empty alt text) because they are decorative — screen readers will skip them. If a logo is meaningful in context, add a descriptive alt string such as alt="React".

Build docs developers (and LLMs) love