libreria-astro-lefebvre 0.1.122 → 0.1.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -113,8 +113,66 @@ Para listar componentes en Vue:
113
113
 
114
114
  ---
115
115
 
116
+ ## 🔤 Tipografía de marca: requisitos del portal
117
+
118
+ La librería se publica como código fuente y **no define ni carga fuentes**: los componentes usan tres clases de Tailwind que debe definir el portal que los consume. Sin ellas los textos caen a la fuente por defecto del navegador.
119
+
120
+ | Clase | Fuente | Pesos a cargar | Uso |
121
+ |---|---|---|---|
122
+ | `font-heading` | IBM Plex Serif | 400, 600 | Titulares (Display, Heading 1-4) |
123
+ | `font-body` | IBM Plex Sans | 400, 600 | Texto, enlaces y botones |
124
+ | `font-mono` | IBM Plex Mono | 400, 700 | Etiquetas en mayúsculas y anotaciones |
125
+
126
+ Cada portal tiene que hacer dos cosas:
127
+
128
+ 1. **Cargar las fuentes** IBM Plex Serif, Sans y Mono (por ejemplo con Google Fonts) con los pesos de la tabla.
129
+ 2. **Definir las tres clases** en su configuración de Tailwind. Ejemplos, según la versión que use el portal:
130
+
131
+ ```css
132
+ /* Tailwind v4 (CSS) */
133
+ @theme {
134
+ --font-heading: 'IBM Plex Serif', serif;
135
+ --font-body: 'IBM Plex Sans', sans-serif;
136
+ --font-mono: 'IBM Plex Mono', monospace;
137
+ }
138
+ ```
139
+
140
+ ```js
141
+ // Tailwind v3 (tailwind.config.js)
142
+ theme: {
143
+ extend: {
144
+ fontFamily: {
145
+ heading: ['"IBM Plex Serif"', 'serif'],
146
+ body: ['"IBM Plex Sans"', 'sans-serif'],
147
+ mono: ['"IBM Plex Mono"', 'monospace'],
148
+ },
149
+ },
150
+ }
151
+ ```
152
+
153
+ Equivalencia con la tipografía anterior: Poppins → `font-heading`, Inter → `font-body`, Roboto Mono → `font-mono`. Los componentes de la librería ya no usan `font-poppins` ni `font-inter`; el portal puede mantener esas clases definidas mientras tenga componentes propios que las necesiten.
154
+
155
+ Los tamaños, pesos e interlineados ya van dentro de cada componente según la tabla de estilos del diseño: el portal solo aporta la familia.
156
+
157
+ > ⚠️ **Cambio que afecta al aspecto de los portales existentes.** Al actualizar a esta versión sin haber definido las clases anteriores, los textos pierden la tipografía de marca. Actualiza el portal y la librería a la vez.
158
+
159
+ **Color de marca:** el azul digital `#2134F1` pasa al azul corporativo `#001978` en botones (relleno, borde y etiqueta), iconos y cabeceras. Los degradados con rosa se han sustituido por `#001978` plano. El hover de los botones rellenos es una capa blanca `rgba(255, 255, 255, 0.10)`.
160
+
161
+ ---
162
+
116
163
  ## Bitácora de cambios
117
164
 
165
+ ### 16/10/2026
166
+
167
+ #### Cambio de marca: tipografía y color
168
+
169
+ - Poppins → IBM Plex Serif (`font-heading`), Inter → IBM Plex Sans (`font-body`), Roboto Mono → IBM Plex Mono (`font-mono`). Requisitos del portal en la sección «Tipografía de marca».
170
+ - Tamaños, pesos e interlineados ajustados a la tabla de estilos del diseño; se eliminó el tracking negativo de los titulares.
171
+ - `#2134F1` → `#001978` en botones e iconos; degradados sustituidos por `#001978` plano.
172
+ - Footers `Footer_2025/2026/2027_Napoles`: banda inferior plana `#001978` de 12px (ya no usan `Separador_2025_Reinosa`, que se mantiene como componente).
173
+ - Nueva cabecera `Cabecera_2027_Guadalajara` con la nueva tipografía y color. Las cabeceras Madrid 2025 y 2026 mantienen su estructura.
174
+ - JSON-LD de `Cabecera_2026_Madrid` y `Cabecera_2027_Guadalajara` serializado con `JSON.stringify` y `<` escapado.
175
+
118
176
  ### 30/03/2026
119
177
 
120
178
  #### Soporte de `items` en campos de tipo `list` en los carbins
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libreria-astro-lefebvre",
3
- "version": "0.1.122",
3
+ "version": "0.1.124",
4
4
  "description": "Librería de componentes Astro, React y Vue para Lefebvre",
5
5
  "author": "Equipo web desarrollo Lefebvre",
6
6
  "type": "module",
