holygrail5 1.0.19 → 1.0.21

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.
Files changed (77) hide show
  1. package/README.md +88 -18
  2. package/config.json +205 -77
  3. package/dist/assets/fonts/suisse-intl-light.woff +0 -0
  4. package/dist/assets/fonts/suisse-intl-light.woff2 +0 -0
  5. package/dist/assets/fonts/suisse-intl-medium.woff +0 -0
  6. package/dist/assets/fonts/suisse-intl-medium.woff2 +0 -0
  7. package/dist/assets/fonts/suisse-intl-regular.woff +0 -0
  8. package/dist/assets/fonts/suisse-intl-regular.woff2 +0 -0
  9. package/dist/assets/fonts/suisse-intl-semibold.woff +0 -0
  10. package/dist/assets/fonts/suisse-intl-semibold.woff2 +0 -0
  11. package/dist/assets/fonts/suisse-works-bold.woff +0 -0
  12. package/dist/assets/fonts/suisse-works-bold.woff2 +0 -0
  13. package/dist/assets/fonts/suisse-works-medium.woff +0 -0
  14. package/dist/assets/fonts/suisse-works-medium.woff2 +0 -0
  15. package/dist/assets/fonts/suisse-works-regular.woff +0 -0
  16. package/dist/assets/fonts/suisse-works-regular.woff2 +0 -0
  17. package/dist/componentes.html +429 -0
  18. package/dist/developer-guide.md +7 -7
  19. package/dist/guide-styles.css +197 -25
  20. package/dist/index.html +807 -689
  21. package/dist/output.css +217 -114
  22. package/dist/skills.html +14 -8
  23. package/dist/themes/dutti-demo.html +713 -19
  24. package/dist/themes/dutti.css +67 -16
  25. package/dist/themes/limited-demo.html +1121 -0
  26. package/dist/themes/limited.css +2493 -0
  27. package/package.json +1 -1
  28. package/src/.data/.previous-values.json +151 -84
  29. package/src/assets/fonts/suisse-intl-light.woff +0 -0
  30. package/src/assets/fonts/suisse-intl-light.woff2 +0 -0
  31. package/src/assets/fonts/suisse-intl-medium.woff +0 -0
  32. package/src/assets/fonts/suisse-intl-medium.woff2 +0 -0
  33. package/src/assets/fonts/suisse-intl-regular.woff +0 -0
  34. package/src/assets/fonts/suisse-intl-regular.woff2 +0 -0
  35. package/src/assets/fonts/suisse-intl-semibold.woff +0 -0
  36. package/src/assets/fonts/suisse-intl-semibold.woff2 +0 -0
  37. package/src/assets/fonts/suisse-works-bold.woff +0 -0
  38. package/src/assets/fonts/suisse-works-bold.woff2 +0 -0
  39. package/src/assets/fonts/suisse-works-medium.woff +0 -0
  40. package/src/assets/fonts/suisse-works-medium.woff2 +0 -0
  41. package/src/assets/fonts/suisse-works-regular.woff +0 -0
  42. package/src/assets/fonts/suisse-works-regular.woff2 +0 -0
  43. package/src/build/asset-manager.js +94 -3
  44. package/src/build/build-orchestrator.js +74 -12
  45. package/src/build/components-generator.js +584 -0
  46. package/src/build/skills-generator.js +43 -5
  47. package/src/build/theme-config-loader.js +58 -0
  48. package/src/build/theme-transformer.js +109 -16
  49. package/src/build/theme-vars-table-generator.js +349 -0
  50. package/src/build/typo-table-generator.js +154 -0
  51. package/src/docs-generator/guide-styles.css +197 -24
  52. package/src/docs-generator/html-generator.js +92 -246
  53. package/src/docs-generator/sections/colors-section.js +109 -0
  54. package/src/docs-generator/value-tracker.js +154 -0
  55. package/src/generators/typo-generator.js +2 -1
  56. package/src/generators/utils.js +90 -1
  57. package/src/skills.html +1 -0
  58. package/src/watch-config.js +99 -32
  59. package/themes/{dutti → _base}/_buttons.css +2 -2
  60. package/themes/{dutti → _base}/_checkboxes.css +1 -1
  61. package/themes/{dutti → _base}/_forms.css +1 -1
  62. package/themes/{dutti → _base}/_inputs.css +55 -10
  63. package/themes/{dutti → _base}/_labels.css +1 -1
  64. package/themes/dutti/README.md +67 -21
  65. package/themes/dutti/_variables.css +7 -1
  66. package/themes/dutti/demo.html +18 -14
  67. package/themes/dutti/theme.css +22 -44
  68. package/themes/dutti/theme.json +86 -0
  69. package/themes/limited/_variables.css +123 -0
  70. package/themes/limited/demo.html +296 -0
  71. package/themes/limited/theme.css +32 -0
  72. package/themes/limited/theme.json +105 -0
  73. /package/themes/{dutti → _base}/_containers.css +0 -0
  74. /package/themes/{dutti → _base}/_radios.css +0 -0
  75. /package/themes/{dutti → _base}/_switches.css +0 -0
  76. /package/themes/{dutti → _base}/components/_icons.css +0 -0
  77. /package/themes/{dutti → _base}/objects/_grid.css +0 -0
