@media (min-width:992px) {
    .footer {
        padding-bottom: 25px
    }
}

/* --- RESPONSIVE: 2 BREAKPOINTS (>=992px escritorio / <=991px apilado) --- */
/* Pedido explícito: consolidar el responsive a solo 2 breakpoints — por
   debajo de 992px, todo debe verse apilado en una sola columna, igual que
   HOY se ve por debajo de 768px (no lado a lado, como se ve hoy entre
   768-991px). Bootstrap activa sus columnas .col-sm-* a partir de 768px
   por su propio CSS (viene del CDN, no se puede cambiar ese breakpoint
   desde aquí) — así que, sin este override, entre 768px y 991px las
   columnas quedarían lado a lado. Se fuerzan de vuelta a 100% de ancho
   (apiladas) en todo ese rango, para que el corte quede único en 992px.
   Corrección de seguimiento (reportado por el usuario): el corte se eligió
   primero en 989/990px, pero el header/#size-panel (ver más abajo) siempre
   estuvieron gateados en el propio 992px de Bootstrap (su breakpoint natural
   sm→md) — eso dejaba un hueco real en 990-991px donde la distribución ya
   se veía "de escritorio" pero el header/panel seguían en modo mobile. Se
   unificó TODO a 991/992 (el breakpoint que Bootstrap ya usa internamente)
   en vez de inventar un tercer valor, así no vuelve a haber huecos.
   Solo se listan las clases que realmente se usan en el sitio (col-sm-4/6/8
   y su offset-2), no las 12 posibles de Bootstrap. */
@media (max-width: 991px) {
    .col-sm-4,
    .col-sm-6,
    .col-sm-8 {
        width: 100%;
        float: none;
    }

    .col-sm-offset-2 {
        margin-left: 0;
    }
}

/* Pedido explícito: el botón "subir arriba" con bordes redondeados (antes
   border-radius:0 de theme.css) y el ícono más visible — a 11px (heredado
   del font-size del botón) el icon-arrow-up quedaba diminuto dentro de un
   botón de 40x40px. */
.back-to-top {
    border-radius: 10px;
}

.back-to-top i {
    font-size: 20px;
}

@media (max-width: 991px) {
    .text-copy {
        font-size: 11px;
        padding-left: 15px
    }

    .promo-block {
        padding-top: 80px !important
    }

    .promo-block-title {
        font-size: 50px !important;
    }

    .promo-block-text {
        font-size: 20px !important
    }
}

/* Pedido explícito: "Ronal Coello" y "Tech Lead & Digital Product Manager"
   más pequeños en menos de 510px (por debajo del tamaño que ya bajan a
   50px/20px en el bloque de arriba, <=991px). .promo-block .title-tag (2
   clases) en vez de .title-tag solo — la regla base más abajo en el
   archivo (línea ~550) también es 1 clase + !important, y con igual
   especificidad gana la que aparece después en el archivo; 2 clases la
   supera sin depender del orden. */
@media (max-width: 510px) {
    .promo-block-title {
        font-size: 36px !important;
    }

    .promo-block-text {
        font-size: 16px !important;
    }

    .promo-block .title-tag {
        font-size: 14px !important;
    }
}

.promo-block-title {

    margin: 0 !important;
}

#experience .container.content-lg{
    margin-top: 60px;
}

/* Pedido explícito: las 4 tarjetas de servicio de #experience quedan a la
   misma altura. El tema original trae un mecanismo jQuery para esto
   (.bg-color-sky-light[data-auto-height] mide la .service[data-height] más
   alta de las 4 y les fija esa altura por JS al cargar) pero se verificó con
   Chrome headless que en la práctica NO está igualando las 4 alturas
   (266/276/286/286px medidos) — probablemente por timing: la medición corre
   en document.ready, antes de que carguen las fuentes web (Hind) y con
   #experience dentro de un contenedor position:fixed/opacity:0 por defecto
   (nuestro mecanismo .section-active), lo que puede alterar cuándo jQuery
   puede medir el alto real. En vez de depurar esa cadena de timing, se
   igualan las alturas con flexbox (robusto, sin depender de mediciones en
   un momento específico): la fila pasa a flex y cada columna estira su
   .service a la altura del más alto automáticamente.
   De paso se encontró y limpió un bug real: la 2ª tarjeta tenía un
   style="visibility:hidden;height:275.797px" pegado en el HTML — residuo de
   una captura de estado de WOW.js guardada por error como marcado estático
   (WOW.js usa ese inline style de forma temporal, antes de animar la
   entrada). Al vivir dentro de un contenedor fixed/opacity controlado por
   nuestro propio JS, el trigger de scroll de WOW.js podía no dispararse
   nunca y esa tarjeta quedaba invisible para siempre — se quitó el
   `style` inline (ver index.html).
   Los bordes redondeados solo en los extremos exteriores (primer item:
   esquinas izquierdas; último: esquinas derechas) hacen que las 4 tarjetas
   adyacentes se lean como un solo panel continuo — solo tiene sentido en
   una fila, que con col-md-3×4 = 12 columnas ocurre únicamente desde el
   breakpoint md (>=992px); por debajo cae a col-sm-6 (2 por fila, 2 filas)
   y no aplica. */
@media (min-width: 992px) {
    /* Pedido/bug relacionado (ver js/main.js, equalizeExperienceCardHeights):
       con display:flex, la tarjeta "Desarrollador Full Stack" (única con
       un <img class="svg-code"> en vez de un <i> de fuente de ícono) se
       quedaba 10-20px más baja que las otras 3 pese a align-items:stretch
       — confirmado con Chrome headless que el bug sigue al CONTENIDO
       (intercambiar el contenido entre tarjetas mueve el problema con él)
       y que forzar la altura a mano SÍ funciona (no hay ningún max-height
       escondido), así que es específicamente el cálculo de stretch de
       flexbox el que no se aplica bien a un item con un elemento con
       relación de aspecto intrínseca adentro — ni align-items:stretch
       explícito, ni align-self:stretch en el item, ni fijar dimensiones
       en el <img> lo resolvían. display:grid (probado como alternativa)
       tampoco sirvió: interactúa mal con el margen negativo de
       .row-space-2 (el truco de gutter de Bootstrap) y hace que un item
       salte a una fila implícita nueva. La igualación de alturas se
       resolvió en JS (medir y aplicar la más alta a las 4, ver
       js/main.js) — el flexbox de abajo queda solo para la distribución
       en columnas, sin depender de su stretch para la altura. */
    #experience .row-space-2 {
        display: flex;
    }

    /* width:25%/flex-shrink:1 pisan a swiper.css: sus tarjetas ahora
       también llevan la clase .swiper-slide (para funcionar como carrusel
       en <=991px, ver js/main.js), y swiper.css trae una regla base
       .swiper-slide{width:100%;flex-shrink:0} sin ningún media query — sin
       este override, en escritorio las 4 tarjetas intentaban ocupar 100%
       del ancho cada una y se desbordaban una tras otra en vez de ocupar
       1/4 de la fila. Este selector (id + 2 clases + combinador) ya le
       gana en especificidad a la regla de swiper.css (una sola clase). */
    #experience .row-space-2 > .col-md-3 {
        display: flex;
        width: 25%;
        flex-shrink: 1;
    }

    #experience .row-space-2 > .col-md-3 .service {
        width: 100%;
    }

    #experience .row-space-2 > .col-md-3:first-child .service {
        border-top-left-radius: 12px;
        border-bottom-left-radius: 12px;
    }

    #experience .row-space-2 > .col-md-3:last-child .service {
        border-top-right-radius: 12px;
        border-bottom-right-radius: 12px;
    }
}