@@ -3,8 +3,8 @@ import type { ComponentMetadata } from '../interfaces/types';
3
3
  export const metadata: ComponentMetadata = {
4
4
  component_name: 'Cabecera_2027_Guadalajara',
5
5
  category: 'Header',
6
- name: 'Cabecera sticky con menú configurable y subcategorías 2027',
7
- description: 'Cabecera fija (sticky top-0) con logo de Lefebvre, botón CTA y menú horizontal completamente configurable vía prop "categories". Cada ítem admite target (_self o _blank) y una lista opcional de subcategorías en dropdown al hacer hover. Si no se pasa menú custom, cae en un menú por defecto. Incluye hamburguesa en móvil y structured data schema.org (SiteNavigationElement raíz con la jerarquía del menú)',
6
+ name: 'Cabecera sticky con menú configurable 2027',
7
+ description: 'Cabecera fija (sticky top-0) con logo de Lefebvre, botón CTA y menú horizontal completamente configurable vía prop "categories". Cada ítem admite target (_self o _blank). Los ítems personalizados no admiten subcategorías desde Mecano; solo el menú por defecto (si no se pasa menú custom) incluye el desplegable de Blog al hacer hover. Incluye hamburguesa en móvil y structured data schema.org (SiteNavigationElement raíz con la jerarquía del menú)',
8
8
  framework: 'Astro',
9
9
  priority: 1,
10
10
  tags: ['header', 'cabecera', 'navegacion', 'sticky', 'menu', 'cta', 'configurable'],
@@ -81,7 +81,7 @@ const categories = [
81
81
 
82
82
  ---
83
83
 
84
- <header class="sticky top-0 w-full font-inter bg-white z-50 flex flex-col justify-center align-center items-center p-0 overflow-hidden">
84
+ <header class="sticky top-0 w-full font-body bg-white z-50 flex flex-col justify-center align-center items-center p-0 overflow-hidden">
85
85
  <nav class="px-4 py-3 w-full">
86
86
 
87
87
  <div class="max-w-7xl flex items-center justify-between mx-auto">
@@ -90,7 +90,7 @@ const categories = [
90
90
  </a>
91
91
  <div class="flex items-center gap-4">
92
92
  <div class="flex items-center gap-4">
93
- <a href={url} class="h-12 max-[375px] px-3 md:px-5 py-3 font-inter font-semibold max-[375px]:text-sm text-base border-1 border-[#001978] text-[#001978] hover:text-[#ffffff] hover:border-[#001978] hover:bg-[#001978] transition-all duration-300 rounded-lg cursor-pointer shadow-sm">{label}</a>
93
+ <a href={url} class="h-12 max-[375px] px-3 md:px-5 py-3 font-body font-semibold max-[375px]:text-sm text-base border-1 border-[#001978] text-[#001978] hover:text-[#ffffff] hover:border-[#001978] hover:bg-[#001978] transition-all duration-300 rounded-lg cursor-pointer shadow-sm">{label}</a>
94
94
  </div>
95
95
  <button onclick="document.getElementById('navbar-default').classList.toggle('hidden'); event.target.classList.toggle('bg-gray-50');"
96
96
  type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100
@@ -70,24 +70,25 @@ const categories: MenuItem[] = (categoriesProp && categoriesProp.length > 0)
70
70
 
71
71
  const identifier = 'header-cab-' + Math.random().toString(36).substring(2, 10);
72
72
 
73
- const structuredData = `<script type="application/ld+json">
74
- {
73
+ // JSON.stringify escapa comillas y barras de lo que escribe el editor; el reemplazo de "<" evita que un "</script>" cierre la etiqueta.
74
+ const structuredDataJson = JSON.stringify({
75
75
  "@context": "https://schema.org",
76
76
  "@type": "SiteNavigationElement",
77
77
  "name": "Main Navigation",
78
- "url": "${origin}${subdirectory}",
79
- "hasPart": [${categories.map(category => `{
78
+ "url": `${origin}${subdirectory}`,
79
+ "hasPart": categories.map(category => ({
80
80
  "@type": "SiteNavigationElement",
81
- "name": "${category.name}",
82
- "url": "${origin}${category.link}"
83
- }`).join(',')}]
84
- }
85
- </script>`;
81
+ "name": category.name,
82
+ "url": `${origin}${category.link}`
83
+ }))
84
+ }).replace(/</g, '\\u003c');
85
+
86
+ const structuredData = `<script type="application/ld+json">${structuredDataJson}</script>`;
86
87
 
87
88
 
88
89
  ---
89
90
 
90
- <header class="sticky top-0 w-full font-inter bg-white z-50 flex flex-col justify-center align-center items-center p-0">
91
+ <header class="sticky top-0 w-full font-body bg-white z-50 flex flex-col justify-center align-center items-center p-0">
91
92
  <nav class="px-4 py-3 w-full">
92
93
 
93
94
  <div class="max-w-7xl flex items-center justify-between mx-auto">
@@ -96,7 +97,7 @@ const categories: MenuItem[] = (categoriesProp && categoriesProp.length > 0)
96
97
  </a>
97
98
  <div class="flex items-center gap-4">
98
99
  <div class="flex items-center gap-4">
99
- <a href={url} class="h-12 max-[375px] px-3 md:px-5 py-3 font-inter font-semibold max-[375px]:text-sm text-base border-1 border-[#001978] text-[#001978] hover:text-[#ffffff] hover:border-[#001978] hover:bg-[#001978] transition-all duration-300 rounded-lg cursor-pointer shadow-sm">{label}</a>
100
+ <a href={url} class="h-12 max-[375px] px-3 md:px-5 py-3 font-body font-semibold max-[375px]:text-sm text-base border-1 border-[#001978] text-[#001978] hover:text-[#ffffff] hover:border-[#001978] hover:bg-[#001978] transition-all duration-300 rounded-lg cursor-pointer shadow-sm">{label}</a>
100
101
  </div>
101
102
  <button onclick="document.getElementById('navbar-default').classList.toggle('hidden'); event.target.classList.toggle('bg-gray-50');"
102
103
  type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100
@@ -13,7 +13,7 @@ interface MenuItem {
13
13
  }
14
14
 
15
15
  const {
16
- label = 'Pruébalo ya',
16
+ label = '',
17
17
  url = '#',
18
18
  subdirectory = '',
19
19
  loading = 'eager' as 'lazy' | 'eager',
@@ -70,19 +70,23 @@ const categories: MenuItem[] = (categoriesProp && categoriesProp.length > 0)
70
70
 
71
71
  const identifier = 'header-cab-' + Math.random().toString(36).substring(2, 10);
72
72
 
73
- const structuredData = `<script type="application/ld+json">
74
- {
73
+ // Los enlaces con esquema (https:, mailto:, tel:) o "//" ya son absolutos; solo a los relativos se les antepone el origen.
74
+ const toAbsoluteUrl = (link = '') => /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(link) ? link : `${origin}${link}`;
75
+
76
+ // JSON.stringify escapa comillas y barras de lo que escribe el editor; el reemplazo de "<" evita que un "</script>" cierre la etiqueta.
77
+ const structuredDataJson = JSON.stringify({
75
78
  "@context": "https://schema.org",
76
79
  "@type": "SiteNavigationElement",
77
80
  "name": "Main Navigation",
78
- "url": "${origin}${subdirectory}",
79
- "hasPart": [${categories.map(category => `{
81
+ "url": `${origin}${subdirectory}`,
82
+ "hasPart": categories.map(category => ({
80
83
  "@type": "SiteNavigationElement",
81
- "name": "${category.name}",
82
- "url": "${origin}${category.link}"
83
- }`).join(',')}]
84
- }
85
- </script>`;
84
+ "name": category.name,
85
+ "url": toAbsoluteUrl(category.link)
86
+ }))
87
+ }).replace(/</g, '\\u003c');
88
+
89
+ const structuredData = `<script type="application/ld+json">${structuredDataJson}</script>`;
86
90
 