@@ -0,0 +1,154 @@
1
+ // value-tracker.js
2
+ // Persistencia y diff de valores del design system entre builds.
3
+ //
4
+ // Antes vivía dentro de html-generator.js como ~160 líneas de lógica
5
+ // mezclada con la generación de HTML. Se extrae aquí porque:
6
+ // - No depende del HTML en absoluto: es I/O + comparación.
7
+ // - Se puede testear de forma aislada sin construir toda la guía.
8
+ // - Mantiene html-generator.js enfocado en... generar HTML.
9
+ //
10
+ // Contrato:
11
+ // - `loadPreviousValues(path)` devuelve el JSON previo o null.
12
+ // - `saveCurrentValues(values, path)` persiste el estado actual.
13
+ // - `getChangedValues(current, previous)` devuelve un Set con las
14
+ // claves cambiadas, usando el mismo formato que esperaba el
15
+ // callsite original ("className.prop", "className.bp.prop",
16
+ // "colors.key", "spacingMap.key", "fontFamilyMap.key",
17
+ // "breakpoints.mobile|desktop", "variable.<name>").
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ function loadPreviousValues(previousValuesPath) {
23
+ try {
24
+ if (fs.existsSync(previousValuesPath)) {
25
+ const content = fs.readFileSync(previousValuesPath, 'utf8');
26
+ return JSON.parse(content);
27
+ }
28
+ } catch (_) {
29
+ // Si no existe o hay error, devuelve null
30
+ }
31
+ return null;
32
+ }
33
+
34
+ function saveCurrentValues(currentValues, previousValuesPath) {
35
+ try {
36
+ const dir = path.dirname(previousValuesPath);
37
+ if (!fs.existsSync(dir)) {
38
+ fs.mkdirSync(dir, { recursive: true });
39
+ }
40
+ fs.writeFileSync(previousValuesPath, JSON.stringify(currentValues, null, 2), 'utf8');
41
+ } catch (error) {
42
+ console.warn('⚠️ No se pudo guardar los valores anteriores:', error.message);
43
+ }
44
+ }
45
+
46
+ function diffMap(current, previous, prefix, changes) {
47
+ const cur = current || {};
48
+ const prev = previous || {};
49
+ Object.keys(cur).forEach(key => {
50
+ if (cur[key] !== prev[key]) {
51
+ changes.add(`${prefix}.${key}`);
52
+ }
53
+ });
54
+ // Claves eliminadas: el callsite histórico las marca también.
55
+ Object.keys(prev).forEach(key => {
56
+ if (!(key in cur)) {
57
+ changes.add(`${prefix}.${key}`);
58
+ }
59
+ });
60
+ }
61
+
62
+ function getChangedValues(currentValues, previousValues) {
63
+ const changes = new Set();
64
+
65
+ // Primera ejecución: sin diff (nada se marca cambiado).
66
+ if (!previousValues) {
67
+ return changes;
68
+ }
69
+
70
+ // Breakpoints
71
+ if (currentValues.breakpoints) {
72
+ const curBp = currentValues.breakpoints;
73
+ const prevBp = previousValues.breakpoints;
74
+ if (!prevBp) {
75
+ changes.add('breakpoints.mobile');
76
+ changes.add('breakpoints.desktop');
77
+ } else {
78
+ if (curBp.mobile !== prevBp.mobile) changes.add('breakpoints.mobile');
79
+ if (curBp.desktop !== prevBp.desktop) changes.add('breakpoints.desktop');
80
+ }
81
+ }
82
+
83
+ // Mapas planos (fontFamilyMap, spacingMap, colors) comparten el mismo
84
+ // patrón de diff: misma clave con valor distinto → marca.
85
+ if (currentValues.fontFamilyMap) {
86
+ diffMap(currentValues.fontFamilyMap, previousValues.fontFamilyMap, 'fontFamilyMap', changes);
87
+ }
88
+ if (currentValues.spacingMap) {
89
+ diffMap(currentValues.spacingMap, previousValues.spacingMap, 'spacingMap', changes);
90
+ }
91
+ if (currentValues.colors) {
92
+ diffMap(currentValues.colors, previousValues.colors, 'colors', changes);
93
+ }
94
+
95
+ // Clases de tipografía (typo): tienen propiedades base + breakpoints
96
+ // anidados (mobile/desktop.fontSize|lineHeight). Mantenemos el
97
+ // comportamiento histórico: aceptar tanto `currentValues.typo` como
98
+ // `currentValues` al raíz, por compatibilidad con callsites antiguos.
99
+ const currentClasses = currentValues.typo || currentValues;
100
+ const previousClasses = previousValues.typo || previousValues;
101
+ Object.keys(currentClasses).forEach(className => {
102
+ const current = currentClasses[className];
103
+ const previous = previousClasses[className];
104
+ if (!current || typeof current !== 'object') return;
105
+ if (!previous) {
106
+ // Clase nueva: marcar todas sus props base (no mobile/desktop).
107
+ Object.keys(current).forEach(prop => {
108
+ if (prop !== 'mobile' && prop !== 'desktop') {
109
+ changes.add(`${className}.${prop}`);
110
+ }
111
+ });
112
+ return;
113
+ }
114
+ ['fontFamily', 'fontWeight', 'letterSpacing', 'textTransform'].forEach(prop => {
115
+ if (current[prop] !== previous[prop]) {
116
+ changes.add(`${className}.${prop}`);
117
+ }
118
+ });
119
+ ['mobile', 'desktop'].forEach(bp => {
120
+ if (!current[bp]) return;
121
+ if (!previous[bp]) {
122
+ if (current[bp].fontSize) changes.add(`${className}.${bp}.fontSize`);
123
+ if (current[bp].lineHeight) changes.add(`${className}.${bp}.lineHeight`);
124
+ } else {
125
+ if (current[bp].fontSize !== previous[bp].fontSize) {
126
+ changes.add(`${className}.${bp}.fontSize`);
127
+ }
128
+ if (current[bp].lineHeight !== previous[bp].lineHeight) {
129
+ changes.add(`${className}.${bp}.lineHeight`);
130
+ }
131
+ }
132
+ });
133
+ });
134
+
135
+ // Variables CSS compartidas: solo marca nuevas o cambiadas
136
+ // (no detecta eliminadas, manteniendo el comportamiento histórico).
137
+ if (currentValues.variables) {
138
+ const curVars = currentValues.variables;
139
+ const prevVars = previousValues.variables || {};
140
+ Object.keys(curVars).forEach(varName => {
141
+ if (prevVars[varName] === undefined || curVars[varName] !== prevVars[varName]) {
142
+ changes.add(`variable.${varName}`);
143
+ }
144
+ });
145
+ }
146
+
147
+ return changes;
148
+ }
149
+
150
+ module.exports = {
151
+ loadPreviousValues,
152
+ saveCurrentValues,
153
+ getChangedValues
154
+ };
@@ -198,7 +198,8 @@ function generateClassCSS(className, cls, breakpointName, valueMap, prefix, cate
198
198
  varName = getSharedVarName(prop, props[prop], prefix, category);
199
199
  }