/* Pedido explícito: los paneles blancos de #experience (.service) pasan a
   fondo blanco con 0.5 de transparencia (antes #fff sólido de theme.css).
   La tarjeta celeste ("Aceleración Responsable con IA", .service.bg-color-
   base) no es un panel blanco — su fondo usa !important en theme.css y
   sigue ganando, esta regla no la afecta. */
#experience .service {
    background: rgba(255, 255, 255, 0.5);
}

/* Pedido explícito: el botón "Descarga mi CV" con fondo blanco 0.5 de
   opacidad en su estado normal (antes #f3f4f5 sólido de theme.css).
   Bug encontrado y corregido (reportado por el usuario: "perdió el hover
   celeste que tenía antes"): el selector de abajo tiene un ID (#about),
   que le gana en especificidad a .btn-default-bg:hover de theme.css (2
   clases) aunque ese selector no incluya :hover — un ID siempre le gana a
   cualquier cantidad de clases, así que sin una regla :hover propia, el
   fondo rgba se quedaba puesto incluso en hover. Se agrega la regla :hover
   explícita, con la misma especificidad (id+clase+pseudo-clase), para que
   vuelva a pasar a celeste sólido igual que antes. */
#about .btn-default-bg {
    background: rgba(255, 255, 255, 0.5);
}

#about .btn-default-bg:hover {
    background: #17bed2;
    color: #fff;
}

#work .container.content-lg{
        margin-top: 20px;
        width : calc(100% - 40px);
}

#contact .container.content-lg{
        max-width: 770px;
            padding-top: 140px !important;
}

/* Pedido explícito: logos + contacto + copy (fusionados en #contact) más
   integrados, con menos espacio entre los logos y el contenido/copy — el
   content-lg original de theme.css (100px arriba/abajo) se reduce en todo
   este bloque. */
#contact .content-lg {
    padding-top: 40px;
    padding-bottom: 40px;
}

.promo-block .maw{

    width : 1000px !important;
    max-width: 96%;
}

@media (min-width: 992px) {
   /* Pedido explícito: invertido — antes empujaba la columna de texto hacia
      el centro/la foto (que estaba a la derecha); ahora que la foto pasa a
      la izquierda y el texto a la derecha, el mismo empuje "hacia la foto"
      se logra invirtiendo los márgenes. */
   .promo-block .data-m {
      margin-left: -70px;
      margin-right: 70px;
   }
}

.grecaptcha-badge {
    display: none !important
}

@media (max-width:1070px) {
    .container {
        padding-left: 35px !important;
        padding-right: 35px !important
    }
}

.content.container {
    padding-top: 20px;
    padding-bottom: 20px
}

.navbar-toggle {
    margin-right: -55px !important
}

.swiper-clients-img {
    width: 190px !important;
    max-width: 100%;
}

/* Pedido explícito: los items del carrusel de logos con ancho fijo y
   separación fija — !important porque Swiper (slidesPerView numérico)
   asigna width/marginRight por JS vía inline style; una regla !important
   en el stylesheet gana sobre ese inline style no-important. */
.swiper-clients .swiper-slide {
    width: 150px !important;
    margin-right: 15px !important;
}

@media (max-width: 991px) {
    .item-1 {
        order: 1
    }

    .item-2 {
        order: 0
    }

    .dflex {
        display: flex;
        flex-direction: column
    }

    /* Pedido explícito: la imagen de #about con max-width:380px en
       responsive (en este rango sigue en flujo normal, no fixed — eso
       solo aplica desde 992px, ver más abajo). Centrada porque en mobile
       la columna ocupa el 100% del ancho apilada. */
    #about .item-1 img.full-width {
        max-width: 380px;
        margin: 0 auto;
        display: block;
    }

    .content-lg {
        padding-bottom: 40px !important;
        padding-top: 40px !important;
    }

    #about .content-lg{
        padding-top: 60px !important;
                padding-left: 35px !important;
        padding-right: 35px !important        
    }


    #experience .content-lg{
                padding-left: 15px !important;
        padding-right: 15px !important        
    }

        #work .content-lg{
                padding-left: 0 !important;
        padding-right: 0px !important        
    }

    .content-lg.image {
        padding-bottom: 0 !important
    }
}

/* Pedido explícito: en >=992px, la imagen de #about (about.png) queda
   fixed, centrada verticalmente en la pantalla, manteniéndose a la
   izquierda, VISIBLE DESDE EL INICIO (sin esperar scroll).
   Corrección de enfoque (pedido explícito: "sin moverse y no espere el
   scroll"): el primer intento usaba position:sticky, que solo llega a su
   posición centrada DESPUÉS de scrollear hasta ese punto — antes de eso se
   ve en su posición estática normal (arriba de la columna), lo cual se
   percibe como "se mueve"/"espera el scroll". Se cambió a position:fixed
   real, con opacidad atada a #about.section-active (mismo mecanismo
   binario ya usado para el resto del contenido fijo del sitio —
   hero/#experience/#work/#contact) para que aparezca ya centrada apenas la
   sección se vuelve activa, sin ningún estado intermedio.
   El left se calcula con el ancho real del .container de Bootstrap en cada
   tier (970px en el tier md, 1100px — nuestro propio tope en #about
   .container — en el lg) + 15px de su padding izquierdo, para alinear con
   el borde real de la columna .item-1 sin necesitar JS. Ya no hace falta
   que la fila sea flex (eso solo era necesario para darle "recorrido" a
   sticky). */
@media (min-width: 992px) {
    /* Bug encontrado y corregido (reportado por el usuario: "se volvió
       gigante y ocupa toda la pantalla"): .full-width (theme.css) pone
       width:100% — con position:static/sticky eso se resolvía contra el
       ancho de la columna .item-1, pero con position:fixed el containing
       block pasa a ser el viewport (no hay ningún ancestro con
       transform/filter/perspective que lo cambie), así que 100% se volvía
       100vw. Se fija un ancho explícito en píxeles por tier (ver los 2
       media queries de abajo, mismos breakpoints que el left) en vez de
       depender de un porcentaje. */
    #about .item-1 img.full-width {
        position: fixed;
        top: 50%;
        transform: translateY(-50%);
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.3s ease;
    }

    #about.section-active .item-1 img.full-width {
        opacity: 1;
        transition: opacity 0.3s ease 0.4s;
    }
}

