# CSS y JavaScript — Convenciones del Módulo Este documento define cómo escribir CSS, JavaScript y, cuando hace falta, Vue 3 dentro de un módulo Acai. Cubre la regla "Tailwind first" + BEM para CSS custom, las clases utilitarias propias de Acai (`transition3s`, `click-a-child`, `line-clamp2`, `lazyload`, `bg-main-color`, etc.), las CSS variables del tema (`--main-color`), el patrón obligatorio de **scoping** vía la clase raíz del módulo, la regla dura de que `script.js` y `style.css` son **archivos estáticos** (sin Twig dentro), cómo pasar valores dinámicos desde `index-base.tpl` a JS vía `data-*`, cuándo usar Vue 3 y cómo integrarlo evitando conflicto de delimiters con Twig, y los componentes nativos del builder (Carousel `c-tns-wrapper`, Lightbox, Breadcrumb, AOS, Lazy loading). Léelo antes de escribir cualquier `style.css` o `script.js`. ## Estructura del módulo - Cada módulo genera HTML + CSS + JS (y opcionalmente Vue 3). - Define una **clase raíz en kebab-case** específica del módulo: `product-card`, `hero-section`, `buscador-apartados`. - **Todo el CSS y JS deben quedar scopeados bajo esa clase raíz.** ## CSS ### Tailwind first Usa TailwindCSS como método principal. Reserva CSS custom solo cuando Tailwind no cubra el caso (estados complejos, transiciones específicas, animaciones). ```html

Title

``` ### BEM para CSS custom Cuando necesites CSS propio, scopealo bajo la clase raíz con BEM: ```css .hero-section { } .hero-section__title { } .hero-section__image { } .hero-section--dark { } ``` Nunca uses clases globales sin prefijo de módulo. ### CSS variables del tema | Variable | Descripción | |----------|-------------| | `var(--main-color)` | Color de marca principal | | `var(--main-color-light)` | Variante clara | | `var(--main-color-dark)` | Variante oscura | ### Estilos inline con fallbacks Patrón estándar para colores configurables por el usuario (variables del builder): ```html