200
200
 
201
- cssProps.push(` ${toKebabCase(prop)}: var(${varName});`);
201
+ const importantFlag = prop === 'lineHeight' ? ' !important' : '';
202
+ cssProps.push(` ${toKebabCase(prop)}: var(${varName})${importantFlag};`);
202
203
  });
203
204
 
204
205
  return cssProps.length ? ` .${className} {\n${cssProps.join('\n')}\n }` : '';
@@ -112,12 +112,101 @@ function combineThemeCSS(themeDir) {
112
112
  }
113
113
  }
114
114
 
115
+ /**
116
+ * Devuelve la lista canónica de temas activos a partir de un config.
117
+ *
118
+ * Soporta dos formatos de configuración:
119
+ * - Nuevo: { themes: [{ name, enabled }, ...] }
120
+ * - Antiguo: { theme: { name, enabled } }
121
+ *
122
+ * Cuando hay ambos, `themes[]` tiene prioridad. Los consumidores que
123
+ * heredan configs antiguos siguen funcionando sin cambios. El objeto
124
+ * devuelto por cada tema se enriquece con `label` (capitalización del
125
+ * slug) para que los generadores de nav no tengan que recalcularlo.
126
+ *
127
+ * @param {Object} configData - Config ya cargado (objeto JS).
128
+ * @returns {Array<{name:string, enabled:boolean, label:string}>}
129
+ */
130
+ function resolveActiveThemes(configData) {
131
+ if (!configData) return [];
132
+
133
+ const rawList = Array.isArray(configData.themes)
134
+ ? configData.themes
135
+ : (configData.theme ? [configData.theme] : []);
136
+
137
+ return rawList
138
+ .filter(t => t && t.enabled && t.name)
139
+ .map(t => ({
140
+ name: t.name,
141
+ enabled: true,
142
+ label: 'Tema ' + t.name.charAt(0).toUpperCase() + t.name.slice(1)
143
+ }));
144
+ }
145
+
146
+ /**
147
+ * Aplica los overrides de tipografía de un tema sobre el config base.
148
+ *
149
+ * Motivación: la tabla de tipografía lee `config.typo.*.fontFamily`
150
+ * (strings CSS crudos). Cuando un tema redefine las variables
151
+ * `--hg-typo-font-family-*` en su `_variables.css`, el Preview se
152
+ * renderiza con la fuente del tema (porque las variables ganan), pero
153
+ * las columnas "Font family" seguirían mostrando las de la base. Para
154
+ * que la tabla refleje lo que realmente se pinta, mapeamos cada
155
+ * `fontFamily` del typo a través de su alias en la fontFamilyMap base
156
+ * y lo sustituimos por el override del tema.
157
+ *
158
+ * Convención: el tema declara sus overrides en
159
+ * theme.json → typography.fontFamilyMap
160
+ * con las MISMAS claves que la base (`primary-light`, `primary-regular`,
161
+ * `primary-bold`, `secondary`, …). Así seguimos teniendo una única
162
+ * fuente de verdad para la tabla y evitamos duplicar la lista de clases
163
+ * (`title-xxl`, `title-l`, …) en cada tema.
164
+ *
165
+ * Devuelve un NUEVO objeto (no muta el config original). Si no hay
166
+ * overrides, devuelve el config tal cual.
167
+ *
168
+ * @param {Object} config - Config base (config.json parseado).
169
+ * @param {Object} themeData - theme.json parseado (puede ser null).
170
+ * @returns {Object} Config con `fontFamilyMap` y `typo.*.fontFamily` remapeados.
171
+ */
172
+ function applyThemeTypographyOverrides(config, themeData) {
173
+ if (!config) return config;
174
+ const overrides =
175
+ themeData &&
176
+ themeData.typography &&
177
+ themeData.typography.fontFamilyMap;
178
+ if (!overrides || Object.keys(overrides).length === 0) return config;
179
+
180
+ const baseMap = config.fontFamilyMap || {};
181
+ const mergedMap = { ...baseMap, ...overrides };
182
+
183
+ const remappedTypo = {};
184
+ Object.entries(config.typo || {}).forEach(([className, cls]) => {
185
+ const alias = getFontFamilyName(cls.fontFamily, baseMap);
186
+ // `alias` será una clave conocida (primary-regular, …) cuando el
187
+ // fontFamily del typo coincida con un valor de la base. Si coincide
188
+ // y el tema la sobreescribe, sustituimos el fontFamily por el del
189
+ // tema. Si no, dejamos el string original (no hay nada que remapear).
190
+ const overriddenFamily =
191
+ overrides[alias] !== undefined ? overrides[alias] : cls.fontFamily;
192
+ remappedTypo[className] = { ...cls, fontFamily: overriddenFamily };
193
+ });
194
+
195
+ return {
196
+ ...config,
197
+ fontFamilyMap: mergedMap,
198
+ typo: remappedTypo
199
+ };
200
+ }
201
+
115
202
  module.exports = {
116
203
  toKebabCase,
117
204
  pxToRem,
118
205
  remToPx,
119
206
  getFontFamilyName,
120
207
  writeFile,
121
- combineThemeCSS
208
+ combineThemeCSS,
209
+ resolveActiveThemes,
210
+ applyThemeTypographyOverrides
122
211
  };