@media (min-width: 992px) and (max-width: 1199px) {
    #about .item-1 img.full-width {
        left: calc((100vw - 970px) / 2 + 15px);
        width: 297px;
    }
}

@media (min-width: 1200px) {
    #about .item-1 img.full-width {
        left: calc((100vw - 1100px) / 2 + 15px);
        width: 337px;
    }
}

@media (max-width: 991px) {
    .iten-contact {
        width: 100% !important;
        padding-bottom: 20px
    }

    .iten-contact h4 {
        margin-bottom: 5px !important
    }
}

/* Pedido explícito: el ícono de contacto (teléfono/email) queda al lado
   del bloque completo (título + dato), no solo del título — .iten-contact
   pasa a fila (ícono | columna con título+dato), y el ícono en gris en vez
   del celeste de marca usado antes. */
.iten-contact {
    display: flex;
    align-items: flex-start;
}

.iten-contact .contact-icon {
    font-size: 20px;
    line-height: 1.4;
    color: #17bed2;
}

.img-cont {
    border: solid 2px #f1f1f1
}

.margin-b-10 {
    margin-bottom: 10px
}

.item-port {
    margin-bottom: 50px
}

h4.mb-5 {
    margin-bottom: 5px !important
}

.psmall {
    font-weight: bold;
    margin-bottom: 15px;
    display: inline-block
}

.grid-3-cols {
    display: grid;
    gap: 1rem;
    grid-template-columns: 1fr;
    align-items: stretch
}

.grid-3-cols:before,
.grid-3-cols:after {
    display: none !important
}

@media (min-width:640px) {
    .grid-3-cols {
        grid-template-columns: repeat(2, 1fr)
    }
}

@media (min-width:1024px) {
    .grid-3-cols {
        grid-template-columns: repeat(3, 1fr)
    }
}

.grid-3-cols>* {
    padding: 0 10px 0 10px;
    height: 100%;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    justify-content: start
}

/* --- REFINAMIENTOS UI --- */

/* Pedido explícito: la parte "clara" de los títulos (la palabra después de
   la primera, ej. "Coello" en "Ronal Coello", "de Mi" en "Acerca de Mi" —
   todas marcadas con <b class="clear-hl"> en el HTML) y los textos largos
   (<p>) de la página se ven un poco más oscuros que el original de
   theme.css. .clear-hl pasa de opacity:.4 a .6; los <p> pasan de #a6a7aa a
   #86878b. Nada más cambia (tamaño, familia, line-height siguen igual). */
.clear-hl {
    opacity: 0.6;
}

p {
    color: #86878b;
}

/* Hero section: Solo centrar el texto verticalmente */
.promo-block {
    padding-top: 100px !important;
    padding-bottom: 0 !important;
}

/* Pedido explícito: la foto del hero con un ancho máximo de 480px. */
.promo-block-img {
    width: 420px;
    min-width: 420px;
}

@media (min-width: 990px) {
    .promo-block .row {
        display: flex;
    }

    /* Pedido explícito: invertir el layout del hero — la foto (2º hijo en
       el HTML) pasa a la izquierda, el contenido (1º hijo) a la derecha.
       Se hace con `order` (flex), sin tocar el HTML — así el orden en el
       documento (y por lo tanto en mobile, donde no aplica este bloque)
       queda intacto. */
    .promo-block .col-sm-6:first-child {
        order: 2;
    }

    .promo-block .image-content {
        order: 1;
    }

    /* Pedido explícito: contenido alineado a la izquierda (antes a la
       derecha, cuando el texto vivía en la columna izquierda pegada a la
       foto que estaba a su derecha) — mismo criterio, ahora espejado: el
       texto vive en la columna derecha y se pega a la izquierda, hacia la
       foto. */
    .promo-block .col-sm-6:first-child {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: flex-start;
        height: 460px;
        text-align: left;
    }

    .promo-block .col-sm-6:first-child .d-flex.no-wrap {
        justify-content: flex-start;
    }

    /* Pedido explícito: dentro de esa fila, los íconos de redes quedan del
       lado de adentro del grupo (el grupo en sí ahora se pega al borde
       IZQUIERDO de la columna, vía justify-content:flex-start de arriba —
       esto solo reordena el contenido interno, sin cambiar cuál borde). */
    .promo-block .col-sm-6:first-child .d-flex.no-wrap .list-inline {
        order: 3;
    }

    .promo-block .col-sm-6:first-child .d-flex.no-wrap .promo-block-text.spacer {
        order: 2;
    }

    .promo-block .col-sm-6:first-child .d-flex.no-wrap .promo-block-text:not(.spacer) {
        order: 1;
    }

    /* Pedido explícito: el borde celeste del subtítulo (antes a la
       derecha) pasa a la izquierda, siguiendo el mismo espejado del resto
       del contenido. */
    .promo-block .title-tag {
        border-right: none;
        border-left: solid #17bed2 4px;
    }

    .promo-block-img-wrap {
        height: 460px;
    }
}

    .promo-block .promo-block-img {
        bottom: 2px;
    }

/* Pedido explícito: cerrar el hueco entre 768px y 991px. theme.css centra
   el hero (text-align:center, imagen relativa y centrada en vez de
   absoluta) solo por debajo de 768px (su propio breakpoint, no tocable
   desde acá); nuestro layout flex/right-align de escritorio (arriba) recién
   arranca en 992px; entre 768 y 991px no aplicaba ninguno de los dos y el
   hero quedaba sin distribución clara. Se replica aquí el mismo centrado
   de theme.css, extendido hasta 991px, para que todo el rango <992px se
   vea igual. */
@media (max-width: 990px) {
    .promo-block {
        padding-top: 80px !important;
        text-align: center;
    }

    .promo-block>.container{
        margin-top: 10px !important;
    }

    .promo-block .promo-block-img-wrap {
        height: auto;
        width: calc(100% - 30px);
        bottom: -25px;
        position: absolute;
    }

    .promo-block .row {
        height: 100%;
        position: relative;
    }

    .promo-block .image-content{
        position: absolute !important;
        bottom: 0;
    }

    /* Selector con la misma especificidad que theme.css (.promo-block
       .promo-block-img, 2 clases) — con solo .promo-block-img (1 clase)
       la regla de theme.css seguía ganando y la imagen quedaba
       position:absolute en este rango. */
    .promo-block .promo-block-img {
        position: relative;
        margin-left: -100px;
        margin-right: -100px;
        display: inline-block;
    }

    /* Fix reportado por el usuario: la fila de íconos de redes (.d-flex.
       no-wrap, display:flex por theme.css) no se centraba con el resto del
       texto — text-align:center (arriba) solo centra contenido inline,
       no el layout interno de un contenedor flex; hace falta
       justify-content:center explícito.
       Pedido explícito de seguimiento: "@roncreative" debe salir debajo de
       los íconos (no al lado) en este rango — flex-direction:column apila
       en el orden real del HTML (íconos, luego el separador "/", luego el
       texto). El separador "/" solo tiene sentido como espaciador en línea
       (icono / texto); apilado se vería como un "/" suelto en su propia
       fila, así que se oculta acá y en su lugar el texto lleva un
       margin-top para separarse de los íconos. */
    .promo-block .col-sm-6:first-child .d-flex.no-wrap {
        justify-content: center;
        flex-direction: column;
        align-items: center;
    }

    .promo-block .col-sm-6:first-child .d-flex.no-wrap .promo-block-text.spacer {
        display: none;
    }

    .promo-block .col-sm-6:first-child .d-flex.no-wrap .promo-block-text:not(.spacer) {
        margin-top: 10px;
    }

    
}