87
91
 
88
92
  ---
@@ -95,12 +99,14 @@ const categories: MenuItem[] = (categoriesProp && categoriesProp.length > 0)
95
99
  <img src="https://assets.lefebvre.es/media/logos-2/svg/lefebvre.svg" class="h-10" alt="GenIA-L Lefebvre" width="160" height="40" loading={loading}>
96
100
  </a>
97
101
  <div class="flex items-center gap-4">
102
+ {label && (
98
103
  <div class="flex items-center gap-4">
99
104
  <a href={url} class="h-12 max-[375px] px-3 md:px-5 py-3 font-body font-semibold max-[375px]:text-sm max-[375px]:leading-[21px] text-base leading-6 border-1 border-[#001978] text-[#001978] hover:text-[#ffffff] hover:border-[#001978] hover:bg-[#001978] transition-all duration-300 rounded-lg cursor-pointer shadow-sm">{label}</a>
100
105
  </div>
101
- <button onclick="document.getElementById('navbar-default').classList.toggle('hidden'); event.target.classList.toggle('bg-gray-50');"
102
- type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100
103
- focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
106
+ )}
107
+ <button onclick="document.getElementById('navbar-default').classList.toggle('hidden'); event.target.classList.toggle('bg-gray-50');"
108
+ type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100
109
+ focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
104
110
  aria-controls="navbar-default" aria-expanded="false">
105
111
  <span class="sr-only pointer-events-none">Abrir menú</span>
106
112
  <svg class="w-5 h-5 pointer-events-none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">