slicejs-web-framework 2.4.3 → 2.4.5

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.
@@ -295,9 +295,18 @@ export default class Controller {
295
295
  return Promise.resolve(false);
296
296
  }
297
297
 
298
- const { components, metadata } = bundle;
298
+ // Set tracking flags synchronously before any async work, so callers that
299
+ // await import() see the flags set immediately when the Promise resolves.
300
+ const { components, metadata } = bundle;
301
+ const bundleKey = metadata?.bundleKey;
302
+ if (bundleKey) {
303
+ this.loadedBundles.add(bundleKey);
304
+ if (metadata?.type === 'critical') {
305
+ this.criticalBundleLoaded = true;
306
+ }
307
+ }
299
308
 
300
- console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
309
+ console.log(`📦 Registering bundle: ${metadata.type} (${metadata.componentCount} components)`);
301
310
 
302
311
  const entries = Object.entries(components);
303
312
  const chunkSize = 50;
@@ -19,7 +19,8 @@ export default class StylesManager {
19
19
  }
20
20
 
21
21
  if (slice.themeConfig.enabled) {
22
- const ThemeManagerClass = slice.frameworkClasses?.ThemeManager;
22
+ const ThemeManagerClass = slice.frameworkClasses?.ThemeManager
23
+ || await slice.getClass(`${slice.paths.structuralComponentFolderPath}/StylesManager/ThemeManager/ThemeManager.js`);
23
24
  if (!ThemeManagerClass) {
24
25
  throw new Error('ThemeManager not available');
25
26
  }
package/Slice/Slice.js CHANGED
@@ -21,6 +21,10 @@ export default class Slice {
21
21
  this.loadingConfig = sliceConfig.loading;
22
22
  this.eventsConfig = sliceConfig.events;
23
23
 
24
+ // Default to production until init() resolves the actual mode.
25
+ // Safe to call isProduction() before init() completes.
26
+ this._mode = 'production';
27
+
24
28
  // 📦 Bundle system is initialized automatically via import in index.js
25
29
  }
26
30
 
@@ -39,11 +43,12 @@ export default class Slice {
39
43
  }
40
44
 
41
45
  /**
42
- * Flag for production behavior (override in builds).
46
+ * Returns true when running in production mode.
47
+ * Reliable after init() has completed.
43
48
  * @returns {boolean}
44
49
  */
45
50
  isProduction() {
46
- return true;
51
+ return this._mode === 'production';
47
52
  }
48
53
 
49
54
  /**
@@ -233,7 +238,6 @@ async function loadConfig() {
233
238
  const response = await fetch('/sliceConfig.json'); // 🔹 Express lo sirve desde `src/`
234
239
  if (!response.ok) throw new Error('Error loading sliceConfig.json');
235
240
  const json = await response.json();
236
- console.log(json);
237
241
  return json;
238
242
  } catch (error) {
239
243
  console.error(`Error loading config file: ${error.message}`);
@@ -250,59 +254,101 @@ async function init() {
250
254
  return;
251
255
  }
252
256
 
257
+ // 1+2. Fetch mode endpoint and bundle config in parallel — both are independent.
258
+ // In production, /slice-env.json returns 404 (catch is expected and normal).
259
+ // bundleConfigJson.production serves as a mode fallback when env endpoint is absent.
253
260
  let frameworkClasses = null;
254
- let bundleConfigJson = null;
255
- try {
256
- const response = await fetch('/bundles/bundle.config.json', { cache: 'no-store' });
257
- if (response.ok) {
258
- bundleConfigJson = await response.json();
259
- }
260
- } catch (error) {
261
- // ignore
261
+ const [envResult, configResult] = await Promise.all([
262
+ fetch('/slice-env.json', { cache: 'no-store' })
263
+ .then(r => r.ok ? r.json() : null)
264
+ .catch(() => null),
265
+ fetch('/bundles/bundle.config.json', { cache: 'no-store' })
266
+ .then(r => r.ok ? r.json() : null)
267
+ .catch(() => null)
268
+ ]);
269
+ const envMode = envResult?.mode ?? null;
270
+ const bundleConfigJson = configResult;
271
+
272
+ // 3. Determine canonical mode: env endpoint takes precedence, then bundle config
273
+ let resolvedMode;
274
+ if (envMode) {
275
+ resolvedMode = envMode;
276
+ } else if (bundleConfigJson?.production) {
277
+ resolvedMode = 'production';
278
+ } else {
279
+ resolvedMode = 'development';
262
280
  }
263
281
 
264
- try {
265
- if (bundleConfigJson?.production) {
266
- await import('/bundles/slice-bundle.framework.js');
267
- frameworkClasses = window.SLICE_FRAMEWORK_CLASSES || null;
268
- }
269
- } catch (error) {
270
- console.warn('Framework bundle not available, falling back to dynamic imports', error);
282
+ // Pre-fetch critical bundle to warm the browser HTTP cache while the framework
283
+ // bundle is downloading and executing. fetch() downloads bytes without evaluating
284
+ // the module, so the auto-registration block runs safely later when window.slice
285
+ // already exists. Errors are silently ignored — import() will retry if needed.
286
+ const criticalBundleUrl = (resolvedMode === 'production' && bundleConfigJson?.bundles?.critical?.file)
287
+ ? `/bundles/${bundleConfigJson.bundles.critical.file}`
288
+ : null;
289
+ if (criticalBundleUrl) {
290
+ fetch(criticalBundleUrl).catch(() => {});
271
291
  }
272
292
 
273
- if (!frameworkClasses) {
274
- const imports = await Promise.all([
275
- import('./Components/Structural/Controller/Controller.js'),
276
- import('./Components/Structural/StylesManager/StylesManager.js')
277
- ]);
278
- frameworkClasses = {
279
- Controller: imports[0].default,
280
- StylesManager: imports[1].default
281
- };
282
- }
293
+ // 4. Load framework classes.
294
+ // In production the bundler generates slice-bundle.framework.js which
295
+ // sets window.SLICE_FRAMEWORK_CLASSES. In dev mode always use individual
296
+ // imports so the live /Slice/ source is served directly without bundles.
297
+ if (resolvedMode === 'production' && bundleConfigJson?.bundles?.framework?.file) {
298
+ try {
299
+ await import(`/bundles/${bundleConfigJson.bundles.framework.file}`);
300
+ if (window.SLICE_FRAMEWORK_CLASSES) {
301
+ frameworkClasses = window.SLICE_FRAMEWORK_CLASSES;
302
+ }
303
+ } catch (e) {
304
+ // framework bundle failed — fall through to individual imports
305
+ console.error('[Slice.js] framework bundle import failed:', e?.message || e);
306
+ }
307
+ }
308
+
309
+ if (!frameworkClasses) {
310
+ try {
311
+ const imports = await Promise.all([
312
+ import('./Components/Structural/Controller/Controller.js'),
313
+ import('./Components/Structural/StylesManager/StylesManager.js')
314
+ ]);
315
+ frameworkClasses = {
316
+ Controller: imports[0].default,
317
+ StylesManager: imports[1].default
318
+ };
319
+ } catch (e) {
320
+ console.error('[Slice.js] individual imports fallback failed:', e?.message || e);
321
+ throw e;
322
+ }
323
+ }
283
324
 
325
+ // 5. Create Slice instance and set resolved mode
284
326
  window.slice = new Slice(sliceConfig, frameworkClasses);
327
+ window.slice._mode = resolvedMode;
285
328
 
286
- // Initialize bundles before building components
287
- try {
288
- const configResponse = await fetch('/bundles/bundle.config.json', { cache: 'no-store' });
289
- if (configResponse.ok) {
290
- const config = await configResponse.json();
291
- window.slice.controller.bundleConfig = config;
292
- }
329
+ // Initialize bundles before building components.
330
+ // Only in production — dev mode loads each component individually from source.
331
+ // bundleConfigJson was already fetched above (step 2); reuse it.
332
+ try {
333
+ if (resolvedMode === 'production' && bundleConfigJson) {
334
+ window.slice.controller.bundleConfig = bundleConfigJson;
335
+ }
293
336
 
294
337
  if (window.slice.controller.bundleConfig) {
295
338
  const config = window.slice.controller.bundleConfig;
296
339
 
297
- const criticalFile = config?.bundles?.critical?.file;
298
- if (criticalFile) {
299
- const criticalModule = await import(`/bundles/${criticalFile}`);
300
- if (criticalModule.SLICE_BUNDLE) {
301
- await window.slice.controller.registerBundle(criticalModule.SLICE_BUNDLE);
302
- window.slice.controller.loadedBundles.add('critical');
303
- window.slice.controller.criticalBundleLoaded = true;
340
+ const criticalFile = config?.bundles?.critical?.file;
341
+ if (criticalFile) {
342
+ // Bundle auto-registers itself on import via its own registration block.
343
+ // The block pushes its registerBundle() Promise to window.__slicePendingRegistrations
344
+ // so we can await full chunk processing before continuing to build('Loading').
345
+ // criticalBundleUrl was pre-fetched above — import() reuses the cached bytes.
346
+ await import(criticalBundleUrl);
347
+ if (window.__slicePendingRegistrations?.length) {
348
+ await Promise.all(window.__slicePendingRegistrations);
349
+ window.__slicePendingRegistrations = [];
304
350
  }
305
- }
351
+ }
306
352
 
307
353
  const routeBundles = config?.routeBundles || {};
308
354
  const initialPath = window.location.pathname || '/';
@@ -313,13 +359,10 @@ async function init() {
313
359
  if (bundleName === 'critical') continue;
314
360
  const bundleInfo = config?.bundles?.routes?.[bundleName];
315
361
  if (!bundleInfo?.file) continue;
316
- const routeModule = await import(`/bundles/${bundleInfo.file}`);
317
- if (routeModule.SLICE_BUNDLE) {
318
- await window.slice.controller.registerBundle(routeModule.SLICE_BUNDLE);
319
- window.slice.controller.loadedBundles.add(bundleName);
320
- }
321
- }
322
- };
362
+ // Bundle auto-registers itself on import.
363
+ await import(`/bundles/${bundleInfo.file}`);
364
+ }
365
+ };
323
366
 
324
367
  if (typeof requestIdleCallback === 'function') {
325
368
  requestIdleCallback(() => loadRouteBundles());
package/api/index.js CHANGED
@@ -86,6 +86,23 @@ app.use((req, res, next) => {
86
86
  }
87
87
  });
88
88
 
89
+ // ==============================================
90
+ // RUNTIME MODE ENDPOINT
91
+ // ==============================================
92
+
93
+ // Expone el modo actual al framework Slice.js en runtime.
94
+ // Solo se registra en development — 404 en production indica modo producción.
95
+ if (runMode === 'development') {
96
+ app.get('/slice-env.json', (req, res) => {
97
+ res.json({ mode: 'development' });
98
+ });
99
+ } else {
100
+ // Explicit 404 so the SPA fallback doesn't return 200 for this dev-only endpoint.
101
+ app.get('/slice-env.json', (req, res) => {
102
+ res.status(404).json({ error: 'Not found' });
103
+ });
104
+ }
105
+
89
106
  // ==============================================
90
107
  // ARCHIVOS ESTÁTICOS (DESPUÉS DE SEGURIDAD)
91
108
  // ==============================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slicejs-web-framework",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
4
4
  "description": "",
5
5
  "engines": {
6
6
  "node": ">=20"
@@ -6,200 +6,196 @@ slice-home-page {
6
6
  color: var(--font-primary-color);
7
7
  }
8
8
 
9
- /* Hero section styles */
9
+ /* ── HERO ── */
10
10
  .hero-section {
11
- position: relative;
12
11
  min-height: 80vh;
13
12
  display: flex;
14
13
  align-items: center;
15
14
  justify-content: center;
16
15
  text-align: center;
17
- padding: 2rem;
16
+ padding: 7rem 2rem 5rem;
18
17
  background-color: var(--primary-background-color);
18
+ position: relative;
19
19
  overflow: hidden;
20
20
  }
21
21
 
22
+ .hero-section::before {
23
+ content: '';
24
+ position: absolute;
25
+ top: -150px;
26
+ left: 50%;
27
+ transform: translateX(-50%);
28
+ width: 700px;
29
+ height: 700px;
30
+ background: radial-gradient(
31
+ circle,
32
+ rgba(var(--primary-color-rgb), 0.07) 0%,
33
+ transparent 70%
34
+ );
35
+ pointer-events: none;
36
+ }
37
+
22
38
  .hero-content {
23
- max-width: 800px;
39
+ max-width: 640px;
40
+ position: relative;
24
41
  z-index: 1;
25
42
  }
26
43
 
27
- .hero-logo-container {
44
+ .hero-badge {
45
+ display: inline-block;
46
+ background: rgba(var(--primary-color-rgb), 0.1);
47
+ color: var(--primary-color);
48
+ font-size: 0.75rem;
49
+ font-weight: 700;
50
+ letter-spacing: 0.08em;
51
+ text-transform: uppercase;
52
+ padding: 0.3rem 0.85rem;
53
+ border-radius: 20px;
28
54
  margin-bottom: 1.5rem;
29
55
  }
30
56
 
31
- .hero-logo {
32
- max-width: 150px;
33
- height: auto;
34
- /* Convertir el logo a una silueta */
35
- filter: brightness(0) drop-shadow(0 0 8px var(--primary-color));
36
- }
37
-
38
57
  .hero-title {
39
58
  font-size: 3rem;
40
- margin-bottom: 1rem;
59
+ font-weight: 800;
41
60
  color: var(--font-primary-color);
61
+ line-height: 1.1;
62
+ margin-bottom: 1rem;
63
+ letter-spacing: -0.03em;
42
64
  }
43
65
 
44
66
  .hero-title .highlight {
45
67
  color: var(--primary-color);
46
68
  }
47
69
 
48
- .hero-subtitle {
49
- font-size: 1.5rem;
50
- margin-bottom: 1.5rem;
70
+ .hero-sub {
71
+ font-size: 1.05rem;
51
72
  color: var(--font-secondary-color);
52
- }
53
-
54
- .hero-description {
55
- font-size: 1.1rem;
56
73
  margin-bottom: 2rem;
57
74
  line-height: 1.6;
58
75
  }
59
76
 
60
- .cta-buttons {
77
+ .hero-cta {
61
78
  display: flex;
62
- gap: 1rem;
79
+ gap: 0.75rem;
63
80
  justify-content: center;
64
- margin-top: 2rem;
81
+ margin-bottom: 2.5rem;
65
82
  }
66
83
 
67
- /* Features section styles */
84
+ .hero-chips {
85
+ display: flex;
86
+ gap: 0.5rem;
87
+ justify-content: center;
88
+ flex-wrap: wrap;
89
+ }
90
+
91
+ .hero-chip {
92
+ background: white;
93
+ border: 1px solid var(--disabled-color);
94
+ color: var(--primary-color);
95
+ font-size: 0.75rem;
96
+ font-weight: 600;
97
+ padding: 0.3rem 0.75rem;
98
+ border-radius: 20px;
99
+ box-shadow: var(--box-shadow-primary);
100
+ }
101
+
102
+ /* ── FEATURES ── */
68
103
  .features-section {
69
104
  padding: 5rem 2rem;
70
- background-color: var(--secondary-background-color);
105
+ background: white;
106
+ border-top: 1px solid var(--disabled-color);
71
107
  }
72
108
 
73
- .section-title {
109
+ .section-header {
74
110
  text-align: center;
75
- font-size: 2.5rem;
76
- margin-bottom: 3rem;
77
- color: var(--primary-color);
111
+ margin-bottom: 2.5rem;
112
+ }
113
+
114
+ .section-label {
115
+ font-size: 0.72rem;
116
+ font-weight: 700;
117
+ color: var(--accent-color);
118
+ text-transform: uppercase;
119
+ letter-spacing: 0.1em;
120
+ margin-bottom: 0.4rem;
121
+ }
122
+
123
+ .section-title {
124
+ font-size: 1.9rem;
125
+ font-weight: 800;
126
+ color: var(--font-primary-color);
127
+ letter-spacing: -0.02em;
78
128
  }
79
129
 
80
130
  .feature-grid {
81
131
  display: grid;
82
132
  grid-template-columns: repeat(3, 1fr);
83
- gap: 2rem;
84
- max-width: 1200px;
133
+ gap: 1.2rem;
134
+ max-width: 820px;
85
135
  margin: 0 auto;
86
136
  }
87
137
 
88
138
  .feature-item {
89
- padding: 2rem;
90
- background-color: var(--primary-background-color);
91
- border-radius: var(--border-radius-slice);
92
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
93
- transition: transform 0.3s ease, box-shadow 0.3s ease;
139
+ background: var(--primary-background-color);
140
+ border-radius: 10px;
141
+ padding: 1.4rem 1.2rem;
142
+ border: 1px solid var(--disabled-color);
143
+ transition: box-shadow 0.2s, transform 0.15s;
94
144
  }
95
145
 
96
146
  .feature-item:hover {
97
- transform: translateY(-5px);
98
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
147
+ box-shadow: var(--box-shadow-primary);
148
+ transform: translateY(-2px);
99
149
  }
100
150
 
101
151
  .feature-title {
102
- font-size: 1.5rem;
103
- margin-bottom: 1rem;
104
- color: var(--primary-color);
152
+ font-size: 0.9rem;
153
+ font-weight: 700;
154
+ color: var(--font-primary-color);
155
+ margin-bottom: 0.35rem;
105
156
  }
106
157
 
107
158
  .feature-description {
108
- color: var(--font-secondary-color);
109
- line-height: 1.6;
159
+ font-size: 0.78rem;
160
+ color: var(--medium-color);
161
+ line-height: 1.5;
110
162
  }
111
163
 
112
- /* Example section styles */
113
- .example-section {
164
+ /* ── SHOWCASE ── */
165
+ .showcase-section {
114
166
  padding: 5rem 2rem;
115
- background-color: var(--primary-background-color);
167
+ background: var(--primary-background-color);
168
+ border-top: 1px solid var(--disabled-color);
116
169
  }
117
170
 
118
- .section-description {
119
- text-align: center;
120
- max-width: 800px;
121
- margin: 0 auto 3rem auto;
122
- color: var(--font-secondary-color);
123
- font-size: 1.2rem;
124
- }
125
-
126
- .examples-container {
171
+ .showcase-grid {
127
172
  display: grid;
128
- grid-template-columns: repeat(2, 1fr);
129
- gap: 3rem;
130
- max-width: 1000px;
131
- margin: 0 auto;
132
- }
133
-
134
- .example-item {
135
- display: flex;
136
- flex-direction: column;
173
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
137
174
  gap: 1rem;
138
- padding: 2rem;
139
- background-color: var(--secondary-background-color);
140
- border-radius: var(--border-radius-slice);
141
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
142
- }
143
-
144
- .example-item h3 {
145
- margin: 0;
146
- font-size: 1.3rem;
147
- color: var(--primary-color);
175
+ max-width: 860px;
176
+ margin: 0 auto;
148
177
  }
149
178
 
150
- /* Getting started section styles */
151
- .getting-started-section {
152
- padding: 5rem 2rem;
153
- background-color: var(--secondary-background-color);
154
- text-align: center;
179
+ .comp-card {
180
+ background: white;
181
+ border-radius: 10px;
182
+ padding: 1.3rem 1.2rem;
183
+ border: 1px solid var(--disabled-color);
184
+ box-shadow: 0 2px 8px rgba(var(--primary-color-rgb), 0.06);
155
185
  }
156
186
 
157
- .code-sample {
158
- max-width: 800px;
159
- margin: 0 auto;
160
- text-align: left;
187
+ .comp-label {
188
+ font-size: 0.65rem;
189
+ font-weight: 700;
190
+ color: var(--medium-color);
191
+ text-transform: uppercase;
192
+ letter-spacing: 0.1em;
193
+ margin-bottom: 0.75rem;
161
194
  }
162
195
 
163
- /* Footer styles */
164
- .home-footer {
165
- padding: 3rem 2rem;
166
- text-align: center;
167
- background-color: var(--primary-color);
168
- color: var(--primary-color-contrast);
196
+ .comp-demo {
197
+ display: flex;
198
+ align-items: center;
199
+ gap: 0.6rem;
200
+ flex-wrap: wrap;
169
201
  }
170
-
171
- .copyright {
172
- margin-top: 1rem;
173
- font-size: 0.9rem;
174
- opacity: 0.8;
175
- }
176
-
177
- /* Responsive styles */
178
- @media (max-width: 992px) {
179
- .feature-grid {
180
- grid-template-columns: repeat(2, 1fr);
181
- }
182
-
183
- .examples-container {
184
- grid-template-columns: 1fr;
185
- }
186
- }
187
-
188
- @media (max-width: 768px) {
189
- .hero-title {
190
- font-size: 2.5rem;
191
- }
192
-
193
- .hero-subtitle {
194
- font-size: 1.3rem;
195
- }
196
-
197
- .feature-grid {
198
- grid-template-columns: 1fr;
199
- }
200
-
201
- .cta-buttons {
202
- flex-direction: column;
203
- align-items: center;
204
- }
205
- }
@@ -1,49 +1,37 @@
1
1
  <div class="home-page-container">
2
- <div class="hero-section">
2
+
3
+ <section class="hero-section">
3
4
  <div class="hero-content">
4
- <div class="hero-logo-container">
5
- <img src="/images/Slice.js-logo.png" alt="Slice.js Logo" class="hero-logo" />
5
+ <div class="hero-badge">v2.4 · Vanilla JS Framework</div>
6
+ <h1 class="hero-title">Build your web app<br>one <span class="highlight">Slice</span> at a time</h1>
7
+ <p class="hero-sub">A lightweight, component-based framework for building web applications with vanilla JavaScript and web standards.</p>
8
+ <div class="hero-cta"></div>
9
+ <div class="hero-chips">
10
+ <span class="hero-chip">Button</span>
11
+ <span class="hero-chip">Input</span>
12
+ <span class="hero-chip">Switch</span>
13
+ <span class="hero-chip">Navbar</span>
14
+ <span class="hero-chip">Grid</span>
15
+ <span class="hero-chip">Card</span>
16
+ <span class="hero-chip">+ more</span>
6
17
  </div>
7
- <h1 class="hero-title">Welcome to <span class="highlight">Slice.js</span></h1>
8
- <p class="hero-subtitle">Build Your Web App One Slice at a Time</p>
9
- <p class="hero-description">
10
- A modern, lightweight component-based framework for building web applications using vanilla JavaScript and web standards.
11
- </p>
12
- <div class="cta-buttons"></div>
13
18
  </div>
14
- </div>
19
+ </section>
15
20
 
16
21
  <section class="features-section">
17
- <h2 class="section-title">Why Choose Slice.js?</h2>
22
+ <div class="section-header">
23
+ <p class="section-label">Why Slice.js</p>
24
+ <h2 class="section-title">Everything you need, nothing you don't</h2>
25
+ </div>
18
26
  <div class="feature-grid"></div>
19
27
  </section>
20
28
 
21
- <section class="example-section">
22
- <h2 class="section-title">Component Showcase</h2>
23
- <p class="section-description">
24
- Explore some of the ready-to-use components that come with Slice.js
25
- </p>
26
- <div class="examples-container"></div>
27
- </section>
28
-
29
- <section class="getting-started-section">
30
- <h2 class="section-title">Get Started Now</h2>
31
- <div class="code-sample">
32
- <pre><code>
33
- // Initialize a new Slice.js project
34
- npm run slice:init
35
-
36
- // Create a new component
37
- npm run slice:create
38
-
39
- // Start your application
40
- npm run slice:start
41
- </code></pre>
29
+ <section class="showcase-section">
30
+ <div class="section-header">
31
+ <p class="section-label">Component Showcase</p>
32
+ <h2 class="section-title">Ready-to-use components</h2>
42
33
  </div>
34
+ <div class="showcase-grid"></div>
43
35
  </section>
44
36
 
45
- <footer class="home-footer">
46
- <p>Made with ❤️ using Slice.js</p>
47
- <p class="copyright">© 2025 Slice.js Framework</p>
48
- </footer>
49
- </div>
37
+ </div>
@@ -2,15 +2,20 @@ export default class HomePage extends HTMLElement {
2
2
  constructor(props) {
3
3
  super();
4
4
  slice.attachTemplate(this);
5
-
6
- this.$examplesContainer = this.querySelector('.examples-container');
7
-
8
5
  slice.controller.setComponentProps(this, props);
9
6
  this.debuggerProps = [];
10
7
  }
11
8
 
12
9
  async init() {
13
- // Crear la barra de navegación
10
+ await Promise.all([
11
+ this._buildNavbar(),
12
+ this._buildHeroCta(),
13
+ this._buildFeatures(),
14
+ this._buildShowcase(),
15
+ ]);
16
+ }
17
+
18
+ async _buildNavbar() {
14
19
  const navbar = await slice.build('Navbar', {
15
20
  position: 'fixed',
16
21
  logo: {
@@ -20,177 +25,186 @@ export default class HomePage extends HTMLElement {
20
25
  items: [
21
26
  { text: 'Home', path: '/' },
22
27
  { text: 'Playground', path: '/Playground' },
23
-
24
28
  ],
25
29
  buttons: [
26
30
  {
27
31
  value: 'Change Theme',
28
32
  onClickCallback: async () => {
29
- const currentTheme = slice.stylesManager.themeManager.currentTheme;
30
- if (currentTheme === 'Slice') {
31
- await slice.setTheme('Light');
32
- } else if (currentTheme === 'Light') {
33
- await slice.setTheme('Dark');
34
- } else {
35
- await slice.setTheme('Slice');
36
- }
33
+ const current = slice.stylesManager.themeManager.currentTheme;
34
+ const next = current === 'Slice' ? 'Light'
35
+ : current === 'Light' ? 'Dark'
36
+ : 'Slice';
37
+ await slice.setTheme(next);
37
38
  },
38
39
  },
39
40
  ],
40
41
  });
41
-
42
- // Crear botones para la sección de llamada a la acción
43
- const docsButton = await slice.build('Button', {
42
+ this.insertBefore(navbar, this.firstChild);
43
+ }
44
+
45
+ async _buildHeroCta() {
46
+ const docsBtn = await slice.build('Button', {
44
47
  value: 'Documentation',
45
- onClickCallback: () => //redirect to https://slice-js-docs.vercel.app/Documentation
46
- window.open('https://slice-js-docs.vercel.app/Documentation', '_blank'),
48
+ onClickCallback: () =>
49
+ window.open('https://slice-js-docs.vercel.app/Documentation', '_blank'),
47
50
  customColor: {
48
51
  button: 'var(--primary-color)',
49
- label: 'var(--primary-color-contrast)'
50
- }
52
+ label: 'var(--primary-color-contrast)',
53
+ },
51
54
  });
52
-
53
- const componentsButton = await slice.build('Button', {
55
+
56
+ const componentsBtn = await slice.build('Button', {
54
57
  value: 'Components Library',
55
- onClickCallback: () => window.open('https://slice-js-docs.vercel.app/Documentation/Visual', '_blank'),
58
+ onClickCallback: () =>
59
+ window.open('https://slice-js-docs.vercel.app/Documentation/Visual', '_blank'),
56
60
  customColor: {
57
- button: 'var(--secondary-color)',
58
- label: 'var(--secondary-color-contrast)'
59
- }
61
+ button: 'var(--secondary-background-color)',
62
+ label: 'var(--primary-color)',
63
+ },
60
64
  });
61
-
62
- // Añadir botones a la sección CTA
63
- this.querySelector('.cta-buttons').appendChild(docsButton);
64
- this.querySelector('.cta-buttons').appendChild(componentsButton);
65
-
66
- // Crear features section con un enfoque diferente (sin usar Cards)
67
- await this.createFeatures();
68
-
69
- // Crear ejemplos de componentes
70
- await this.createComponentExamples();
71
-
72
- // Configurar la sección de código de inicio
73
- await this.setupGettingStartedSection();
74
-
75
- // Añadir la barra de navegación al inicio del componente
76
- this.insertBefore(navbar, this.firstChild);
65
+
66
+ const cta = this.querySelector('.hero-cta');
67
+ cta.appendChild(docsBtn);
68
+ cta.appendChild(componentsBtn);
77
69
  }
78
-
79
- async createFeatures() {
80
- // Definir características
70
+
71
+ async _buildFeatures() {
81
72
  const features = [
82
73
  {
83
74
  title: 'Component-Based',
84
- description: 'Build your app using modular, reusable components following web standards.'
75
+ description: 'Build your app using modular, reusable components following web standards.',
85
76
  },
86
77
  {
87
- title: 'Zero Dependencies',
88
- description: 'Built with vanilla JavaScript. No external libraries required.'
78
+ title: 'Themeable',
79
+ description: 'Swap themes at runtime. Ships with Slice, Light, Dark and more.',
89
80
  },
90
81
  {
91
- title: 'Easy Routing',
92
- description: 'Simple and powerful routing system for single page applications.'
82
+ title: 'Lightweight',
83
+ description: 'No heavy runtime. Just vanilla JavaScript and web standards.',
93
84
  },
94
85
  {
95
- title: 'Theme System',
96
- description: 'Built-in theme support with easy customization through CSS variables.'
86
+ title: 'Built-in Router',
87
+ description: 'Client-side routing with MultiRoute no extra libraries needed.',
97
88
  },
98
89
  {
99
- title: 'Developer Tools',
100
- description: 'Integrated debugging and logging for faster development.'
90
+ title: 'CLI Tools',
91
+ description: 'Scaffold projects, create components and build bundles from the command line.',
101
92
  },
102
93
  {
103
- title: 'Performance Focused',
104
- description: 'Lightweight and optimized for fast loading and execution.'
105
- }
94
+ title: 'Services',
95
+ description: 'Built-in FetchManager, LocalStorage and IndexedDB integrations.',
96
+ },
106
97
  ];
107
-
108
- const featureGrid = this.querySelector('.feature-grid');
109
-
110
- // Crear y añadir cada feature como un elemento HTML simple
111
- for (const feature of features) {
112
- const featureElement = document.createElement('div');
113
- featureElement.classList.add('feature-item');
114
-
115
- const featureTitle = document.createElement('h3');
116
- featureTitle.textContent = feature.title;
117
- featureTitle.classList.add('feature-title');
118
-
119
- const featureDescription = document.createElement('p');
120
- featureDescription.textContent = feature.description;
121
- featureDescription.classList.add('feature-description');
122
-
123
- featureElement.appendChild(featureTitle);
124
- featureElement.appendChild(featureDescription);
125
-
126
- featureGrid.appendChild(featureElement);
98
+
99
+ const grid = this.querySelector('.feature-grid');
100
+ for (const { title, description } of features) {
101
+ const item = document.createElement('div');
102
+ item.classList.add('feature-item');
103
+
104
+ const h3 = document.createElement('h3');
105
+ h3.classList.add('feature-title');
106
+ h3.textContent = title;
107
+
108
+ const p = document.createElement('p');
109
+ p.classList.add('feature-description');
110
+ p.textContent = description;
111
+
112
+ item.appendChild(h3);
113
+ item.appendChild(p);
114
+ grid.appendChild(item);
127
115
  }
128
116
  }
129
-
130
- async createComponentExamples() {
131
- // Crear ejemplos para demostrar componentes
132
- const inputExample = await slice.build('Input', {
133
- placeholder: 'Try typing here...',
134
- type: 'text'
117
+
118
+ async _buildShowcase() {
119
+ const grid = this.querySelector('.showcase-grid');
120
+
121
+ // Helper: wrap built components in a labeled card and append to grid
122
+ const addCard = (label, ...components) => {
123
+ const card = document.createElement('div');
124
+ card.classList.add('comp-card');
125
+
126
+ const labelEl = document.createElement('p');
127
+ labelEl.classList.add('comp-label');
128
+ labelEl.textContent = label;
129
+
130
+ const demo = document.createElement('div');
131
+ demo.classList.add('comp-demo');
132
+ components.forEach(c => demo.appendChild(c));
133
+
134
+ card.appendChild(labelEl);
135
+ card.appendChild(demo);
136
+ grid.appendChild(card);
137
+ };
138
+
139
+ // Button — primary + secondary variants
140
+ const [btnPrimary, btnSecondary] = await Promise.all([
141
+ slice.build('Button', {
142
+ value: 'Primary',
143
+ onClickCallback: () => {},
144
+ customColor: {
145
+ button: 'var(--primary-color)',
146
+ label: 'var(--primary-color-contrast)',
147
+ },
148
+ }),
149
+ slice.build('Button', {
150
+ value: 'Secondary',
151
+ onClickCallback: () => {},
152
+ customColor: {
153
+ button: 'var(--secondary-background-color)',
154
+ label: 'var(--primary-color)',
155
+ },
156
+ }),
157
+ ]);
158
+ addCard('Button', btnPrimary, btnSecondary);
159
+
160
+ // Input
161
+ const input = await slice.build('Input', {
162
+ placeholder: 'Type something...',
163
+ type: 'text',
135
164
  });
136
-
137
- const switchExample = await slice.build('Switch', {
165
+ addCard('Input', input);
166
+
167
+ // Switch
168
+ const sw = await slice.build('Switch', {
138
169
  label: 'Toggle me',
139
- checked: true
140
- });
141
-
142
- const checkboxExample = await slice.build('Checkbox', {
143
- label: 'Check me',
144
- labelPlacement: 'right'
170
+ checked: true,
145
171
  });
146
-
147
- const detailsExample = await slice.build('Details', {
148
- title: 'Click to expand',
149
- text: 'This is a collapsible details component that can contain any content.'
150
- });
151
-
152
- // Crear sección para cada ejemplo
153
- const exampleSections = [
154
- { title: 'Input Component', component: inputExample },
155
- { title: 'Switch Component', component: switchExample },
156
- { title: 'Checkbox Component', component: checkboxExample },
157
- { title: 'Details Component', component: detailsExample }
172
+ addCard('Switch', sw);
173
+
174
+ // Select theme chooser
175
+ const themeOptions = [
176
+ { name: 'Slice Theme' },
177
+ { name: 'Light Theme' },
178
+ { name: 'Dark Theme' },
179
+ { name: 'Purple Theme' },
158
180
  ];
159
-
160
- // Añadir cada ejemplo a la sección de ejemplos
161
- for (const section of exampleSections) {
162
- const container = document.createElement('div');
163
- container.classList.add('example-item');
164
-
165
- const title = document.createElement('h3');
166
- title.textContent = section.title;
167
-
168
- container.appendChild(title);
169
- container.appendChild(section.component);
170
-
171
- this.$examplesContainer.appendChild(container);
172
- }
173
- }
174
-
175
- async setupGettingStartedSection() {
176
- // Opcionalmente podríamos mejorar esta sección usando el CodeVisualizer component
177
- // en lugar del código HTML estático en el template
178
- const codeVisualizer = await slice.build('CodeVisualizer', {
179
- value: `// Initialize a new Slice.js project
180
- npm run slice:init
181
-
182
- // Create a new component
183
- npm run slice:create
184
-
185
- // Start your application
186
- npm run slice:start`,
187
- language: 'bash'
181
+ const select = await slice.build('Select', {
182
+ label: 'Pick a theme',
183
+ options: themeOptions,
184
+ visibleProp: 'name',
185
+ onOptionSelect: async (option) => {
186
+ if (!option) return;
187
+ const themeName = option.name.replace(' Theme', '');
188
+ await slice.setTheme(themeName);
189
+ },
190
+ });
191
+ addCard('Select', select);
192
+
193
+ // Loading — triggered by a demo button (Loading appends to document.body)
194
+ const loading = await slice.build('Loading', {});
195
+ const demoBtn = await slice.build('Button', {
196
+ value: 'Demo Loading',
197
+ onClickCallback: () => {
198
+ loading.start();
199
+ setTimeout(() => loading.stop(), 1500);
200
+ },
201
+ customColor: {
202
+ button: 'var(--primary-color)',
203
+ label: 'var(--primary-color-contrast)',
204
+ },
188
205
  });
189
-
190
- const codeSample = this.querySelector('.code-sample');
191
- codeSample.innerHTML = ''; // Clear the static code sample
192
- codeSample.appendChild(codeVisualizer);
206
+ addCard('Loading', demoBtn);
193
207
  }
194
208
  }
195
209
 
196
- customElements.define('slice-home-page', HomePage);
210
+ customElements.define('slice-home-page', HomePage);