.size-panel{
    z-index: -1;
    border-radius: 16px;
    transition: border-radius 0.3s ease;
}

/* Cuando data-h de la sección activa pide más alto que la pantalla y
   max-height:100vh recorta el panel (ver "PANEL DE TAMAÑO" más abajo), los
   bordes redondeados se ven raros contra ese recorte recto — .panel-capped
   (togglada por js/main.js comparando data-h contra window.innerHeight)
   los deja rectos mientras el panel esté en ese estado. */
.size-panel.panel-capped {
    border-radius: 0;
}

/* data-wf en una sección (ver HTML) fuerza el panel a 100vw con bordes
   rectos mientras esa sección esté activa — independiente del ancho pedido
   por data-w. js/main.js togglea esta clase leyendo el atributo de la
   sección "best" (mismo patrón que .panel-capped/.panel-gray). */
.size-panel.panel-full {
    width: 100vw;
    max-width: 100vw;
    border-radius: 0;
}



/* Sin fondo propio (pedido explícito): el header/nav vive fuera de
   #size-panel (ver HTML) como capa independiente. El blanco lo aporta
   únicamente #size-panel, que está detrás y sincronizado en tamaño/posición
   — el nav nunca pinta su propio fondo encima del contenido. */
.header .wrap .navbar,
.header {
    background: transparent !important;
    background-color: transparent !important;
    padding-top: 0 !important;
    top: 0;
}
#body.page-on-scroll{
    position: relative;
}

.header .wrap .navbar{
    background-color: #fff;
    z-index: 10;
}

.header.navbar-fixed-top.top-nav-collapse .wrap {
    max-height: 100%;
    border-radius: 16px 16px 0 0;
    width: calc(100% - 4px);
    transform: translateX(2px);
}

/* Pedido explícito: el backdrop-filter:blur del wrap del header solo se
   aplica si la sección activa tiene el atributo data-hb (mismo patrón que
   data-w/data-h/data-c/data-wf) — js/main.js togglea .header-hb leyendo ese
   atributo de la sección "best".
   Bug encontrado y corregido (reportado por el usuario: "en responsive
   990px el wrap nunca toma el blur"): esta regla dependía únicamente de
   `.top-nav-collapse`, una clase de Layout() (js/main.js original del
   tema) que se agrega según `$(".navbar").offset().top>150` — un cálculo
   de jQuery sobre un elemento position:fixed que no se comporta igual en
   todos los anchos (en el bloque <=991px el navbar tiene top:0 en vez de
   los 25px de escritorio, cambiando cuándo cruza el umbral de 150px), así
   que en la práctica no disparaba de forma confiable en ese rango. Se
   agrega `#body.page-on-scroll` como disparador alternativo — el mismo
   mecanismo, ya probado y usado por theme.css para poner el navbar blanco
   al hacer scroll (`.page-on-scroll .header .navbar{background:#fff}`),
   basado solo en `scrollTop()>60` sin depender del ancho de pantalla. */
.header.navbar-fixed-top.top-nav-collapse.header-hb .wrap,
#body.page-on-scroll .header.header-hb .wrap {
    backdrop-filter: blur(20px);
}


.header.navbar-fixed-top .wrap .navbar {
    border-bottom: 0;
}

/* --- GEMA 3D DEL NAVBAR (Three.js, ver js/main.js) --- */
/* Pedido explícito: reemplazar el diamante de CSS puro por un objeto 3D
   real (gema) renderizado con Three.js. Este div solo posiciona el
   <canvas> que Three.js monta adentro — la geometría/material/luces/loop
   de render viven en js/main.js. position:absolute sobre .navbar (que pasa
   a position:relative para servir de referencia) para no alterar el
   layout existente del toggle/nav-links.
   Pedido explícito (ronda siguiente): hover con tooltip "Tema Oscuro" — la
   gema pasa a ser interactiva (pointer-events:auto, ya no aria-hidden en
   el HTML) para poder recibir :hover; sin esto, pointer-events:none hace
   que el navegador ignore la gema por completo para hit-testing y :hover
   nunca llegaría a activarse. El tooltip es CSS puro (::after con
   content:attr(data-tooltip), sin JS ni librería de tooltips) para no
   sumar dependencias por un texto fijo. */
.header .wrap .navbar {
    position: relative;
}

.navbar-diamond {
    position: absolute;
    top: 50%;
    left: 20px;
    width: 30px;
    height: 30px;
    transform: translateY(-50%);
    pointer-events: auto;
    cursor: pointer;
    margin-left: -30px;
    transition: transform 0.2s ease;
}

.navbar-diamond:hover {
    transform: translateY(-50%) scale(1.2);
}

.navbar-diamond canvas {
    display: block;
}

/* Glow gris "que respira" detrás de la gema: radial-gradient difuminado
   con blur, en pulso continuo de opacidad/escala vía @keyframes. z-index
   -1 la manda detrás del <canvas> — ambos son hijos directos del mismo
   .navbar-diamond (positioned), así que no aplica la limitación ya
   documentada de "un hijo con z-index negativo no pinta detrás del fondo
   opaco de su propio padre" (.navbar-diamond no tiene fondo propio). */
.navbar-diamond::before {
    content: "";
    position: absolute;
    inset: -8px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(81, 87, 105, 0.55), transparent 70%);
    filter: blur(3px);
    z-index: -1;
    pointer-events: none;
    animation: navbar-gem-breathe 2.8s ease-in-out infinite;
}

@keyframes navbar-gem-breathe {
    0%, 100% {
        opacity: 0.35;
        transform: scale(0.85);
    }
    50% {
        opacity: 1;
        transform: scale(1.1);
    }
}

/* Tooltip "Tema Oscuro", solo visible en hover. */
/* Pedido explícito: tooltip más chico y a la derecha del ícono (antes
   debajo, con texto más grande). */