``` ### Clases utilitarias de Acai | Clase | Descripción | |-------|-------------| | `transition3s` | Transición suave 0.3s | | `click-a-child` | Hace al padre clickeable vía el primer `` hijo | | `line-clamp2` / `line-clamp3` / `line-clamp5` | Truncar texto a N líneas | | `filter-white` | Filtro CSS para teñir imágenes/iconos en blanco | | `lazyload` | Activa lazy loading (usar con `data-src`) | | `text-shadow` | Sombra de texto para legibilidad sobre imágenes | | `wysiwyg` | Wrapper para contenido rico (estilos coherentes) | | `bg-main-color` / `bg-main-color-light` / `bg-main-color-dark` | Fondos con color primario | | `text-main-color` / `text-main-color-light` / `text-main-color-dark` | Texto con color primario | | `titulo-main-color` / `titulo-white` | Estilos de título resaltado | | `c-tns-wrapper` / `c-tns-container` / `c-tns-nav-container` | Carousel built-in | | `glightbox` | Activa lightbox | ### Reglas para `style.css` `style.css` es **estático**. Reglas duras: - **NO** uses sintaxis Twig (`{{ ... }}`, `{% ... %}`) ni atributos builder (`c-if`, `c-for`). - **NO** escribas selectores que dependan de `{{ section_id }}` — scopea con la clase raíz del módulo. ```css /* CORRECTO — scopeado con clase raíz */ .product-card { } .product-card__title { color: var(--main-color); } /* INCORRECTO — Twig inside */ #{{ section_id }} h3 { } ``` ## JavaScript ### `script.js` es estático Igual que `style.css`: **NO** uses sintaxis Twig dentro. Si necesitas valores dinámicos, pásalos desde `index-base.tpl` vía atributos `data-*`. #### Patrón correcto `index-base.tpl`: ```html

    ``` `script.js`: ```js document.querySelectorAll('.buscador-apartados').forEach((section) => { const sectionId = section.dataset.sectionId; const hookEndpoint = section.dataset.hookEndpoint; const limit = parseInt(section.dataset.limit, 10); const input = section.querySelector('.buscador-apartados__input'); const results = section.querySelector('.buscador-apartados__results'); input.addEventListener('input', (e) => { CmsApi.hook(hookEndpoint, { termino: e.target.value, limite: limit }, (data) => { results.innerHTML = data.items.map((it) => `
  • ${it.titulo}
  • `).join(''); }); }); }); ``` NUNCA hagas `CmsApi.hook('/hooks/{{ moduleId }}/', ...)` dentro de `script.js`. Si necesitas el endpoint, pásalo por `data-hook-endpoint`. ### CmsApi (cliente) ```js // Llamar hook CmsApi.hook('/hooks/module_id/', { action: 'getData', id: 123 }, (response) => { console.log(response); }); // Si llamas un hook propio del módulo, usa el module-id real: CmsApi.hook('/hooks/buscadorapartados_hjd8s/', { termino: 'vela' }, (response) => { console.log(response); }); ``` ### Embeber ` ``` Reglas: - **Siempre** redefine `delimiters` a `['${', '}']` para no chocar con Twig. - Mountea sobre un id único usando `section_id`: `#app-{{ section_id }}`. - Vue se carga como librería global vía `add_global_library` o ya incluida en el proyecto (ver `08-layout-and-libraries.md`). ## Variables globales disponibles en JS / Twig | Variable | Descripción | Ejemplo | |----------|-------------|---------| | `section_id` | ID único por instancia del módulo | `
    ` | | `server.HTTP_HOST` | Dominio actual | `https://{{ server.HTTP_HOST }}/path` | | `loop.index` | Índice 1-based en `c-for`/`{% for %}` | `{{ loop.index }}` | | `loop.index is odd` / `is even` | Para layouts alternados | zigzag | | `interno` | `true` dentro del editor CMS | `c-class="{'editor-mode': interno}"` | ### Patrón `section_id` Cada instancia de un módulo recibe un `section_id` único. Úsalo para anchors, IDs HTML y selector raíz de JS: ```html
    ``` ## Componentes nativos ### Carousel — `c-tns-wrapper` ```html
    ``` | Atributo | Descripción | |----------|-------------| | `data-responsive` | Items por breakpoint. JSON `{"0":1,"768":2,"1024":3}` o sintaxis corta `"sm:2, md:3, lg:4"` | | `data-autoplay-timeout` | Intervalo autoplay (ms) | | `data-mode` | `"gallery"` o `"carousel"` | | `data-speed` | Velocidad de transición (ms) | | `data-nav` | `"true"` para mostrar dots | Dots de navegación custom: ```html
    ``` ### Lightbox ```html
    ``` ### Breadcrumb ```html ``` ### Animate On Scroll (AOS) ```html
    Contenido
    ``` Valores comunes: `fade-up`, `fade-down`, `fade-left`, `fade-right`, `zoom-in`, `zoom-in-up`, `fade-up-right`, `fade-up-left`. Tras cambios dinámicos en JS: ```js AOS.refresh(); ``` ### Lazy loading ```html ``` ## Reglas críticas 1. `script.js` y `style.css` son **estáticos** — sin sintaxis Twig dentro. 2. Para valores dinámicos en JS, pásalos desde `index-base.tpl` vía atributos `data-*`. 3. Define una **clase raíz en kebab-case** por módulo. Scopea TODO el CSS/JS bajo ella. 4. Tailwind first; CSS custom solo donde Tailwind no llegue, siempre con BEM. 5. Vue 3: redefine `delimiters: ['${', '}']` para evitar conflicto con Twig. 6. Mountea Vue sobre `#app-{{ section_id }}`. 7. Usa las clases utilitarias de Acai (`transition3s`, `lazyload`, `bg-main-color`, etc.) antes de inventar utilidades. 8. NO embebas lógica `