123
212
 
package/src/skills.html CHANGED
@@ -321,6 +321,7 @@
321
321
  <nav class="sk-nav">
322
322
  <a href="index.html">Guía</a>
323
323
  <a href="themes/dutti-demo.html">Tema Dutti</a>
324
+ <a href="themes/limited-demo.html">Tema Limited</a>
324
325
  <a href="skills.html" class="active">Skills</a>
325
326
  </nav>
326
327
  </header>
@@ -7,8 +7,8 @@ const path = require('path');
7
7
  const crypto = require('crypto');
8
8
  const { BuildOrchestrator } = require('./build/build-orchestrator');
9
9
  const { AssetManager } = require('./build/asset-manager');
10
- const { ThemeTransformer } = require('./build/theme-transformer');
11
10
  const { loadConfig } = require('./config-loader');
11
+ const { resolveActiveThemes } = require('./generators/utils');
12
12
 
13
13
  // Constantes
14
14
  const DEBOUNCE_DELAY = 300; // ms - tiempo de espera antes de regenerar
@@ -50,18 +50,41 @@ function generateFiles(configPath, outputPath, htmlPath, silent = false) {
50
50
  }
51
51
 
52
52
  // Función para copiar archivos CSS e imágenes usando AssetManager
53
- function copyCSSFiles(silent = false) {
53
+ // IMPORTANTE: Carga `assets` desde config.json (misma fuente de verdad que
54
+ // usa el BuildOrchestrator). Si no pudiera leerse, AssetManager aplica su
55
+ // fallback interno ASSETS_CONFIG. Esto evita que watch use una lista
56
+ // distinta a la del build principal (incl. woff además de woff2).
57
+ function loadAssetsConfig(configPath) {
58
+ if (!configPath) return null;
59
+ try {
60
+ const cfg = loadConfig(configPath);
61
+ return cfg && cfg.assets ? cfg.assets : null;
62
+ } catch (_) {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ function copyCSSFiles(silent = false, configPath = null) {
54
68
  const projectRoot = path.join(__dirname, '..');
55
- const assetManager = new AssetManager(projectRoot);
69
+ const assetsConfig = loadAssetsConfig(configPath);
70
+ const assetManager = new AssetManager(projectRoot, assetsConfig);
56
71
  assetManager.copyCSS(silent);
57
72
  }
58
73
 
59
- function copyImageFiles(silent = false) {
74
+ function copyImageFiles(silent = false, configPath = null) {
60
75
  const projectRoot = path.join(__dirname, '..');
61
- const assetManager = new AssetManager(projectRoot);
76
+ const assetsConfig = loadAssetsConfig(configPath);
77
+ const assetManager = new AssetManager(projectRoot, assetsConfig);
62
78
  assetManager.copyImages(silent);
63
79
  }
64
80
 
81
+ function copyFontFiles(silent = false, configPath = null) {
82
+ const projectRoot = path.join(__dirname, '..');
83
+ const assetsConfig = loadAssetsConfig(configPath);
84
+ const assetManager = new AssetManager(projectRoot, assetsConfig);
85
+ assetManager.copyFonts(silent);
86
+ }
87
+
65
88
  // Función principal de watch optimizada
66
89
  function watch(configPath = path.join(__dirname, '..', 'config.json'), outputPath = path.join(__dirname, '..', 'dist', 'output.css'), htmlPath = path.join(__dirname, '..', 'dist', 'index.html'), silent = false) {
67
90
  if (!silent) {
@@ -86,10 +109,54 @@ function watch(configPath = path.join(__dirname, '..', 'config.json'), outputPat
86
109
  path.join(__dirname, 'docs-generator', 'guide-styles.css')
87
110
  ];
88
111
 
89
- // Archivos de tema a observar
90
- const themeFilesToWatch = [
91
- path.join(projectRoot, 'themes', 'dutti', 'demo.html')
92
- ];
112
+ // Archivos de tema a observar.
113
+ // Antes solo se vigilaba themes/dutti/demo.html. Con la arquitectura
114
+ // actual:
115
+ // - Puede haber varios temas activos (config.themes[]).
116
+ // - Los componentes compartidos viven en themes/_base/ y cambios ahí
117
+ // también deben disparar un rebuild de todas las demos.
118
+ // Recolectamos dinámicamente los demo.html de cada tema activo +
119
+ // todos los .css de themes/_base/ (recursivo).
120
+ const themeFilesToWatch = (() => {
121
+ const watched = [];
122
+
123
+ // Intentamos leer el config para saber qué temas están activos.
124
+ // Delegamos en `resolveActiveThemes` para que el watch reaccione
125
+ // exactamente a los mismos temas que construye el orquestador.
126
+ // Soporta tanto `config.themes[]` (nuevo) como `config.theme` (antiguo).
127
+ let activeThemes = [];
128
+ try {
129
+ const configData = loadConfig(configPath);
130
+ activeThemes = resolveActiveThemes(configData);
131
+ } catch (e) {
132
+ // Si no podemos leer el config, caemos al fallback histórico
133
+ // para no romper flujos existentes.
134
+ activeThemes = [{ name: 'dutti', enabled: true, label: 'Tema Dutti' }];
135
+ }
136
+
137
+ activeThemes.forEach(t => {
138
+ const demoFile = path.join(projectRoot, 'themes', t.name, 'demo.html');
139
+ if (fs.existsSync(demoFile)) watched.push(demoFile);
140
+ });
141
+
142
+ // Componentes compartidos: watch recursivo simple sobre themes/_base/.
143
+ const baseDir = path.join(projectRoot, 'themes', '_base');
144
+ if (fs.existsSync(baseDir)) {
145
+ const walk = (dir) => {
146
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
147
+ const full = path.join(dir, entry.name);
148
+ if (entry.isDirectory()) {
149
+ walk(full);
150
+ } else if (entry.isFile() && full.endsWith('.css')) {
151
+ watched.push(full);
152
+ }
153
+ }
154
+ };
155
+ try { walk(baseDir); } catch (_) { /* ignorar errores de lectura */ }
156
+ }
157
+
158
+ return watched;
159
+ })();
93
160
 
94
161
  // Estado del watch
95
162
  let lastHash = getFileHash(configPath);
@@ -156,8 +223,9 @@ function watch(configPath = path.join(__dirname, '..', 'config.json'), outputPat
156
223
  if (!silent) {
157
224
  console.log(`🔄 Detectado cambio en ${path.basename(cssFile)}, copiando a dist/...\n`);
158
225
  }
159
- copyCSSFiles(silent);
160
- copyImageFiles(silent);
226
+ copyCSSFiles(silent, configPath);
227
+ copyImageFiles(silent, configPath);
228
+ copyFontFiles(silent, configPath);
161
229
  if (!silent) {
162
230
  console.log('✨ CSS actualizado - Recarga el navegador para ver los cambios\n');
163
231
  }
@@ -166,44 +234,43 @@ function watch(configPath = path.join(__dirname, '..', 'config.json'), outputPat
166
234
  }, DEBOUNCE_DELAY);
167
235
  }
168
236
 
169
- // Función para manejar cambios en archivos de tema
237
+ // Función para manejar cambios en archivos de tema.
238
+ // Antes el handler intentaba re-transformar SOLO la demo de un único
239
+ // tema (leyendo `configData.theme` singular). Con la arquitectura
240
+ // actual, un cambio puede venir de:
241
+ // - themes/<name>/demo.html → afecta a la demo de ESE tema
242
+ // - themes/_base/**/*.css → afecta al CSS de TODOS los temas
243
+ // Para cubrir ambos casos sin reimplementar la orquestación,
244
+ // delegamos en `generateFiles`, que usa BuildOrchestrator y ya sabe
245
+ // recorrer `config.themes[]` (o el antiguo `config.theme`).
170
246
  function handleThemeChange(themeFile) {
171
247
  if (debounceTimer) {
172
248
  clearTimeout(debounceTimer);
173
249
  }
174
-
250
+
175
251
  debounceTimer = setTimeout(() => {
176
252
  const currentHash = getFileHash(themeFile);
177
253
  const lastThemeHash = themeHashes.get(themeFile);
178
-
254
+
179
255
  if (currentHash && currentHash !== lastThemeHash && !isRegenerating) {
180
256
  isRegenerating = true;
181
257
  themeHashes.set(themeFile, currentHash);
182
258
  if (!silent) {
183
- console.log(`🔄 Detectado cambio en ${path.basename(themeFile)}, regenerando demo...\n`);
259
+ const rel = path.relative(projectRoot, themeFile);
260
+ console.log(`🔄 Detectado cambio en ${rel}, regenerando...\n`);
184
261
  }
185
-
186
- // Usar ThemeTransformer en lugar de ejecutar script externo
262
+
187
263
  try {
188
- const configData = loadConfig(configPath);
189
- if (configData.theme && configData.theme.enabled && configData.theme.name) {
190
- const themeName = configData.theme.name;
191
- const outputDir = path.dirname(outputPath);
192
- const targetFile = path.join(outputDir, 'themes', `${themeName}-demo.html`);
193
-
194
- const themeTransformer = new ThemeTransformer(projectRoot);
195
- themeTransformer.transform(themeFile, targetFile, themeName, silent);
196
-
197
- if (!silent) {
198
- console.log('✨ Demo actualizado - Recarga el navegador para ver los cambios\n');
199
- }
264
+ generateFiles(configPath, outputPath, htmlPath, silent);
265
+ if (!silent) {
266
+ console.log('✨ Tema(s) actualizado(s) - Recarga el navegador para ver los cambios\n');
200
267
  }
201
268
  } catch (error) {
202
269
  if (!silent) {
203
- console.error('❌ Error al regenerar demo:', error.message);
270
+ console.error('❌ Error al regenerar tema:', error.message);
204
271
  }
205
272
  }
206
-
273
+
207
274
  isRegenerating = false;
208
275
  }
209
276
  }, DEBOUNCE_DELAY);
@@ -337,4 +404,4 @@ if (require.main === module) {
337
404
  watch(configPath, outputPath, htmlPath);
338
405
  }
339
406
 
340
- module.exports = { watch, generateFiles, copyCSSFiles, copyImageFiles };
407
+ module.exports = { watch, generateFiles, copyCSSFiles, copyImageFiles, copyFontFiles };
@@ -9,7 +9,7 @@
9
9
  align-items: center;
10
10
  justify-content: center;
11
11
  gap: var(--hg-spacing-8);
12
- font-family: var(--hg-typo-font-family-primary);
12
+ font-family: var(--hg-typo-font-family-primary-regular);
13
13
  font-size: var(--hg-typo-font-size-13);
14
14
  font-weight: var(--hg-typo-font-weight-400);
15
15
  line-height: var(--hg-typo-line-height-1-5);
@@ -45,7 +45,7 @@
45
45
  color: var(--hg-color-white);
46
46
  background-color: var(--hg-color-black);
47
47
  border: var(--border-width) var(--border-style) var(--hg-color-black);
48
- font-family: var(--hg-typo-font-family-primary);
48
+ font-family: var(--hg-typo-font-family-primary-regular);
49
49
  font-weight: var(--hg-typo-font-weight-400);
50
50
  }
51
51
 
@@ -89,7 +89,7 @@
89
89
  }
90
90
 
91
91
  .checkbox-label {
92
- font-family: var(--hg-typo-font-family-primary);
92
+ font-family: var(--hg-typo-font-family-primary-regular);
93
93
  font-size: var(--hg-typo-font-size-13);
94
94
  font-weight: var(--hg-typo-font-weight-400);
95
95
  line-height: var(--hg-typo-line-height-1-5);
@@ -28,7 +28,7 @@
28
28
  /* Helper Text / Messages */
29
29
  .helper-text {
30
30
  display: block;
31
- font-family: var(--hg-typo-font-family-primary);
31
+ font-family: var(--hg-typo-font-family-primary-regular);
32
32
  font-size: var(--hg-typo-font-size-12);
33
33
  font-weight: var(--hg-typo-font-weight-400);
34
34
  line-height: var(--hg-typo-line-height-1-5);
@@ -6,7 +6,7 @@
6
6
  .input {
7
7
  display: block;
8
8
  width: 100%;
9
- font-family: var(--hg-typo-font-family-primary);
9
+ font-family: var(--hg-typo-font-family-primary-regular);
10
10
  font-size: var(--hg-typo-font-size-13);
11
11
  font-weight: var(--hg-typo-font-weight-400);
12
12
  line-height: var(--hg-typo-line-height-1-5);
@@ -56,7 +56,7 @@
56
56
 
57
57
  .form-input-label-2,
58
58
  .error-zone {
59
- font-family: var(--hg-typo-font-family-primary);
59
+ font-family: var(--hg-typo-font-family-primary-regular);
60
60
  position: relative;
61
61
 
62
62
  display: block;
@@ -74,7 +74,7 @@
74
74
 
75
75
  .form-input-label-2 .help,
76
76
  .error-zone .help {
77
- font-family: var(--hg-typo-font-family-primary);
77
+ font-family: var(--hg-typo-font-family-primary-regular);
78
78
  position: relative;
79
79
  line-height: 1.5;
80
80
  font-size: 12px;
@@ -104,15 +104,23 @@
104
104
  }
105
105
 
106
106
  .form-input-label-2 > input,
107
+ .form-input-label-2 > textarea,
108
+ .form-input-label-2 > select,
107
109
  .form-input-label-2 > label,
108
110
  .error-zone > input,
111
+ .error-zone > textarea,
112
+ .error-zone > select,
109
113
  .error-zone > label {
110
- font-family: var(--hg-typo-font-family-primary);
114
+ font-family: var(--hg-typo-font-family-primary-regular);
111
115
  font-weight: normal;
112
116
  }
113
117
 
114
118
  .form-input-label-2 > input,
115
- .error-zone > input {
119
+ .form-input-label-2 > textarea,
120
+ .form-input-label-2 > select,
121
+ .error-zone > input,
122
+ .error-zone > textarea,
123
+ .error-zone > select {
116
124
  border: none;
117
125
  border-bottom: var(--border-width) var(--border-style) var(--hg-color-primary);
118
126
  border-radius: 0;
@@ -120,13 +128,27 @@
120
128
  padding: var(--hg-spacing-16) 0 var(--hg-spacing-8) 0;
121
129
  padding-left: 0;
122
130
  padding-right: 0;
123
- height: 40px;
124
131
  box-sizing: border-box;
125
132
  transition: border-bottom-color 0.1s ease-in-out, border-bottom-width 0.1s ease-in-out;
126
133
  margin: 0;
127
134
  vertical-align: top;
128
135
  }
129
136
 
137
+ .form-input-label-2 > input,
138
+ .form-input-label-2 > select,
139
+ .error-zone > input,
140
+ .error-zone > select {
141
+ height: 40px;
142
+ }
143
+
144
+ /* El textarea mantiene su altura natural (rows) pero respeta el padding
145
+ superior para dejar aire al label flotado. */
146
+ .form-input-label-2 > textarea,
147
+ .error-zone > textarea {
148
+ min-height: 40px;
149
+ resize: vertical;
150
+ }
151
+
130
152
  .form-input-label-2 > label,
131
153
  .error-zone > label {
132
154
  box-sizing: border-box;
@@ -147,12 +169,27 @@
147
169
  }
148
170
 
149
171
  .form-input-label-2 input::placeholder,
150
- .error-zone input::placeholder {
172
+ .form-input-label-2 textarea::placeholder,
173
+ .error-zone input::placeholder,
174
+ .error-zone textarea::placeholder {
151
175
  color: transparent;
152
176
  }
153
177
 
154
178
  .form-input-label-2 input:not(:placeholder-shown) ~ label,
155
- .error-zone input:not(:placeholder-shown) ~ label {
179
+ .form-input-label-2 textarea:not(:placeholder-shown) ~ label,
180
+ .error-zone input:not(:placeholder-shown) ~ label,
181
+ .error-zone textarea:not(:placeholder-shown) ~ label {
182
+ top: 0;
183
+ font-size: 12px;
184
+ color: var(--hg-color-dark-grey);
185
+ transform: translateY(0);
186
+ }
187
+
188
+ /* El <select> no soporta :placeholder-shown (siempre tiene una option
189
+ seleccionada), así que forzamos el label a estar permanentemente
190
+ flotado arriba cuando el control es un select. */
191
+ .form-input-label-2 > select ~ label,
192
+ .error-zone > select ~ label {
156
193
  top: 0;
157
194
  font-size: 12px;
158
195
  color: var(--hg-color-dark-grey);
@@ -160,12 +197,20 @@
160
197
  }
161
198
 
162
199
  .form-input-label-2 input:focus,
163
- .error-zone input:focus {
200
+ .form-input-label-2 textarea:focus,
201
+ .form-input-label-2 select:focus,
202
+ .error-zone input:focus,
203
+ .error-zone textarea:focus,
204
+ .error-zone select:focus {
164
205
  outline: none;
165
206
  }
166
207
 
167
208
  .form-input-label-2 input:focus ~ label,
168
- .error-zone input:focus ~ label {
209
+ .form-input-label-2 textarea:focus ~ label,
210
+ .form-input-label-2 select:focus ~ label,
211
+ .error-zone input:focus ~ label,
212
+ .error-zone textarea:focus ~ label,
213
+ .error-zone select:focus ~ label {
169
214
  top: 0;
170
215
  font-size: 12px;
171
216
  color: var(--hg-color-dark-grey);
@@ -5,7 +5,7 @@
5
5
 
6
6
  .label {
7
7
  display: block;
8
- font-family: var(--hg-typo-font-family-primary);
8
+ font-family: var(--hg-typo-font-family-primary-regular);
9
9
  font-size: var(--hg-typo-font-size-12);
10
10
  font-weight: var(--hg-typo-font-weight-700);
11
11
  line-height: var(--hg-typo-line-height-1-5);