.navbar-diamond::after {
    content: attr(data-tooltip);
    position: absolute;
    top: 50%;
    left: 100%;
    margin-left: 10px;
    transform: translateY(-50%);
    background: rgba(0, 0, 0, 0.8);
    color: #fff;
    font-size: 9px;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    padding: 4px 7px 2px 7px;
    border-radius: 3px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

.navbar-diamond:hover::after {
    opacity: 1;
}

/* Detalle azul para el cargo */
.title-tag {
    background-color: rgba(255, 255, 255, 0.5);
    color: #777777;
    padding: 0 10px;
    font-size: 0.8em;
    font-weight: 600;
    display: inline-block;
    letter-spacing: 1px;
    text-transform: uppercase;
    padding-top: 2px !important;
    font-weight: 500;
    font-size: 18px !important;
    border-right: solid #17bed2 4px;
}

/* Pedido explícito: los íconos de redes sociales del hero pasan de fondo
   blanco sólido (theme.css: .social-icons{background:#fff}) a blanco con
   0.5 de transparencia, igual que .title-tag arriba. */
.social-icons {
    background-color: rgba(255, 255, 255, 0.5);
}

/* Bug encontrado y corregido (reportado por el usuario: "en modo oscuro
   el ícono de TikTok no se pone blanco"): a diferencia de los demás
   íconos sociales (fuentes de ícono, heredan color vía .social-icons
   {color:...}), el de TikTok es un <svg> con fill="#000000" fijo en el
   HTML — una propiedad CSS, aunque no tenga !important, le gana a un
   atributo de presentación SVG, así que basta con fill:currentColor para
   que siga el mismo color que el resto (tanto en tema claro como oscuro),
   en vez de quedar siempre negro. */
.social-icons.tik svg {
    fill: currentColor;
}

/* Panel de resaltado en Acerca de Mi */
/* Pedido explícito: fondo blanco con 0.5 de transparencia (antes
   #f9f9f9 sólido). */
.highlight-panel {
    background-color: rgba(255, 255, 255, 0.5);
    padding: 15px;
    border-left: 5px solid #17bed2;
    margin-top: 10px;
    color: #727272;
    line-height: 1.6;
}

.main{
    position: relative;
    z-index: 1;
}

/* --- UNA SECCIÓN A LA VEZ (scroll-snap) --- */
/* Cada sección es su propio "slide": alto mínimo de pantalla completa,
   contenido centrado verticalmente dentro. Si el contenido ya es más alto
   que la pantalla, min-height no lo achica — sigue su alto natural.
   scroll-snap hace que al soltar el scroll quede encuadrada una sola
   sección, nunca dos a medias. El header es el original (fixed, transparente
   sobre el hero, blanco al hacer scroll) — sin tamaño ni posición dinámicos.
   Ninguna sección tiene fondo propio: todas transparentes, se ve el blanco
   del body detrás (el color de elementos internos como el acento celeste o
   la franja sky-light sigue intacto, viven en sus propios divs anidados).
   Aparición/desaparición: NO es un fade proporcional al scroll — es binaria.
   Cada sección arranca OCULTA (opacity:0) por defecto; js/main.js le agrega
   la clase .section-active solo a la sección dominante (el mismo "best" que
   decide el tamaño del panel). Importante: el default es oculto (no
   visible) para que, en el instante inicial antes de que el observer corra
   por primera vez, no se vean varias secciones superpuestas a la vez —
   sobre todo ahora que el hero y #experience tienen contenido position:fixed
   (ver más abajo): si el default fuera "visible" ambos se mostrarían uno
   encima del otro hasta que JS corrigiera el estado. Con "oculto por
   defecto" el peor caso es un parpadeo en blanco de unos milisegundos, no
   contenido superpuesto. La transición de 0.3s es un fundido de duración
   fija al cambiar de estado, no algo que avance junto con el scroll. El
   fade-IN tiene un delay de 0.4s (mismo tiempo que tarda #size-panel en
   redimensionar, ver "PANEL DE TAMAÑO" más abajo) para que el contenido no
   aparezca mientras el panel todavía se está acomodando; el fade-OUT no
   tiene delay, se oculta enseguida al dejar de ser la sección activa. */
/* Pedido explícito: "una sección a la vez" (scroll-snap + fade binario)
   ya no queda solo para escritorio — se quitó el @media(min-width:992px)
   que lo limitaba, así que aplica en todas las resoluciones. El header
   sigue siendo el de mobile (navbar-fixed-top + hamburguesa, sin
   #size-panel) por decisión explícita — ver el bloque "PANEL DE TAMAÑO"
   más abajo, que sigue gateado a >=992px. */
html {
    scroll-snap-type: y mandatory;
}

.promo-block,
#about,
#experience,
#work,
#contact {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    scroll-snap-align: start;
    background: transparent;
    opacity: 0;
    transition: opacity 0.3s ease;
}

.promo-block.section-active,
#about.section-active,
#experience.section-active,
#work.section-active,
#contact.section-active {
    opacity: 1;
    transition: opacity 0.3s ease 0.4s;
}


/* --- HERO, EXPERIENCE, WORK Y CONTACT: contenido interno fijo (no reacciona al scroll) --- */
/* Pedido explícito: el contenido del hero (.promo-block > .container),
   #experience (#experience > .bg-color-sky-light), #work
   (#work > .container.content-lg) y, ahora también, #contact
   (#contact > .contact-fixed-wrap) no deben moverse con el scroll. Se sacan
   del flujo con position:fixed, centrados en el viewport, y usan la misma
   clase .section-active de arriba (default oculto, binaria, no
   proporcional al scroll, con el mismo delay de 0.4s al aparecer) para
   aparecer/desaparecer. Las secciones en sí siguen en flujo normal
   (min-height:100vh, scroll-snap-align) solo para que el scroll-snap y el
   observer sigan funcionando.
   #contact necesitó un wrapper nuevo (.contact-fixed-wrap, ver HTML) porque
   a diferencia de #experience/#work (un solo hijo directo), #contact tenía
   3 hijos directos (logos, contacto, footer) — no se puede centrar cada uno
   por separado con translate(-50%,-50%) sin que los 3 queden superpuestos
   exactamente en el mismo punto; hacía falta un único contenedor envolvente
   para aplicar el mismo patrón de fixed+fade que las demás secciones.
   Nota: los botones prev/next del riel de #work (ver HTML) se sacaron a
   propósito de este contenedor — un ancestro con transform (como este,
   translate(-50%,-50%)) crea un containing block nuevo para descendientes
   position:fixed, y esos botones necesitan seguir fijos al viewport real. */
/* Pedido explícito: este bloque (contenido fijo + fade + slide del hero)
   ya no se limita a >=992px — se quitó el @media que lo envolvía para que
   el mismo comportamiento aplique en todas las resoluciones.
   Bug real encontrado al extenderlo a mobile: sin un ancho explícito, un
   elemento position:fixed no se comporta como bloque normal (que ocupa el
   ancho disponible) — se encoge a su contenido ("shrink-to-fit"). En
   desktop esto quedaba oculto porque el Bootstrap .container de adentro
   siempre tiene un ancho fijo propio (750/970/1170px según el tier), así
   que el shrink-to-fit del wrapper terminaba coincidiendo con ese ancho.
   Pero el .container de Bootstrap NO tiene ancho fijo por debajo de 768px
   (fluido, depende del padre) — con el padre también en shrink-to-fit, los
   dos "auto" se retroalimentan y todo colapsaba a un ancho mínimo
   (columnas de ~200px en vez de ocupar la pantalla). width:calc(100%-40px)
   en el propio elemento fixed resuelve el porcentaje contra el viewport
   (que es su containing block, al ser fixed), no contra un ancestro —
   corta ese círculo. #work ya tenía este ancho en su .container.content-lg
   propio; se agrega igual a los otros 3 para que quede consistente. */
