@bebranded/bb-contents 1.0.56-beta → 1.0.58-beta

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 (2) hide show
  1. package/bb-contents.js +155 -240
  2. package/package.json +1 -1
package/bb-contents.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * BeBranded Contents
3
3
  * Contenus additionnels français pour Webflow
4
- * @version 1.0.56-beta
4
+ * @version 1.0.58-beta
5
5
  * @author BeBranded
6
6
  * @license MIT
7
7
  * @website https://www.bebranded.xyz
@@ -28,7 +28,7 @@
28
28
 
29
29
  // Configuration
30
30
  const config = {
31
- version: '1.0.56-beta',
31
+ version: '1.0.58-beta',
32
32
  debug: false, // Debug désactivé
33
33
  prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
34
34
  youtubeEndpoint: null, // URL du worker YouTube (à définir par l'utilisateur)
@@ -240,250 +240,180 @@
240
240
 
241
241
  // Modules
242
242
  bbContents.modules = {
243
- // Module Marquee - Version live 1.0.41-beta avec modules parasites supprimés
243
+ // Module Marquee - Version 1.0.37-beta robuste avec attente window.load et vérification images
244
244
  marquee: {
245
- detect: function(scope) {
246
- const s = scope || document;
245
+ detect: function(scope) {
246
+ const s = scope || document;
247
247
  return s.querySelector(bbContents._attrSelector('marquee')) !== null;
248
248
  },
249
249
 
250
250
  // Nouvelle méthode pour vérifier les éléments échoués
251
251
  checkFailed: function(scope) {
252
- const s = scope || document;
252
+ const s = scope || document;
253
253
  const failedElements = s.querySelectorAll('[bb-marquee]:not([data-bb-marquee-processed])');
254
254
  return failedElements.length > 0;
255
- },
256
-
257
- init: function(root) {
258
- const scope = root || document;
259
- if (scope.closest && scope.closest('[data-bb-disable]')) return;
260
- const elements = scope.querySelectorAll(bbContents._attrSelector('marquee'));
261
-
262
- // Debug: Log du nombre d'éléments marquee trouvés
263
- console.log(`[bb-contents] Marquee init: ${elements.length} éléments trouvés`);
255
+ },
256
+
257
+ init: function(root) {
258
+ const scope = root || document;
259
+ if (scope.closest && scope.closest('[data-bb-disable]')) return;
260
+ const elements = scope.querySelectorAll(bbContents._attrSelector('marquee'));
264
261
 
265
- elements.forEach(function(element, index) {
266
- // Debug: Log de chaque élément
267
- console.log(`[bb-contents] Marquee ${index + 1}:`, {
268
- element: element,
269
- alreadyProcessed: element.bbProcessed,
270
- hasYouTubeProcessed: element.hasAttribute('data-bb-youtube-processed'),
271
- hasMarqueeProcessed: element.hasAttribute('data-bb-marquee-processed'),
272
- attributes: {
273
- speed: element.getAttribute('bb-marquee-speed'),
274
- direction: element.getAttribute('bb-marquee-direction'),
275
- orientation: element.getAttribute('bb-marquee-orientation'),
276
- height: element.getAttribute('bb-marquee-height')
277
- }
278
- });
262
+ elements.forEach(function(element) {
279
263
  // Vérifier si l'élément a déjà été traité par un autre module
280
264
  if (element.bbProcessed || element.hasAttribute('data-bb-youtube-processed')) {
281
- // Élément marquee déjà traité par un autre module, ignoré
282
- console.log(`[bb-contents] Marquee ${index + 1}: IGNORÉ (déjà traité)`);
265
+ bbContents.utils.log('Élément marquee déjà traité par un autre module, ignoré:', element);
283
266
  return;
284
267
  }
285
- element.bbProcessed = true;
286
- console.log(`[bb-contents] Marquee ${index + 1}: TRAITEMENT EN COURS`);
268
+ element.bbProcessed = true;
287
269
 
288
- // Récupérer les options
270
+ // Récupérer les options
289
271
  const speed = bbContents._getAttr(element, 'bb-marquee-speed') || '100';
290
272
  const direction = bbContents._getAttr(element, 'bb-marquee-direction') || 'left';
273
+ const pauseOnHover = bbContents._getAttr(element, 'bb-marquee-pause') || 'true';
291
274
  const gap = bbContents._getAttr(element, 'bb-marquee-gap') || '50';
292
275
  const orientation = bbContents._getAttr(element, 'bb-marquee-orientation') || 'horizontal';
293
276
  const height = bbContents._getAttr(element, 'bb-marquee-height') || '300';
294
277
  const minHeight = bbContents._getAttr(element, 'bb-marquee-min-height');
295
278
 
296
- // Sauvegarder le contenu original
297
- const originalHTML = element.innerHTML;
298
-
299
- // Créer le conteneur principal
300
- const mainContainer = document.createElement('div');
301
- const isVertical = orientation === 'vertical';
279
+ // Sauvegarder le contenu original
280
+ const originalHTML = element.innerHTML;
281
+
282
+ // Créer le conteneur principal
283
+ const mainContainer = document.createElement('div');
284
+ const isVertical = orientation === 'vertical';
302
285
  const useAutoHeight = isVertical && height === 'auto';
303
286
 
304
- mainContainer.style.cssText = `
305
- position: relative;
306
- width: 100%;
287
+ mainContainer.style.cssText = `
288
+ position: relative;
289
+ width: 100%;
307
290
  height: ${isVertical ? (height === 'auto' ? 'auto' : height + 'px') : 'auto'};
308
- overflow: hidden;
309
- min-height: ${isVertical ? '100px' : '50px'};
291
+ overflow: hidden;
292
+ min-height: ${isVertical ? '100px' : '50px'};
310
293
  ${minHeight ? `min-height: ${minHeight};` : ''}
311
- `;
294
+ `;
312
295
 
313
- // Créer le conteneur de défilement
314
- const scrollContainer = document.createElement('div');
315
- scrollContainer.style.cssText = `
296
+ // Créer le conteneur de défilement
297
+ const scrollContainer = document.createElement('div');
298
+ scrollContainer.style.cssText = `
316
299
  ${useAutoHeight ? 'position: relative;' : 'position: absolute;'}
317
- will-change: transform;
300
+ will-change: transform;
318
301
  ${useAutoHeight ? '' : 'height: 100%; top: 0px; left: 0px;'}
319
- display: flex;
320
- ${isVertical ? 'flex-direction: column;' : ''}
321
- align-items: center;
322
- gap: ${gap}px;
323
- ${isVertical ? '' : 'white-space: nowrap;'}
324
- flex-shrink: 0;
325
- transition: transform 0.1s ease-out;
326
- `;
327
-
328
- // Créer le bloc de contenu principal
329
- const mainBlock = document.createElement('div');
330
- mainBlock.innerHTML = originalHTML;
331
- mainBlock.style.cssText = `
332
- display: flex;
333
- ${isVertical ? 'flex-direction: column;' : ''}
334
- align-items: center;
335
- gap: ${gap}px;
336
- ${isVertical ? '' : 'white-space: nowrap;'}
337
- flex-shrink: 0;
338
- ${isVertical ? 'min-height: 100px;' : ''}
339
- `;
302
+ display: flex;
303
+ ${isVertical ? 'flex-direction: column;' : ''}
304
+ align-items: center;
305
+ gap: ${gap}px;
306
+ ${isVertical ? '' : 'white-space: nowrap;'}
307
+ flex-shrink: 0;
308
+ `;
340
309
 
341
- // Créer plusieurs répétitions pour un défilement continu
342
- const repeatBlock1 = mainBlock.cloneNode(true);
343
- const repeatBlock2 = mainBlock.cloneNode(true);
344
- const repeatBlock3 = mainBlock.cloneNode(true);
345
-
346
- // Assembler la structure
347
- scrollContainer.appendChild(mainBlock);
348
- scrollContainer.appendChild(repeatBlock1);
349
- scrollContainer.appendChild(repeatBlock2);
350
- scrollContainer.appendChild(repeatBlock3);
351
- mainContainer.appendChild(scrollContainer);
352
-
353
- // Vider et remplacer le contenu original
354
- element.innerHTML = '';
355
- element.appendChild(mainContainer);
310
+ // Créer le bloc de contenu principal
311
+ const mainBlock = document.createElement('div');
312
+ mainBlock.innerHTML = originalHTML;
313
+ mainBlock.style.cssText = `
314
+ display: flex;
315
+ ${isVertical ? 'flex-direction: column;' : ''}
316
+ align-items: center;
317
+ gap: ${gap}px;
318
+ ${isVertical ? '' : 'white-space: nowrap;'}
319
+ flex-shrink: 0;
320
+ ${isVertical ? 'min-height: 100px;' : ''}
321
+ `;
356
322
 
323
+ // Créer plusieurs répétitions pour un défilement continu
324
+ const repeatBlock1 = mainBlock.cloneNode(true);
325
+ const repeatBlock2 = mainBlock.cloneNode(true);
326
+ const repeatBlock3 = mainBlock.cloneNode(true);
327
+
328
+ // Assembler la structure
329
+ scrollContainer.appendChild(mainBlock);
330
+ scrollContainer.appendChild(repeatBlock1);
331
+ scrollContainer.appendChild(repeatBlock2);
332
+ scrollContainer.appendChild(repeatBlock3);
333
+ mainContainer.appendChild(scrollContainer);
334
+
335
+ // Vider et remplacer le contenu original
336
+ element.innerHTML = '';
337
+ element.appendChild(mainContainer);
338
+
357
339
  // Marquer l'élément comme traité par le module marquee
358
340
  element.setAttribute('data-bb-marquee-processed', 'true');
359
341
 
360
- // Fonction pour initialiser l'animation avec vérification robuste des dimensions
342
+ // Fonction pour initialiser l'animation avec retry amélioré - Version 1.0.37-beta robuste
361
343
  const initAnimation = (retryCount = 0) => {
362
- console.log(`[bb-contents] Marquee ${index + 1}: initAnimation tentative ${retryCount + 1}`);
363
-
364
- // Vérifier que les images sont chargées
365
- const images = mainBlock.querySelectorAll('img');
366
-
367
- // Forcer le chargement des images lazy loading
368
- images.forEach(img => {
369
- if (img.loading === 'lazy' || img.hasAttribute('data-src')) {
370
- // Forcer le chargement de l'image lazy
371
- if (img.hasAttribute('data-src')) {
372
- img.src = img.getAttribute('data-src');
373
- }
374
- img.loading = 'eager';
375
- }
376
- });
377
-
378
- const imagesLoaded = Array.from(images).every(img => img.complete && img.naturalHeight > 0);
379
-
380
- console.log(`[bb-contents] Marquee ${index + 1}: Images chargées: ${imagesLoaded} (${images.length} images)`);
381
-
382
- // Attendre que le contenu soit dans le DOM et que les images soient chargées
383
- requestAnimationFrame(() => {
384
- // Calcul plus robuste des dimensions
385
- const rect = mainBlock.getBoundingClientRect();
386
- const contentWidth = rect.width || mainBlock.offsetWidth;
387
- const contentHeight = rect.height || mainBlock.offsetHeight;
344
+ // Attendre que le contenu soit dans le DOM
345
+ requestAnimationFrame(() => {
346
+ const contentWidth = mainBlock.offsetWidth;
347
+ const contentHeight = mainBlock.offsetHeight;
388
348
 
389
- console.log(`[bb-contents] Marquee ${index + 1}: Dimensions calculées:`, {
390
- rect: rect,
391
- contentWidth: contentWidth,
392
- contentHeight: contentHeight,
393
- isVertical: isVertical
394
- });
349
+ // Debug amélioré
350
+ bbContents.utils.log('Debug - Largeur du contenu:', contentWidth, 'px', 'Hauteur:', contentHeight, 'px', 'Enfants:', mainBlock.children.length, 'Vertical:', isVertical, 'Direction:', direction, 'Tentative:', retryCount + 1);
395
351
 
396
- // Pour les marquees verticaux, utiliser la largeur du parent si nécessaire
397
- let finalWidth = contentWidth;
398
- let finalHeight = contentHeight;
352
+ // Vérifier que les images sont chargées
353
+ const images = mainBlock.querySelectorAll('img');
354
+ const imagesLoaded = Array.from(images).every(img => img.complete && img.naturalHeight > 0);
399
355
 
400
- if (isVertical && contentWidth < 10) {
401
- // Si largeur trop petite, utiliser la largeur du parent
402
- const parentRect = mainBlock.parentElement.getBoundingClientRect();
403
- finalWidth = parentRect.width || mainBlock.parentElement.offsetWidth;
404
- // Largeur corrigée pour marquee vertical
356
+ // Si pas de contenu, réessayer avec délai progressif
357
+ if ((isVertical && contentHeight === 0) || (!isVertical && contentWidth === 0)) {
358
+ if (retryCount < 8) { // Plus de tentatives
359
+ bbContents.utils.log('Contenu non prêt, nouvelle tentative dans', (200 + retryCount * 100), 'ms');
360
+ setTimeout(() => initAnimation(retryCount + 1), 200 + retryCount * 100);
361
+ return;
362
+ } else {
363
+ bbContents.utils.log('Échec d\'initialisation après 8 tentatives');
364
+ return;
365
+ }
405
366
  }
406
367
 
407
- // Debug supprimé pour console propre
408
-
409
- // Vérifications robustes avant initialisation
410
- const hasValidDimensions = (isVertical && finalHeight > 50) || (!isVertical && finalWidth > 50);
411
- const hasContent = mainBlock.innerHTML.trim().length > 0;
412
- const maxRetries = 8; // Plus de tentatives pour attendre les images
413
-
414
- // Fallback: si pas de dimensions valides mais qu'il y a du contenu, forcer l'initialisation
415
- const shouldForceInit = !hasValidDimensions && hasContent && retryCount >= 3;
416
-
417
- // Fallback pour images: forcer l'initialisation après 3 tentatives même si images pas chargées
418
- const shouldForceInitImages = !imagesLoaded && hasContent && retryCount >= 3;
419
-
420
- console.log(`[bb-contents] Marquee ${index + 1}: Vérifications:`, {
421
- hasValidDimensions: hasValidDimensions,
422
- hasContent: hasContent,
423
- imagesLoaded: imagesLoaded,
424
- retryCount: retryCount,
425
- maxRetries: maxRetries,
426
- shouldForceInit: shouldForceInit,
427
- shouldForceInitImages: shouldForceInitImages
428
- });
429
-
430
- // Si pas de contenu valide ou images pas chargées, réessayer (sauf si on force)
431
- if ((!hasValidDimensions || !imagesLoaded) && !shouldForceInit && !shouldForceInitImages) {
432
- if (retryCount < maxRetries) {
433
- const delay = 50 + retryCount * 50; // Délais rapides pour header
434
- console.log(`[bb-contents] Marquee ${index + 1}: RETRY dans ${delay}ms (dimensions: ${hasValidDimensions}, images: ${imagesLoaded})`);
435
- // Contenu/images non prêts, nouvelle tentative
436
- setTimeout(() => initAnimation(retryCount + 1), delay);
368
+ // Pour le vertical, s'assurer qu'on a une hauteur minimale
369
+ if (isVertical && contentHeight < 50) {
370
+ if (retryCount < 8) { // Plus de tentatives
371
+ bbContents.utils.log('Hauteur insuffisante pour le marquee vertical (' + contentHeight + 'px), nouvelle tentative dans', (200 + retryCount * 100), 'ms');
372
+ setTimeout(() => initAnimation(retryCount + 1), 200 + retryCount * 100);
437
373
  return;
438
374
  } else {
439
- // Échec d'initialisation après plusieurs tentatives
440
- console.log(`[bb-contents] Marquee ${index + 1}: ÉCHEC après ${maxRetries} tentatives`);
441
- return;
375
+ bbContents.utils.log('Échec d\'initialisation - hauteur insuffisante après 8 tentatives');
376
+ return;
442
377
  }
443
- }
444
-
445
- if (shouldForceInit || shouldForceInitImages) {
446
- console.log(`[bb-contents] Marquee ${index + 1}: FORÇAGE DE L'INITIALISATION (fallback)`);
447
- // Utiliser des dimensions par défaut si les vraies dimensions ne sont pas disponibles
448
- if (isVertical && finalHeight <= 50) {
449
- finalHeight = 200; // Hauteur par défaut pour vertical
450
378
  }
451
- if (!isVertical && finalWidth <= 50) {
452
- finalWidth = 300; // Largeur par défaut pour horizontal
379
+
380
+ // Vérifier que les images sont chargées
381
+ if (!imagesLoaded && images.length > 0) {
382
+ if (retryCount < 8) { // Plus de tentatives
383
+ bbContents.utils.log('Images non chargées, nouvelle tentative dans', (200 + retryCount * 100), 'ms');
384
+ setTimeout(() => initAnimation(retryCount + 1), 200 + retryCount * 100);
385
+ return;
386
+ } else {
387
+ bbContents.utils.log('Échec d\'initialisation - images non chargées après 8 tentatives');
388
+ return;
389
+ }
453
390
  }
454
- }
455
-
456
- console.log(`[bb-contents] Marquee ${index + 1}: INITIALISATION DE L'ANIMATION`);
457
391
 
458
- if (isVertical) {
459
- // Animation JavaScript pour le vertical - logique simple et efficace
460
- const contentSize = finalHeight;
461
- const gapSize = parseInt(gap);
462
- const totalSize = contentSize * 4 + gapSize * 3; // 4 copies
392
+ if (isVertical) {
393
+ // Animation JavaScript pour le vertical
394
+ const contentSize = contentHeight;
395
+ const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
463
396
 
464
397
  // Ajuster la hauteur du scrollContainer seulement si pas en mode auto
465
398
  if (!useAutoHeight) {
466
- scrollContainer.style.height = totalSize + 'px';
399
+ scrollContainer.style.height = totalSize + 'px';
467
400
  }
468
-
469
- // Position initiale simple
470
- let currentPosition = direction === 'bottom' ? -(contentSize + gapSize) : 0;
471
- const step = (parseFloat(speed) * 2) / 60;
401
+
402
+ let currentPosition = direction === 'bottom' ? -contentSize - parseInt(gap) : 0;
403
+ const step = (parseFloat(speed) * 2) / 60; // Vitesse différente
472
404
  let isPaused = false;
473
405
 
474
- // Fonction d'animation JavaScript - logique simple
406
+ // Fonction d'animation JavaScript
475
407
  const animate = () => {
476
408
  if (!isPaused) {
477
409
  if (direction === 'bottom') {
478
410
  currentPosition += step;
479
- // Reset simple : quand on arrive au début, on repart du début
480
411
  if (currentPosition >= 0) {
481
- currentPosition = -(contentSize + gapSize);
412
+ currentPosition = -contentSize - parseInt(gap);
482
413
  }
483
414
  } else {
484
415
  currentPosition -= step;
485
- // Reset simple : quand on arrive à la fin, on repart du début
486
- if (currentPosition <= -(contentSize + gapSize)) {
416
+ if (currentPosition <= -contentSize - parseInt(gap)) {
487
417
  currentPosition = 0;
488
418
  }
489
419
  }
@@ -496,40 +426,40 @@
496
426
  // Démarrer l'animation
497
427
  animate();
498
428
 
499
- // Pause au survol simple
500
- element.addEventListener('mouseenter', function() {
501
- isPaused = true;
502
- });
503
- element.addEventListener('mouseleave', function() {
504
- isPaused = false;
505
- });
429
+ bbContents.utils.log('Marquee vertical créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px', 'hauteur-wrapper:', height + 'px');
430
+
431
+ // Pause au survol
432
+ if (pauseOnHover === 'true') {
433
+ element.addEventListener('mouseenter', function() {
434
+ isPaused = true;
435
+ });
436
+ element.addEventListener('mouseleave', function() {
437
+ isPaused = false;
438
+ });
439
+ }
506
440
 
507
441
  // Marquee vertical créé avec animation JS
508
- } else {
509
- // Animation JavaScript pour l'horizontal - logique simple et efficace
510
- const contentSize = finalWidth;
511
- const gapSize = parseInt(gap);
512
- const totalSize = contentSize * 4 + gapSize * 3; // 4 copies
513
- scrollContainer.style.width = totalSize + 'px';
514
-
515
- // Position initiale simple
516
- let currentPosition = direction === 'right' ? -(contentSize + gapSize) : 0;
442
+ } else {
443
+ // Animation JavaScript pour l'horizontal (comme le vertical pour éviter les saccades)
444
+ const contentSize = contentWidth;
445
+ const totalSize = contentSize * 4 + parseInt(gap) * 3;
446
+ scrollContainer.style.width = totalSize + 'px';
447
+
448
+ let currentPosition = direction === 'right' ? -contentSize - parseInt(gap) : 0;
517
449
  const step = (parseFloat(speed) * 0.5) / 60; // Vitesse réduite pour l'horizontal
518
450
  let isPaused = false;
519
451
 
520
- // Fonction d'animation JavaScript - logique simple
452
+ // Fonction d'animation JavaScript
521
453
  const animate = () => {
522
454
  if (!isPaused) {
523
455
  if (direction === 'right') {
524
456
  currentPosition += step;
525
- // Reset simple : quand on arrive au début, on repart du début
526
457
  if (currentPosition >= 0) {
527
- currentPosition = -(contentSize + gapSize);
458
+ currentPosition = -contentSize - parseInt(gap);
528
459
  }
529
460
  } else {
530
461
  currentPosition -= step;
531
- // Reset simple : quand on arrive à la fin, on repart du début
532
- if (currentPosition <= -(contentSize + gapSize)) {
462
+ if (currentPosition <= -contentSize - parseInt(gap)) {
533
463
  currentPosition = 0;
534
464
  }
535
465
  }
@@ -542,44 +472,29 @@
542
472
  // Démarrer l'animation
543
473
  animate();
544
474
 
545
- // Pause au survol simple
546
- element.addEventListener('mouseenter', function() {
547
- isPaused = true;
548
- });
549
- element.addEventListener('mouseleave', function() {
550
- isPaused = false;
551
- });
475
+ bbContents.utils.log('Marquee horizontal créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px');
476
+
477
+ // Pause au survol
478
+ if (pauseOnHover === 'true') {
479
+ element.addEventListener('mouseenter', function() {
480
+ isPaused = true;
481
+ });
482
+ element.addEventListener('mouseleave', function() {
483
+ isPaused = false;
484
+ });
485
+ }
552
486
 
553
487
  // Marquee horizontal créé avec animation JS
554
488
  }
555
- });
556
- };
557
-
558
- // Démarrer l'initialisation avec délai adaptatif - Option 1: Attendre que tout soit prêt
559
- let initDelay = isVertical ? 500 : 200; // Délais plus longs par défaut
560
- if (bbContents._performanceBoostDetected) {
561
- initDelay = isVertical ? 800 : 500; // Délais encore plus longs avec bb-performance-boost
562
- }
563
-
564
- // Attendre window.load si pas encore déclenché
565
- if (document.readyState !== 'complete') {
566
- // Attente de window.load pour initialiser le marquee
567
- window.addEventListener('load', () => {
568
- setTimeout(() => {
569
- initAnimation(0);
570
- console.log(`[bb-contents] Marquee ${index + 1}: Animation démarrée`);
571
- }, initDelay);
572
489
  });
573
- } else {
574
- // window.load déjà déclenché, initialiser directement
575
- setTimeout(() => {
576
- initAnimation(0);
577
- console.log(`[bb-contents] Marquee ${index + 1}: Animation démarrée`);
578
- }, initDelay);
579
- }
490
+ };
491
+
492
+ // Démarrer l'initialisation avec délai adaptatif - Version 1.0.37-beta robuste
493
+ const initDelay = isVertical ? 500 : 200; // Délais plus longs pour attendre les images
494
+ setTimeout(() => initAnimation(0), initDelay);
580
495
  });
581
496
 
582
- // Module Marquee initialisé
497
+ bbContents.utils.log('Module Marquee initialisé:', elements.length, 'éléments');
583
498
  }
584
499
  },
585
500
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bebranded/bb-contents",
3
- "version": "1.0.56-beta",
3
+ "version": "1.0.58-beta",
4
4
  "description": "Contenus additionnels français pour Webflow",
5
5
  "main": "bb-contents.js",
6
6
  "scripts": {