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.
- package/Slice/Components/Structural/Controller/Controller.js +11 -2
- package/Slice/Components/Structural/StylesManager/StylesManager.js +2 -1
- package/Slice/Slice.js +93 -50
- package/api/index.js +17 -0
- package/package.json +1 -1
- package/src/Components/AppComponents/HomePage/HomePage.css +121 -125
- package/src/Components/AppComponents/HomePage/HomePage.html +25 -37
- package/src/Components/AppComponents/HomePage/HomePage.js +151 -137
|
@@ -295,9 +295,18 @@ export default class Controller {
|
|
|
295
295
|
return Promise.resolve(false);
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
@@ -6,200 +6,196 @@ slice-home-page {
|
|
|
6
6
|
color: var(--font-primary-color);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
/*
|
|
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:
|
|
39
|
+
max-width: 640px;
|
|
40
|
+
position: relative;
|
|
24
41
|
z-index: 1;
|
|
25
42
|
}
|
|
26
43
|
|
|
27
|
-
.hero-
|
|
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
|
-
|
|
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-
|
|
49
|
-
font-size: 1.
|
|
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
|
|
77
|
+
.hero-cta {
|
|
61
78
|
display: flex;
|
|
62
|
-
gap:
|
|
79
|
+
gap: 0.75rem;
|
|
63
80
|
justify-content: center;
|
|
64
|
-
margin-
|
|
81
|
+
margin-bottom: 2.5rem;
|
|
65
82
|
}
|
|
66
83
|
|
|
67
|
-
|
|
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
|
|
105
|
+
background: white;
|
|
106
|
+
border-top: 1px solid var(--disabled-color);
|
|
71
107
|
}
|
|
72
108
|
|
|
73
|
-
.section-
|
|
109
|
+
.section-header {
|
|
74
110
|
text-align: center;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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:
|
|
133
|
+
gap: 1.2rem;
|
|
134
|
+
max-width: 820px;
|
|
85
135
|
margin: 0 auto;
|
|
86
136
|
}
|
|
87
137
|
|
|
88
138
|
.feature-item {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
transition:
|
|
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
|
-
|
|
98
|
-
|
|
147
|
+
box-shadow: var(--box-shadow-primary);
|
|
148
|
+
transform: translateY(-2px);
|
|
99
149
|
}
|
|
100
150
|
|
|
101
151
|
.feature-title {
|
|
102
|
-
font-size:
|
|
103
|
-
|
|
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
|
-
|
|
109
|
-
|
|
159
|
+
font-size: 0.78rem;
|
|
160
|
+
color: var(--medium-color);
|
|
161
|
+
line-height: 1.5;
|
|
110
162
|
}
|
|
111
163
|
|
|
112
|
-
/*
|
|
113
|
-
.
|
|
164
|
+
/* ── SHOWCASE ── */
|
|
165
|
+
.showcase-section {
|
|
114
166
|
padding: 5rem 2rem;
|
|
115
|
-
background
|
|
167
|
+
background: var(--primary-background-color);
|
|
168
|
+
border-top: 1px solid var(--disabled-color);
|
|
116
169
|
}
|
|
117
170
|
|
|
118
|
-
.
|
|
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(
|
|
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
|
-
|
|
139
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
2
|
+
|
|
3
|
+
<section class="hero-section">
|
|
3
4
|
<div class="hero-content">
|
|
4
|
-
<div class="hero-
|
|
5
|
-
|
|
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
|
-
</
|
|
19
|
+
</section>
|
|
15
20
|
|
|
16
21
|
<section class="features-section">
|
|
17
|
-
<
|
|
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="
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
43
|
-
|
|
42
|
+
this.insertBefore(navbar, this.firstChild);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async _buildHeroCta() {
|
|
46
|
+
const docsBtn = await slice.build('Button', {
|
|
44
47
|
value: 'Documentation',
|
|
45
|
-
onClickCallback: () =>
|
|
46
|
-
|
|
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
|
|
55
|
+
|
|
56
|
+
const componentsBtn = await slice.build('Button', {
|
|
54
57
|
value: 'Components Library',
|
|
55
|
-
onClickCallback: () =>
|
|
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(--
|
|
59
|
-
}
|
|
61
|
+
button: 'var(--secondary-background-color)',
|
|
62
|
+
label: 'var(--primary-color)',
|
|
63
|
+
},
|
|
60
64
|
});
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
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: '
|
|
88
|
-
description: '
|
|
78
|
+
title: 'Themeable',
|
|
79
|
+
description: 'Swap themes at runtime. Ships with Slice, Light, Dark and more.',
|
|
89
80
|
},
|
|
90
81
|
{
|
|
91
|
-
title: '
|
|
92
|
-
description: '
|
|
82
|
+
title: 'Lightweight',
|
|
83
|
+
description: 'No heavy runtime. Just vanilla JavaScript and web standards.',
|
|
93
84
|
},
|
|
94
85
|
{
|
|
95
|
-
title: '
|
|
96
|
-
description: '
|
|
86
|
+
title: 'Built-in Router',
|
|
87
|
+
description: 'Client-side routing with MultiRoute — no extra libraries needed.',
|
|
97
88
|
},
|
|
98
89
|
{
|
|
99
|
-
title: '
|
|
100
|
-
description: '
|
|
90
|
+
title: 'CLI Tools',
|
|
91
|
+
description: 'Scaffold projects, create components and build bundles from the command line.',
|
|
101
92
|
},
|
|
102
93
|
{
|
|
103
|
-
title: '
|
|
104
|
-
description: '
|
|
105
|
-
}
|
|
94
|
+
title: 'Services',
|
|
95
|
+
description: 'Built-in FetchManager, LocalStorage and IndexedDB integrations.',
|
|
96
|
+
},
|
|
106
97
|
];
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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);
|