.promo-block>.container,
#experience>.bg-color-sky-light,
#work>.container.content-lg,
#contact>.contact-fixed-wrap {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: calc(100% - 40px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.3s ease;
}

#about .container{
    max-width: 1100px;
}

.promo-block>.container {
    margin-top: 50px;
}

.promo-block.section-active>.container,
#experience.section-active>.bg-color-sky-light,
#work.section-active>.container.content-lg,
#contact.section-active>.contact-fixed-wrap {
    opacity: 1;
    pointer-events: auto;
    transition: opacity 0.3s ease 0.4s;
}

/* Hero: además del fade, la imagen entra de derecha a izquierda y el
   texto de izquierda a derecha (pedido explícito). */
.promo-block .col-sm-6:first-child,
.promo-block .col-sm-6:last-child {
    transform: translateX(0);
    transition: transform 0.3s ease;
}

.promo-block:not(.section-active) .col-sm-6:first-child {
    transform: translateX(-50px);
}

.promo-block:not(.section-active) .col-sm-6:last-child {
    transform: translateX(50px);
}

.promo-block.section-active .col-sm-6:first-child,
.promo-block.section-active .col-sm-6:last-child {
    transition: transform 0.5s ease 0.4s;
}

/* --- SIN FONDOS --- */
/* Pedido explícito: ni el body ni ninguna sección (ni las internas, como la
   franja sky-light de Experiencia/Clientes) llevan color de fondo propio.
   Todo queda transparente de punta a punta. */
@media (min-width: 992px) {
    /* Barras superior/inferior fijas de theme.css (body::before/after). */
    body::before,
    body::after {
        display: none;
    }
}

body {
    background: transparent;
    /* Marco lateral gris de theme.css (border-left/right:25px #e0e0e0,
       dentro de @media min-width:992px ahí) eliminado en todas las
       resoluciones — pedido explícito, regla sin media query para que no
       dependa del breakpoint original. */
    border-left: none !important;
    border-right: none !important;
}

html {
    background: #FFFFFF;
}

.bg-color-sky-light {
    background: transparent !important;
}

/* --- FONDO ANIMADO (VANTA.WAVES) --- */
/* Pedido explícito del usuario: reemplaza cualquier fondo decorativo
   anterior (ondas SVG, esferas, franjas diagonales — todos eliminados).
   #vanta-bg es el contenedor donde VANTA.WAVES (js/main.js) monta su
   <canvas> vía Three.js. Hermano de #size-panel, mismo z-index:-3 que
   usaban las capas de fondo anteriores. */
.vanta-bg {
    position: fixed;
    inset: 0;
    z-index: -3;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* --- PANEL DE TAMAÑO (fixed, blanco, centrado; solo desktop) --- */
/* #size-panel es SOLO el fondo: un div vacío en position:fixed, fondo
   blanco, centrado. El <header> (con el nav adentro) ya NO vive dentro de
   este div — es un hermano, capa independiente, para que el nav nunca
   comparta stacking context con el panel ni su color de fondo (pedido
   explícito: "wrap navbar debe quedar fuera del panel blanco... su
   z-index debe estar por encima del contenido sin taparlo con
   background"). Ambos, panel y header, se dimensionan/posicionan con las
   MISMAS variables (--panel-w/--panel-h) y la misma transición, así que
   escalan idénticos y siempre quedan alineados — el panel aporta el
   blanco por detrás, el header (transparente) va encima con un z-index
   mayor al del contenido (.main tiene z-index:1).
   --panel-w/--panel-h las actualiza js/main.js leyendo data-w/data-h de la
   sección activa. Para ajustar el tamaño de una sección a mano: editar sus
   atributos data-w="NNN" y data-h="NNN" en el HTML.
   Tope: si un valor de data-w/data-h pedido excede el tamaño de pantalla,
   max-width/max-height lo limitan a 98vw/98vh en vez de desbordar.
   Solo aplica desde 992px: con el ancho de escritorio (1450px) centrado,
   en mobile el header vuelve a su comportamiento original (navbar-fixed-top
   de Bootstrap, ancho completo), porque el ancho de escritorio centrado
   empujaba el botón de menú fuera de la pantalla. */
:root {
    --panel-w: 1450px;
    --panel-h: 90px;
}
.size-panel {
        background: rgba(255,255,255, 0.2);
            border : solid 2px rgba(255,255,255, 0.4);
                    backdrop-filter: blur(4px);
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.1);
}
#vanta-bg{
    opacity: 0.5;
}
@media (min-width: 992px) {
    /* Pedido explícito: se quitó el glow celeste detrás del panel
       (.panel-glow/#panel-glow, ver historial en done.md) y se reemplazó
       por un box-shadow suave directamente en #size-panel — mismo efecto de
       "elevar" el panel sobre el fondo, sin el elemento hermano adicional
       ni el blur(35px) de aquel glow. */
    .size-panel {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: var(--panel-w);
        height: var(--panel-h);
        max-width: 98vw;
        max-height: 100vh;
        transition: width 0.4s ease, height 0.4s ease, background-color 0.3s ease;
    }

    /* data-c en una sección (ver HTML) marca que, mientras esa sección esté
       activa, el panel se pone gris #fafafa en vez de blanco. js/main.js
       togglea esta clase leyendo el atributo de la sección "best". No se
       agregó data-c a ninguna sección todavía — queda disponible para que
       se marquen las que correspondan. */
    .size-panel.panel-gray {
        background-color: #fafafa;
    }

    /* El <header> se dimensiona igual que #size-panel (mismo --panel-w/-h),
       pero esa caja casi siempre es más grande que la barra de nav visible
       (.wrap/.navbar, que tiene su propio ancho/alto de theme.css) — el
       resto del rectángulo es invisible y no debe bloquear clicks al
       contenido de abajo. pointer-events:none en el header + auto solo en
       .wrap deja clickeable únicamente la barra de nav real. */
    .header.navbar-fixed-top {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: var(--panel-w);
        height: var(--panel-h);
        max-width: 98vw;
        max-height: 100vh;
        z-index: 5;
        transition: width 0.4s ease, height 0.4s ease;
        pointer-events: none;
    }

    .header.navbar-fixed-top .wrap {
        pointer-events: auto;
    }
}

/* Pedido explícito: #size-panel también aparece en responsive (<=991px) —
   antes solo vivía en el bloque >=992px de arriba. En vez de reusar
   --panel-w (pensado para anchos de escritorio como 1450px, no cabe en una
   pantalla chica), acá el ancho queda fijo en 95%. La altura usa una
   variable nueva, --panel-hr, que js/main.js actualiza leyendo el atributo
   data-hr de la sección activa (mismo patrón que data-w/data-h/data-c/
   data-wf/data-hb). Sin data-hr en ninguna sección todavía, cae en
   var(--panel-h) — el mismo alto que ya trae data-h para escritorio de esa
   sección — en vez de un número fijo arbitrario, para que el panel no se
   vea con un tamaño desconectado del contenido real mientras no se defina
   data-hr específico. max-height:100vh asegura que, si data-hr (o su
   respaldo --panel-h) pide más alto que la pantalla, quede topado ahí en
   vez de desbordar (mismo criterio que max-height:100vh en desktop).
   El <header> NO se toca acá — sigue siendo el navbar-fixed-top + hamburguesa
   de Bootstrap, decisión explícita de la tarea de consolidación anterior. */
@media (max-width: 990px) {
    .size-panel {
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        width: 95%;
        height: var(--panel-hr, var(--panel-h));
        max-height: 100vh;
        background: rgba(255, 255, 255, 0);
        border: solid 2px rgba(255, 255, 255, 0.3);
        backdrop-filter: blur(8px);
        box-shadow: 0 20px 60px rgba(0, 0, 0, 0.12);
        transition: height 0.4s ease;
    }
    .promo-block .container{
       height: 500px;
    }
    .promo-block-img-wrap{
        position: absolute !important;
    }
    .header.navbar-fixed-top.top-nav-collapse .wrap{
        border-radius: 0 !important;
    }

}
@media (max-width: 520px) {
    #contact .container.content-lg{
        padding-top: 10px !important;
        padding-left: 20px !important;
        padding-right: 20px !important;
        padding-bottom: 0px !important;

    }

    h4{
        font-size: 16px;
    }

}

/* --- RIEL DE PORTAFOLIO (Swiper, "Últimos Trabajos") --- */
/* Antes era una CSS grid de 3 columnas fija. Ahora es un riel (Swiper,
   igual que .swiper-clients/.swiper-testimonials, ya cargado en el
   proyecto): 3 tarjetas visibles, se mueven de a 1 (slidesPerView:3,
   slidesPerGroup:1, ver js/main.js). Nota: la versión anterior reordenaba
   visualmente las tarjetas con CSS `order` (más nuevas primero); Swiper
   no respeta `order`, así que el orden visual pasó a ser el orden real del
   HTML (MyKlick primero ... Velocraze último). */
.swiper-work {
    padding: 0 45px;
}

/* Pedido explícito: los items del carrusel (y su panel blanco, que es el
   mismo elemento — .item-port ya tiene el fondo #fff) a la misma altura,
   con border-radius:10px.
   .swiper-wrapper ya es flex (swiper.css); por defecto eso ya estiraría
   cada item a la altura del más alto (align-items:stretch es el valor
   inicial). El problema real: swiper.css fija .swiper-slide{height:100%},
   y como el alto de .swiper-work/.swiper-wrapper también depende del
   contenido (no hay un alto fijo explícito en ningún ancestro), ese 100%
   cae en el caso de "porcentaje contra un alto que a su vez depende del
   contenido" — la spec de CSS lo resuelve como si fuera "auto" (no
   estira), que es justo lo que se veía (alturas de 410 a 452px, cada una
   según su propio contenido). Forzar height:auto en el item (más
   específico que .swiper-slide, y scoped solo a .swiper-work para no
   afectar los otros carruseles del sitio) saca a los items de ese cálculo
   de porcentaje y deja que el propio algoritmo de flexbox los estire
   parejo, que sí funciona sin la restricción de "alto definido". */
/* Ancho REAL del slide (no un max-width que recorte el ancho inline que
   asigna Swiper): con slidesPerView numérico, Swiper calculaba el paso del
   riel con SU ancho (contenedor/3 = 465px) mientras el slide medía 320px
   por este tope — 145px de desfase por slide, acumulativo, hasta que el
   riel se quedaba visualmente sin items a la derecha antes de dar la
   vuelta. Ahora el carrusel usa slidesPerView:"auto" (ver js/main.js), que
   mide el ancho real de cada slide, así que este ancho es la única fuente
   de verdad y el loop vuelve a ser continuo. */
.swiper-work .item-port {
    margin-bottom: 0;
    width: 320px;
    max-width: 100%;
    background: rgba(255, 255, 255, 0.5);
    padding: 20px;
    border-radius: 10px;
    box-sizing: border-box;
    height: auto;
    align-self: stretch;
}

/* Pedido explícito: la imagen queda sin padding (a sangre en los bordes
   del panel blanco de arriba) — el wrapper de la imagen cancela el padding
   del panel con un margen negativo, dejando 20px antes del título. */
.swiper-work .item-port > .margin-b-20 {
    margin: -20px -20px 20px -20px;
}

.swiper-work .item-port .img-cont {
    border: none;
}

/* Pedido explícito: las 2 puntas superiores de la imagen redondeadas a
   10px (mismo radio que el panel), ya que la imagen llega a sangre hasta
   el borde superior del panel — sin esto, sus esquinas cuadradas
   sobresalían contra el borde redondeado del panel. Las esquinas
   inferiores de la imagen no tocan ningún borde del panel (hay 20px de
   margen antes del título), así que quedan como estaban. */
.swiper-work .item-port .img-cont img {
    border-top-left-radius: 10px;
    border-top-right-radius: 10px;
}

@media (max-width: 991px) {
    .swiper-work {
        padding: 0 35px;
    }
}


/* Pedido explícito: #experience como carrusel (como el de #work) en
   <=991px, sin botones next/prev (se quitaron del HTML) — avanza solo,
   por autoplay o gestos táctiles (swipe), ver js/main.js. */
@media (max-width: 991px) {
    .swiper-experience {
        padding: 0 45px;
    }

    /* Pedido explícito: los items del carrusel con ancho fijo de 300px
       (antes 100% del contenedor vía el override general de .col-sm-6).
       !important porque Swiper (slidesPerView numérico) asigna su propio
       width por JS vía inline style — mismo patrón ya usado para
       .swiper-clients/.swiper-work. */
    .swiper-experience .col-md-3 {
        width: 300px !important;
        margin-left: auto;
        margin-right: auto;
    }
}

/* Pedido explícito: en menos de 430px, el riel de cada carrusel debe
   mostrar exactamente 1 item completo, sin que se corte, y centrado. Con
   el ancho fijo de arriba (min-width:300px en #work, 300px en
   #experience) + el padding lateral del riel (35px/45px), a partir de
   cierto viewport chico (ej. 320-380px) el item ya no entraba completo y
   quedaba recortado. Acá el ancho pasa a 100% del riel (que ya tiene
   menos padding lateral) en vez de un valor fijo, así el item siempre
   ocupa exactamente el espacio disponible — nunca se desborda ni se
   corta, y al ocupar todo el ancho disponible queda centrado por
   definición (no sobra espacio de ningún lado). */
@media (max-width: 430px) {
    .swiper-work,
    .swiper-experience {
        padding: 0 15px;
    }

    .swiper-work .item-port {
        max-width: 100%;
        width: 100%;
    }

    .swiper-experience .col-md-3 {
        width: 100% !important;
    }
}

/* ======================================================================
   TEMA OSCURO (toggle: click en la gema del navbar, ver js/main.js)
   ======================================================================
   Pedido explícito: un tema oscuro para todo el sitio, conservando el
   tema claro actual — no se reemplaza nada de lo de arriba, se agrega una
   capa de overrides bajo html.theme-dark que js/main.js togglea al hacer
   click en #navbar-gem (document.documentElement.classList.toggle
   ('theme-dark')). La clase vive en <html> (no en <body>) porque el fondo
   de página (html{background:#FFFFFF} más arriba) también necesita
   cambiar, y <html> es ancestro de <body>, así que un selector
   html.theme-dark alcanza a ambos sin duplicar el toggle en dos nodos.

   Fondo #16171d (pedido explícito) — el mismo tono se usa como color de
   VANTA.WAVES en oscuro (js/main.js cambia la opción `color` del efecto
   con `.setOptions()` al togglear, no hace falta reinicializarlo).

   Títulos claros con el efecto "primera palabra más clara" (pedido
   explícito) — no hace falta ningún mecanismo nuevo: los títulos ya usan
   <b class="clear-hl"> en la segunda parte (ej. "Ronal <b
   class="clear-hl">Coello</b>") con opacity:.6 (ver más arriba). Al pasar
   el color base de los títulos a uno claro, esa misma opacity ya hace que
   la palabra con clear-hl se vea más apagada/oscura contra el fondo
   oscuro, mientras la primera palabra (opacity:1) se ve más clara/brillante
   — es el resultado natural de mantener el mismo mecanismo, no uno nuevo. */
html.theme-dark {
    background: #16171d;
}

html.theme-dark body {
    color: #e4e5e8;
}

/* --- Tipografía --- */
html.theme-dark h1,
html.theme-dark h2,
html.theme-dark h3,
html.theme-dark h4,
html.theme-dark h5,
html.theme-dark h6 {
    color: #f5f6f8;
}

html.theme-dark h1 > a,
html.theme-dark h2 > a,
html.theme-dark h3 > a,
html.theme-dark h4 > a,
html.theme-dark h5 > a,
html.theme-dark h6 > a {
    color: #f5f6f8;
}

html.theme-dark p,
html.theme-dark .promo-block-text,
html.theme-dark .text-copy {
    color: #e4e5e8;
}

html.theme-dark em,
html.theme-dark li,
html.theme-dark li a,
html.theme-dark span {
    color: #d5d7dc;
}

html.theme-dark a {
    color: #c7c9d1;
}

html.theme-dark a:hover,
html.theme-dark a:focus,
html.theme-dark a:active {
    color: #17bed2;
}

html.theme-dark .color-heading {
    color: #f5f6f8 !important;
}

html.theme-dark .color-subtitle {
    color: #9a9dab !important;
}

html.theme-dark .color-link {
    color: #c7c9d1 !important;
}

/* --- Header / nav / panel --- */
html.theme-dark .header .nav-item-child {
    color: #e8e9ec;
}

html.theme-dark .header .navbar-toggle .toggle-icon,
html.theme-dark .header .navbar-toggle .toggle-icon:before,
html.theme-dark .header .navbar-toggle .toggle-icon:after {
    background: #e8e9ec;
}

html.theme-dark .header .wrap .navbar,
html.theme-dark .page-on-scroll .header .navbar {
    background-color: #16171d;
}

html.theme-dark .size-panel {
    background: rgba(255, 255, 255, 0.03);
    border-color: rgba(255, 255, 255, 0.04);
}

/* --- Tarjetas / paneles translúcidos (antes blanco sobre fondo claro) --- */
html.theme-dark .service,
html.theme-dark #experience .service,
html.theme-dark .swiper-work .item-port {
    background: rgba(255, 255, 255, 0.06);
}

html.theme-dark .title-tag {
    background-color: rgba(255, 255, 255, 0.08);
    color: #e8e9ec;
}

html.theme-dark .social-icons {
    background-color: rgba(255, 255, 255, 0.08);
    color: #e8e9ec;
}

html.theme-dark .highlight-panel {
    background-color: rgba(255, 255, 255, 0.06);
    color: #d5d7dc;
}

html.theme-dark .img-cont {
    border-color: rgba(255, 255, 255, 0.15);
}

html.theme-dark .back-to-top {
    background: #20222b;
    border-color: rgba(255, 255, 255, 0.15);
}

html.theme-dark #about .btn-default-bg {
    background: rgba(255, 255, 255, 0.08);
    color: #e8e9ec;
}

html.theme-dark .progress-box .progress {
    background: rgba(255, 255, 255, 0.08);
}

/* Bug encontrado y corregido (reportado por el usuario: "en experience,
   Desarrollador Full Stack el icono no se coloca claro"): a diferencia de
   los otros 3 íconos de servicio (<i class="service-icon">, fuente de
   ícono con color vía CSS), este es un <img src="...svg">, una imagen
   externa — su color interno (fill="#212121" dentro del archivo .svg) no
   es alcanzable por CSS de la página en absoluto (a diferencia de un SVG
   inline, ver fix del ícono de TikTok). Se usa un filtro CSS sobre la
   imagen entera: brightness(0) lo fuerza a negro sólido, invert(1) lo
   invierte a blanco — funciona porque el ícono es de un solo tono. */
html.theme-dark .svg-code {
    filter: brightness(0) invert(1);
}

/* --- TRANSICIÓN DE TEMA (cortina de 2 capas) --- */
/* Pedido explícito: al hacer click en la gema, una cortina de 2 capas
   (arriba/abajo) se cierra hasta unirse en el centro, con una línea
   celeste marcando el cruce; el tema cambia mientras la pantalla queda
   cubierta (ver js/main.js: playThemeTransition), y luego las capas se
   abren de nuevo revelando el tema ya cambiado. z-index muy por encima de
   todo lo demás del sitio (header:10, back-to-top:12) para cubrir
   realmente toda la pantalla. El color de cada capa lo fija js/main.js
   ANTES de cerrar (el color del tema AL QUE SE VA), para que al abrirse
   el color de la cortina combine con lo que va quedando debajo — un wipe
   limpio en vez de un parpadeo de color. */
.theme-transition {
    position: fixed;
    inset: 0;
    z-index: 9999;
    pointer-events: none;
}

.theme-transition-layer {
    position: absolute;
    left: 0;
    width: 100%;
    height: 0;
    background: #16171d;
    transition: height 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}

.theme-transition-layer--top {
    top: 0;
}

.theme-transition-layer--bottom {
    bottom: 0;
}

.theme-transition.is-closing .theme-transition-layer {
    height: 50%;
}

.theme-transition-seam {
    position: absolute;
    top: 50%;
    left: 0;
    width: 100%;
    height: 2px;
    background: #17bed2;
    transform: translateY(-50%);
    opacity: 0;
    transition: opacity 0.15s ease;
}

.theme-transition.is-closing .theme-transition-seam {
    opacity: 1;
    transition: opacity 0.15s ease 0.25s;
}