@bebranded/bb-contents 1.0.1 → 1.0.2

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 +314 -381
  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.1
4
+ * @version 1.0.2
5
5
  * @author BeBranded
6
6
  * @license MIT
7
7
  * @website https://www.bebranded.xyz
@@ -9,6 +9,11 @@
9
9
  (function() {
10
10
  'use strict';
11
11
 
12
+ // Créer l'objet temporaire pour la configuration si il n'existe pas
13
+ if (!window._bbContentsConfig) {
14
+ window._bbContentsConfig = {};
15
+ }
16
+
12
17
  // Protection contre le double chargement
13
18
  if (window.bbContents) {
14
19
  console.warn('BeBranded Contents est déjà chargé');
@@ -17,13 +22,24 @@
17
22
 
18
23
  // Configuration
19
24
  const config = {
20
- version: '1.0.1',
21
- debug: true, // Activé temporairement pour debug
25
+ version: '1.0.2',
26
+ debug: false, // Debug désactivé
22
27
  prefix: 'bb-', // utilisé pour générer les sélecteurs (data-bb-*)
28
+ youtubeEndpoint: null, // URL du worker YouTube (à définir par l'utilisateur)
23
29
  i18n: {
24
30
  copied: 'Lien copié !'
25
31
  }
26
32
  };
33
+
34
+ // Détecter la configuration YouTube définie avant le chargement
35
+ if (window.bbContents && window.bbContents.config && window.bbContents.config.youtubeEndpoint) {
36
+ config.youtubeEndpoint = window.bbContents.config.youtubeEndpoint;
37
+ }
38
+
39
+ // Détecter la configuration dans l'objet temporaire
40
+ if (window._bbContentsConfig && window._bbContentsConfig.youtubeEndpoint) {
41
+ config.youtubeEndpoint = window._bbContentsConfig.youtubeEndpoint;
42
+ }
27
43
 
28
44
  // Objet principal
29
45
  const bbContents = {
@@ -84,18 +100,12 @@
84
100
 
85
101
  this.utils.log('Initialisation v' + this.config.version);
86
102
 
87
- // Debug: Analyser l'environnement
88
- this.utils.log('=== DEBUG ENVIRONNEMENT ===');
89
- this.utils.log('Body attributes:', Array.from(document.body.attributes).map(attr => attr.name + '=' + attr.value));
90
- this.utils.log('Scripts chargés:', document.querySelectorAll('script').length);
91
- this.utils.log('Stylesheets chargés:', document.querySelectorAll('link[rel="stylesheet"]').length);
92
- this.utils.log('Marquees détectés:', document.querySelectorAll('[bb-marquee]').length);
93
- this.utils.log('Marquees déjà traités:', document.querySelectorAll('[bb-marquee][data-bb-marquee-processed]').length);
103
+ // Debug environnement supprimé pour console propre
94
104
 
95
105
  // Détection du bb-performance-boost
96
106
  this._performanceBoostDetected = document.body.hasAttribute('bb-performance-boost');
97
107
  if (this._performanceBoostDetected) {
98
- this.utils.log('bb-performance-boost détecté - mode de compatibilité activé');
108
+ // bb-performance-boost détecté - mode de compatibilité activé
99
109
  }
100
110
 
101
111
  // Déterminer la portée
@@ -105,7 +115,7 @@
105
115
  Object.keys(this.modules).forEach(function(moduleName) {
106
116
  const module = bbContents.modules[moduleName];
107
117
  if (module.detect && module.detect(scope)) {
108
- bbContents.utils.log('Module détecté:', moduleName);
118
+ // Module détecté
109
119
  try {
110
120
  module.init(scope);
111
121
  } catch (error) {
@@ -130,7 +140,7 @@
130
140
  // Vérifier les marquees non initialisés
131
141
  const marqueeElements = scope.querySelectorAll('[bb-marquee]:not([data-bb-marquee-processed])');
132
142
  if (marqueeElements.length > 0) {
133
- bbContents.utils.log('Marquees non initialisés détectés:', marqueeElements.length);
143
+ // Marquees non initialisés détectés
134
144
  needsReinit = true;
135
145
  }
136
146
 
@@ -138,7 +148,7 @@
138
148
  Object.keys(this.modules).forEach(function(moduleName) {
139
149
  const module = bbContents.modules[moduleName];
140
150
  if (module.checkFailed && module.checkFailed(scope)) {
141
- bbContents.utils.log('Module', moduleName, 'a des éléments échoués');
151
+ // Module a des éléments échoués
142
152
  needsReinit = true;
143
153
  }
144
154
  });
@@ -146,7 +156,7 @@
146
156
  // Réinitialiser si nécessaire et si on n'a pas dépassé le nombre max de tentatives
147
157
  if (needsReinit && this._initRetryCount < this._maxInitRetries) {
148
158
  this._initRetryCount++;
149
- bbContents.utils.log('Tentative de réinitialisation', this._initRetryCount, '/', this._maxInitRetries);
159
+ // Tentative de réinitialisation
150
160
 
151
161
  const delay = this._performanceBoostDetected ? 1000 * this._initRetryCount : 500 * this._initRetryCount;
152
162
  setTimeout(() => {
@@ -160,6 +170,22 @@
160
170
  this._initRetryCount = 0;
161
171
  this.init();
162
172
  },
173
+
174
+ // Méthode pour détecter la configuration YouTube définie après le chargement
175
+ checkYouTubeConfig: function() {
176
+ // Vérifier si la configuration a été définie après le chargement
177
+ if (this.config.youtubeEndpoint) {
178
+ return true;
179
+ }
180
+
181
+ // Vérifier dans l'objet temporaire
182
+ if (window._bbContentsConfig && window._bbContentsConfig.youtubeEndpoint) {
183
+ this.config.youtubeEndpoint = window._bbContentsConfig.youtubeEndpoint;
184
+ return true;
185
+ }
186
+
187
+ return false;
188
+ },
163
189
 
164
190
  // Observer DOM pour contenu dynamique
165
191
  setupObserver: function() {
@@ -208,193 +234,34 @@
208
234
 
209
235
  // Modules
210
236
  bbContents.modules = {
211
- // Module SEO
212
- seo: {
213
- detect: function(scope) {
214
- return scope.querySelector('[bb-seo]') !== null;
215
- },
216
-
217
- init: function(scope) {
218
- const elements = scope.querySelectorAll('[bb-seo]');
219
- if (elements.length === 0) return;
220
-
221
- bbContents.utils.log('Module détecté: seo');
222
-
223
- elements.forEach(element => {
224
- if (element.bbProcessed) return;
225
- element.bbProcessed = true;
226
-
227
- const title = bbContents._getAttr(element, 'bb-seo-title');
228
- const description = bbContents._getAttr(element, 'bb-seo-description');
229
- const keywords = bbContents._getAttr(element, 'bb-seo-keywords');
230
-
231
- if (title) {
232
- document.title = title;
233
- }
234
-
235
- if (description) {
236
- let meta = document.querySelector('meta[name="description"]');
237
- if (!meta) {
238
- meta = document.createElement('meta');
239
- meta.name = 'description';
240
- document.head.appendChild(meta);
241
- }
242
- meta.content = description;
243
- }
244
-
245
- if (keywords) {
246
- let meta = document.querySelector('meta[name="keywords"]');
247
- if (!meta) {
248
- meta = document.createElement('meta');
249
- meta.name = 'keywords';
250
- document.head.appendChild(meta);
251
- }
252
- meta.content = keywords;
253
- }
254
- });
255
-
256
- bbContents.utils.log('Module SEO initialisé:', elements.length, 'éléments');
257
- }
258
- },
259
-
260
- // Module Images
261
- images: {
262
- detect: function(scope) {
263
- return scope.querySelector('[bb-images]') !== null;
264
- },
265
-
266
- init: function(scope) {
267
- const elements = scope.querySelectorAll('[bb-images]');
268
- if (elements.length === 0) return;
269
-
270
- bbContents.utils.log('Module détecté: images');
271
-
272
- elements.forEach(element => {
273
- if (element.bbProcessed) return;
274
- element.bbProcessed = true;
275
-
276
- const lazy = bbContents._getAttr(element, 'bb-images-lazy');
277
- const webp = bbContents._getAttr(element, 'bb-images-webp');
278
-
279
- if (lazy === 'true') {
280
- // Implémentation lazy loading basique
281
- const images = element.querySelectorAll('img');
282
- images.forEach(img => {
283
- if (!img.loading) {
284
- img.loading = 'lazy';
285
- }
286
- });
287
- }
288
-
289
- if (webp === 'true') {
290
- // Support WebP basique
291
- const images = element.querySelectorAll('img');
292
- images.forEach(img => {
293
- const src = img.src;
294
- if (src && !src.includes('.webp')) {
295
- // Logique de conversion WebP (à implémenter selon les besoins)
296
- bbContents.utils.log('Support WebP activé pour:', src);
297
- }
298
- });
299
- }
300
- });
301
-
302
- bbContents.utils.log('Module Images initialisé:', elements.length, 'éléments');
303
- }
304
- },
305
-
306
- // Module Infinite Scroll
307
- infinite: {
308
- detect: function(scope) {
309
- return scope.querySelector('[bb-infinite]') !== null;
310
- },
311
-
312
- init: function(scope) {
313
- const elements = scope.querySelectorAll('[bb-infinite]');
314
- if (elements.length === 0) return;
315
-
316
- bbContents.utils.log('Module détecté: infinite');
317
-
318
- elements.forEach(element => {
319
- if (element.bbProcessed) return;
320
- element.bbProcessed = true;
321
-
322
- const threshold = bbContents._getAttr(element, 'bb-infinite-threshold') || '0.1';
323
- const url = bbContents._getAttr(element, 'bb-infinite-url');
324
-
325
- if (!url) {
326
- bbContents.utils.log('Erreur: bb-infinite-url manquant');
327
- return;
328
- }
329
-
330
- // Implémentation basique d'infinite scroll
331
- let loading = false;
332
- let page = 1;
333
-
334
- const loadMore = () => {
335
- if (loading) return;
336
- loading = true;
337
-
338
- fetch(`${url}?page=${page}`)
339
- .then(response => response.json())
340
- .then(data => {
341
- if (data.items && data.items.length > 0) {
342
- // Ajouter le contenu
343
- element.innerHTML += data.html || '';
344
- page++;
345
- loading = false;
346
- }
347
- })
348
- .catch(error => {
349
- bbContents.utils.log('Erreur infinite scroll:', error);
350
- loading = false;
351
- });
352
- };
353
-
354
- // Observer d'intersection pour déclencher le chargement
355
- const observer = new IntersectionObserver((entries) => {
356
- entries.forEach(entry => {
357
- if (entry.isIntersecting) {
358
- loadMore();
359
- }
360
- });
361
- }, { threshold: parseFloat(threshold) });
362
-
363
- observer.observe(element);
364
- });
365
-
366
- bbContents.utils.log('Module Infinite Scroll initialisé:', elements.length, 'éléments');
367
- }
368
- },
369
-
370
- // Module Marquee - Version live 1.0.33-beta avec améliorations d'initialisation
237
+ // Module Marquee - Version live 1.0.41-beta avec modules parasites supprimés
371
238
  marquee: {
372
- detect: function(scope) {
373
- const s = scope || document;
239
+ detect: function(scope) {
240
+ const s = scope || document;
374
241
  return s.querySelector(bbContents._attrSelector('marquee')) !== null;
375
242
  },
376
243
 
377
244
  // Nouvelle méthode pour vérifier les éléments échoués
378
245
  checkFailed: function(scope) {
379
- const s = scope || document;
246
+ const s = scope || document;
380
247
  const failedElements = s.querySelectorAll('[bb-marquee]:not([data-bb-marquee-processed])');
381
248
  return failedElements.length > 0;
382
- },
383
-
384
- init: function(root) {
385
- const scope = root || document;
386
- if (scope.closest && scope.closest('[data-bb-disable]')) return;
387
- const elements = scope.querySelectorAll(bbContents._attrSelector('marquee'));
249
+ },
250
+
251
+ init: function(root) {
252
+ const scope = root || document;
253
+ if (scope.closest && scope.closest('[data-bb-disable]')) return;
254
+ const elements = scope.querySelectorAll(bbContents._attrSelector('marquee'));
388
255
 
389
- elements.forEach(function(element) {
256
+ elements.forEach(function(element) {
390
257
  // Vérifier si l'élément a déjà été traité par un autre module
391
258
  if (element.bbProcessed || element.hasAttribute('data-bb-youtube-processed')) {
392
- bbContents.utils.log('Élément marquee déjà traité par un autre module, ignoré:', element);
259
+ // Élément marquee déjà traité par un autre module, ignoré
393
260
  return;
394
261
  }
395
- element.bbProcessed = true;
262
+ element.bbProcessed = true;
396
263
 
397
- // Récupérer les options
264
+ // Récupérer les options
398
265
  const speed = bbContents._getAttr(element, 'bb-marquee-speed') || '100';
399
266
  const direction = bbContents._getAttr(element, 'bb-marquee-direction') || 'left';
400
267
  const pauseOnHover = bbContents._getAttr(element, 'bb-marquee-pause');
@@ -403,67 +270,67 @@
403
270
  const height = bbContents._getAttr(element, 'bb-marquee-height') || '300';
404
271
  const minHeight = bbContents._getAttr(element, 'bb-marquee-min-height');
405
272
 
406
- // Sauvegarder le contenu original
407
- const originalHTML = element.innerHTML;
408
-
409
- // Créer le conteneur principal
410
- const mainContainer = document.createElement('div');
411
- const isVertical = orientation === 'vertical';
273
+ // Sauvegarder le contenu original
274
+ const originalHTML = element.innerHTML;
275
+
276
+ // Créer le conteneur principal
277
+ const mainContainer = document.createElement('div');
278
+ const isVertical = orientation === 'vertical';
412
279
  const useAutoHeight = isVertical && height === 'auto';
413
280
 
414
- mainContainer.style.cssText = `
415
- position: relative;
416
- width: 100%;
281
+ mainContainer.style.cssText = `
282
+ position: relative;
283
+ width: 100%;
417
284
  height: ${isVertical ? (height === 'auto' ? 'auto' : height + 'px') : 'auto'};
418
- overflow: hidden;
419
- min-height: ${isVertical ? '100px' : '50px'};
285
+ overflow: hidden;
286
+ min-height: ${isVertical ? '100px' : '50px'};
420
287
  ${minHeight ? `min-height: ${minHeight};` : ''}
421
- `;
288
+ `;
422
289
 
423
- // Créer le conteneur de défilement
424
- const scrollContainer = document.createElement('div');
425
- scrollContainer.style.cssText = `
290
+ // Créer le conteneur de défilement
291
+ const scrollContainer = document.createElement('div');
292
+ scrollContainer.style.cssText = `
426
293
  ${useAutoHeight ? 'position: relative;' : 'position: absolute;'}
427
- will-change: transform;
294
+ will-change: transform;
428
295
  ${useAutoHeight ? '' : 'height: 100%; top: 0px; left: 0px;'}
429
- display: flex;
430
- ${isVertical ? 'flex-direction: column;' : ''}
431
- align-items: center;
432
- gap: ${gap}px;
433
- ${isVertical ? '' : 'white-space: nowrap;'}
434
- flex-shrink: 0;
296
+ display: flex;
297
+ ${isVertical ? 'flex-direction: column;' : ''}
298
+ align-items: center;
299
+ gap: ${gap}px;
300
+ ${isVertical ? '' : 'white-space: nowrap;'}
301
+ flex-shrink: 0;
435
302
  transition: transform 0.1s ease-out;
436
- `;
303
+ `;
437
304
 
438
- // Créer le bloc de contenu principal
439
- const mainBlock = document.createElement('div');
440
- mainBlock.innerHTML = originalHTML;
441
- mainBlock.style.cssText = `
442
- display: flex;
443
- ${isVertical ? 'flex-direction: column;' : ''}
444
- align-items: center;
445
- gap: ${gap}px;
446
- ${isVertical ? '' : 'white-space: nowrap;'}
447
- flex-shrink: 0;
448
- ${isVertical ? 'min-height: 100px;' : ''}
449
- `;
305
+ // Créer le bloc de contenu principal
306
+ const mainBlock = document.createElement('div');
307
+ mainBlock.innerHTML = originalHTML;
308
+ mainBlock.style.cssText = `
309
+ display: flex;
310
+ ${isVertical ? 'flex-direction: column;' : ''}
311
+ align-items: center;
312
+ gap: ${gap}px;
313
+ ${isVertical ? '' : 'white-space: nowrap;'}
314
+ flex-shrink: 0;
315
+ ${isVertical ? 'min-height: 100px;' : ''}
316
+ `;
317
+
318
+ // Créer plusieurs répétitions pour un défilement continu
319
+ const repeatBlock1 = mainBlock.cloneNode(true);
320
+ const repeatBlock2 = mainBlock.cloneNode(true);
321
+ const repeatBlock3 = mainBlock.cloneNode(true);
322
+
323
+ // Assembler la structure
324
+ scrollContainer.appendChild(mainBlock);
325
+ scrollContainer.appendChild(repeatBlock1);
326
+ scrollContainer.appendChild(repeatBlock2);
327
+ scrollContainer.appendChild(repeatBlock3);
328
+ mainContainer.appendChild(scrollContainer);
329
+
330
+ // Vider et remplacer le contenu original
331
+ element.innerHTML = '';
332
+ element.appendChild(mainContainer);
450
333
 
451
- // Créer plusieurs répétitions pour un défilement continu
452
- const repeatBlock1 = mainBlock.cloneNode(true);
453
- const repeatBlock2 = mainBlock.cloneNode(true);
454
- const repeatBlock3 = mainBlock.cloneNode(true);
455
-
456
- // Assembler la structure
457
- scrollContainer.appendChild(mainBlock);
458
- scrollContainer.appendChild(repeatBlock1);
459
- scrollContainer.appendChild(repeatBlock2);
460
- scrollContainer.appendChild(repeatBlock3);
461
- mainContainer.appendChild(scrollContainer);
462
-
463
- // Vider et remplacer le contenu original
464
- element.innerHTML = '';
465
- element.appendChild(mainContainer);
466
-
467
334
  // Marquer l'élément comme traité par le module marquee
468
335
  element.setAttribute('data-bb-marquee-processed', 'true');
469
336
 
@@ -474,7 +341,7 @@
474
341
  const imagesLoaded = Array.from(images).every(img => img.complete && img.naturalHeight > 0);
475
342
 
476
343
  // Attendre que le contenu soit dans le DOM et que les images soient chargées
477
- requestAnimationFrame(() => {
344
+ requestAnimationFrame(() => {
478
345
  // Calcul plus robuste des dimensions
479
346
  const rect = mainBlock.getBoundingClientRect();
480
347
  const contentWidth = rect.width || mainBlock.offsetWidth;
@@ -488,11 +355,10 @@
488
355
  // Si largeur trop petite, utiliser la largeur du parent
489
356
  const parentRect = mainBlock.parentElement.getBoundingClientRect();
490
357
  finalWidth = parentRect.width || mainBlock.parentElement.offsetWidth;
491
- bbContents.utils.log('Largeur corrigée pour marquee vertical:', finalWidth, 'px (était:', contentWidth, 'px)');
358
+ // Largeur corrigée pour marquee vertical
492
359
  }
493
360
 
494
- // Debug amélioré avec statut des images
495
- bbContents.utils.log('Debug - Largeur:', finalWidth, 'px, Hauteur:', finalHeight, 'px, Images chargées:', imagesLoaded, 'Enfants:', mainBlock.children.length, 'Vertical:', isVertical, 'Direction:', direction, 'Tentative:', retryCount + 1);
361
+ // Debug supprimé pour console propre
496
362
 
497
363
  // Vérifications robustes avant initialisation
498
364
  const hasValidDimensions = (isVertical && finalHeight > 50) || (!isVertical && finalWidth > 50);
@@ -502,29 +368,29 @@
502
368
  if (!hasValidDimensions || !imagesLoaded) {
503
369
  if (retryCount < maxRetries) {
504
370
  const delay = 300 + retryCount * 200; // Délais plus longs pour attendre les images
505
- bbContents.utils.log('Contenu/images non prêts, nouvelle tentative dans', delay, 'ms');
371
+ // Contenu/images non prêts, nouvelle tentative
506
372
  setTimeout(() => initAnimation(retryCount + 1), delay);
507
373
  return;
508
374
  } else {
509
- bbContents.utils.log('Échec d\'initialisation après', maxRetries, 'tentatives - dimensions:', finalWidth + 'x' + finalHeight, 'images chargées:', imagesLoaded);
510
- return;
375
+ // Échec d'initialisation après plusieurs tentatives
376
+ return;
511
377
  }
512
- }
513
-
514
- if (isVertical) {
515
- // Animation JavaScript pour le vertical
378
+ }
379
+
380
+ if (isVertical) {
381
+ // Animation JavaScript pour le vertical
516
382
  const contentSize = finalHeight;
517
- const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
383
+ const totalSize = contentSize * 4 + parseInt(gap) * 3; // 4 copies au lieu de 3
518
384
 
519
385
  // Ajuster la hauteur du scrollContainer seulement si pas en mode auto
520
386
  if (!useAutoHeight) {
521
- scrollContainer.style.height = totalSize + 'px';
387
+ scrollContainer.style.height = totalSize + 'px';
522
388
  }
523
-
524
- let currentPosition = direction === 'bottom' ? -contentSize - parseInt(gap) : 0;
389
+
390
+ let currentPosition = direction === 'bottom' ? -contentSize - parseInt(gap) : 0;
525
391
  const baseStep = (parseFloat(speed) * 2) / 60; // Vitesse de base
526
392
  let currentStep = baseStep;
527
- let isPaused = false;
393
+ let isPaused = false;
528
394
  let animationId = null;
529
395
  let lastTime = 0;
530
396
 
@@ -548,15 +414,15 @@
548
414
 
549
415
  scrollContainer.style.transform = `translate3d(0px, ${currentPosition}px, 0px)`;
550
416
  animationId = requestAnimationFrame(animate);
551
- };
552
-
553
- // Démarrer l'animation
417
+ };
418
+
419
+ // Démarrer l'animation
554
420
  animationId = requestAnimationFrame(animate);
555
-
556
- bbContents.utils.log('Marquee vertical créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px', 'hauteur-wrapper:', height + 'px');
557
-
421
+
422
+ // Marquee vertical créé avec animation JS
423
+
558
424
  // Pause au survol avec transition fluide CSS + JS
559
- if (pauseOnHover === 'true') {
425
+ if (pauseOnHover === 'true') {
560
426
  // Transition fluide avec easing naturel
561
427
  const transitionSpeed = (targetSpeed, duration = 300) => {
562
428
  const startSpeed = currentStep;
@@ -587,19 +453,19 @@
587
453
  requestAnimationFrame(animateTransition);
588
454
  };
589
455
 
590
- element.addEventListener('mouseenter', function() {
456
+ element.addEventListener('mouseenter', function() {
591
457
  transitionSpeed(0); // Ralentir jusqu'à 0
592
- });
593
- element.addEventListener('mouseleave', function() {
458
+ });
459
+ element.addEventListener('mouseleave', function() {
594
460
  transitionSpeed(baseStep); // Revenir à la vitesse normale
595
- });
596
- }
597
- } else {
461
+ });
462
+ }
463
+ } else {
598
464
  // Animation JavaScript pour l'horizontal (comme le vertical pour éviter les saccades)
599
465
  const contentSize = finalWidth;
600
466
  const totalSize = contentSize * 4 + parseInt(gap) * 3;
601
- scrollContainer.style.width = totalSize + 'px';
602
-
467
+ scrollContainer.style.width = totalSize + 'px';
468
+
603
469
  let currentPosition = direction === 'right' ? -contentSize - parseInt(gap) : 0;
604
470
  const baseStep = (parseFloat(speed) * 0.5) / 60; // Vitesse de base
605
471
  let currentStep = baseStep;
@@ -613,12 +479,12 @@
613
479
  const deltaTime = currentTime - lastTime;
614
480
  lastTime = currentTime;
615
481
 
616
- if (direction === 'right') {
482
+ if (direction === 'right') {
617
483
  currentPosition += currentStep * (deltaTime / 16.67); // Normaliser à 60fps
618
484
  if (currentPosition >= 0) {
619
485
  currentPosition = -contentSize - parseInt(gap);
620
486
  }
621
- } else {
487
+ } else {
622
488
  currentPosition -= currentStep * (deltaTime / 16.67);
623
489
  if (currentPosition <= -contentSize - parseInt(gap)) {
624
490
  currentPosition = 0;
@@ -632,10 +498,10 @@
632
498
  // Démarrer l'animation
633
499
  animationId = requestAnimationFrame(animate);
634
500
 
635
- bbContents.utils.log('Marquee horizontal créé avec animation JS - direction:', direction, 'taille:', contentSize + 'px', 'total:', totalSize + 'px');
501
+ // Marquee horizontal créé avec animation JS
636
502
 
637
503
  // Pause au survol avec transition fluide CSS + JS
638
- if (pauseOnHover === 'true') {
504
+ if (pauseOnHover === 'true') {
639
505
  // Transition fluide avec easing naturel
640
506
  const transitionSpeed = (targetSpeed, duration = 300) => {
641
507
  const startSpeed = currentStep;
@@ -666,17 +532,17 @@
666
532
  requestAnimationFrame(animateTransition);
667
533
  };
668
534
 
669
- element.addEventListener('mouseenter', function() {
535
+ element.addEventListener('mouseenter', function() {
670
536
  transitionSpeed(0); // Ralentir jusqu'à 0
671
- });
672
- element.addEventListener('mouseleave', function() {
537
+ });
538
+ element.addEventListener('mouseleave', function() {
673
539
  transitionSpeed(baseStep); // Revenir à la vitesse normale
674
- });
675
- }
540
+ });
676
541
  }
677
- });
678
- };
679
-
542
+ }
543
+ });
544
+ };
545
+
680
546
  // Démarrer l'initialisation avec délai adaptatif - Option 1: Attendre que tout soit prêt
681
547
  let initDelay = isVertical ? 500 : 200; // Délais plus longs par défaut
682
548
  if (bbContents._performanceBoostDetected) {
@@ -685,7 +551,7 @@
685
551
 
686
552
  // Attendre window.load si pas encore déclenché
687
553
  if (document.readyState !== 'complete') {
688
- bbContents.utils.log('Attente de window.load pour initialiser le marquee');
554
+ // Attente de window.load pour initialiser le marquee
689
555
  window.addEventListener('load', () => {
690
556
  setTimeout(() => initAnimation(0), initDelay);
691
557
  });
@@ -695,7 +561,7 @@
695
561
  }
696
562
  });
697
563
 
698
- bbContents.utils.log('Module Marquee initialisé:', elements.length, 'éléments');
564
+ // Module Marquee initialisé
699
565
  }
700
566
  },
701
567
 
@@ -757,7 +623,7 @@
757
623
  init: function(scope) {
758
624
  // Vérifier si c'est un bot - pas d'appel API
759
625
  if (this.isBot()) {
760
- bbContents.utils.log('Bot détecté, pas de chargement YouTube (économie API)');
626
+ // Bot détecté, pas de chargement YouTube (économie API)
761
627
  return;
762
628
  }
763
629
 
@@ -767,120 +633,178 @@
767
633
  const elements = scope.querySelectorAll('[bb-youtube-channel]');
768
634
  if (elements.length === 0) return;
769
635
 
770
- bbContents.utils.log('Module détecté: youtube');
636
+ // Module détecté: youtube
771
637
 
772
638
  elements.forEach(element => {
773
639
  // Vérifier si l'élément a déjà été traité par un autre module
774
640
  if (element.bbProcessed || element.hasAttribute('data-bb-marquee-processed')) {
775
- bbContents.utils.log('Élément youtube déjà traité par un autre module, ignoré:', element);
641
+ // Élément youtube déjà traité par un autre module, ignoré
776
642
  return;
777
643
  }
778
644
  element.bbProcessed = true;
779
645
 
780
- const channelId = bbContents._getAttr(element, 'bb-youtube-channel');
781
- const videoCount = bbContents._getAttr(element, 'bb-youtube-video-count') || '10';
782
- const allowShorts = bbContents._getAttr(element, 'bb-youtube-allow-shorts') === 'true';
783
- const language = bbContents._getAttr(element, 'bb-youtube-language') || 'fr';
784
- const endpoint = bbContents.config.youtubeEndpoint;
785
-
786
- if (!channelId) {
787
- bbContents.utils.log('Erreur: bb-youtube-channel manquant');
788
- return;
789
- }
646
+ // Utiliser la nouvelle fonction initElement
647
+ this.initElement(element);
648
+ });
649
+ },
650
+
651
+ // Fonction pour initialiser un seul élément YouTube
652
+ initElement: function(element) {
653
+ // Vérifier si c'est un bot - pas d'appel API
654
+ if (this.isBot()) {
655
+ return;
656
+ }
657
+
658
+ const channelId = bbContents._getAttr(element, 'bb-youtube-channel');
659
+ const videoCount = bbContents._getAttr(element, 'bb-youtube-video-count') || '10';
660
+ const allowShorts = bbContents._getAttr(element, 'bb-youtube-allow-shorts') === 'true';
661
+ const language = bbContents._getAttr(element, 'bb-youtube-language') || 'fr';
662
+
663
+ // Vérifier la configuration au moment de l'initialisation
664
+ const endpoint = bbContents.checkYouTubeConfig() ? bbContents.config.youtubeEndpoint : null;
665
+
666
+
667
+ if (!channelId) {
668
+ return;
669
+ }
670
+
671
+ if (!endpoint) {
672
+ // Attendre que la configuration soit définie (max 5 secondes)
673
+ const retryCount = element.getAttribute('data-youtube-retry-count') || '0';
674
+ const retries = parseInt(retryCount);
790
675
 
791
- if (!endpoint) {
792
- bbContents.utils.log('Erreur: youtubeEndpoint non configuré. Utilisez bbContents.config.youtubeEndpoint = "votre-worker-url"');
793
- element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Configuration YouTube manquante</strong><br>Ajoutez : bbContents.config.youtubeEndpoint = "votre-worker-url"</div>';
676
+ if (retries < 50) { // 50 * 100ms = 5 secondes max
677
+ element.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Configuration YouTube en cours...</div>';
678
+ element.setAttribute('data-youtube-retry-count', (retries + 1).toString());
679
+
680
+ // Réessayer dans 100ms
681
+ setTimeout(() => {
682
+ this.initElement(element);
683
+ }, 100);
794
684
  return;
795
- }
796
-
797
- // Chercher le template pour une vidéo (directement dans l'élément ou dans un conteneur)
798
- let template = element.querySelector('[bb-youtube-item]');
799
- let container = element;
800
-
801
- // Si pas de template direct, chercher dans un conteneur
802
- if (!template) {
803
- const containerElement = element.querySelector('[bb-youtube-container]');
804
- if (containerElement) {
805
- container = containerElement;
806
- template = containerElement.querySelector('[bb-youtube-item]');
807
- }
808
- }
809
-
810
- if (!template) {
811
- bbContents.utils.log('Erreur: élément [bb-youtube-item] manquant');
812
- element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Template manquant</strong><br>Ajoutez un élément avec l\'attribut bb-youtube-item</div>';
685
+ } else {
686
+ // Timeout après 5 secondes
687
+ element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Configuration YouTube manquante</strong><br>Ajoutez dans le &lt;head&gt; :<br><code style="display: block; background: #f3f4f6; padding: 10px; margin: 10px 0; border-radius: 4px; font-family: monospace;">&lt;script&gt;<br>bbContents.config.youtubeEndpoint = \'votre-worker-url\';<br>&lt;/script&gt;</code></div>';
813
688
  return;
814
689
  }
815
-
816
- // Cacher le template original
817
- template.style.display = 'none';
818
-
819
- // Marquer l'élément comme traité par le module YouTube
820
- element.setAttribute('data-bb-youtube-processed', 'true');
821
-
822
- // Vérifier le cache d'abord
823
- const cacheKey = `youtube_${channelId}_${videoCount}_${allowShorts}_${language}`;
824
- const cachedData = this.cache.get(cacheKey);
825
-
826
- if (cachedData) {
827
- bbContents.utils.log('Données YouTube récupérées du cache (économie API)');
828
- this.generateYouTubeFeed(container, template, cachedData, allowShorts, language);
829
- return;
690
+ }
691
+
692
+ // Chercher le template pour une vidéo (directement dans l'élément ou dans un conteneur)
693
+ let template = element.querySelector('[bb-youtube-item]');
694
+ let container = element;
695
+
696
+ // Si pas de template direct, chercher dans un conteneur
697
+ if (!template) {
698
+ const containerElement = element.querySelector('[bb-youtube-container]');
699
+ if (containerElement) {
700
+ container = containerElement;
701
+ template = containerElement.querySelector('[bb-youtube-item]');
830
702
  }
831
-
832
- // Afficher un loader
833
- container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
834
-
835
- // Appeler l'API via le Worker
836
- fetch(`${endpoint}?channelId=${channelId}&maxResults=${videoCount}&allowShorts=${allowShorts}`)
837
- .then(response => {
838
- if (!response.ok) {
839
- throw new Error(`HTTP ${response.status}`);
840
- }
841
- return response.json();
842
- })
843
- .then(data => {
844
- if (data.error) {
845
- throw new Error(data.error.message || 'Erreur API YouTube');
703
+ }
704
+
705
+ if (!template) {
706
+ element.innerHTML = '<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Template manquant</strong><br>Ajoutez un élément avec l\'attribut bb-youtube-item</div>';
707
+ return;
708
+ }
709
+
710
+ // Cacher le template original
711
+ template.style.display = 'none';
712
+
713
+ // Marquer l'élément comme traité par le module YouTube
714
+ element.setAttribute('data-bb-youtube-processed', 'true');
715
+
716
+ // Vérifier le cache d'abord
717
+ const cacheKey = `youtube_${channelId}_${videoCount}_${allowShorts}_${language}`;
718
+ const cachedData = this.cache.get(cacheKey);
719
+
720
+ if (cachedData && cachedData.value) {
721
+ // Données YouTube récupérées du cache (économie API)
722
+ this.generateYouTubeFeed(container, template, cachedData.value, allowShorts, language);
723
+ return;
724
+ }
725
+
726
+ // Vérifier si un appel API est déjà en cours pour cette clé
727
+ const loadingKey = `loading_${cacheKey}`;
728
+ if (window[loadingKey]) {
729
+ // Attendre que l'autre appel se termine
730
+ const checkLoading = () => {
731
+ if (!window[loadingKey]) {
732
+ // L'autre appel est terminé, vérifier le cache
733
+ const newCachedData = this.cache.get(cacheKey);
734
+ if (newCachedData && newCachedData.value) {
735
+ this.generateYouTubeFeed(container, template, newCachedData.value, allowShorts, language);
736
+ } else {
737
+ container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Erreur de chargement</div>';
846
738
  }
847
-
848
- // Sauvegarder en cache pour 24h
849
- this.cache.set(cacheKey, data);
850
- bbContents.utils.log('Données YouTube mises en cache pour 24h (économie API)');
851
-
852
- this.generateYouTubeFeed(container, template, data, allowShorts, language);
853
- })
854
- .catch(error => {
855
- bbContents.utils.log('Erreur dans le module youtube:', error);
856
-
857
- // En cas d'erreur, essayer de récupérer du cache même expiré
858
- const expiredCache = localStorage.getItem(cacheKey);
859
- if (expiredCache) {
860
- try {
861
- const cachedData = JSON.parse(expiredCache);
862
- bbContents.utils.log('Utilisation du cache expiré en cas d\'erreur API');
863
- this.generateYouTubeFeed(container, template, cachedData.value, allowShorts);
864
- return;
865
- } catch (e) {
866
- // Ignorer les erreurs de parsing
867
- }
739
+ } else {
740
+ setTimeout(checkLoading, 100);
741
+ }
742
+ };
743
+ checkLoading();
744
+ return;
745
+ }
746
+
747
+ // Marquer qu'un appel API est en cours
748
+ window[loadingKey] = true;
749
+
750
+ // Afficher un loader
751
+ container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Chargement des vidéos YouTube...</div>';
752
+
753
+ // Appeler l'API via le Worker
754
+ fetch(`${endpoint}?channelId=${channelId}&maxResults=${videoCount}&allowShorts=${allowShorts}`)
755
+ .then(response => {
756
+ if (!response.ok) {
757
+ throw new Error(`HTTP ${response.status}`);
758
+ }
759
+ return response.json();
760
+ })
761
+ .then(data => {
762
+ if (data.error) {
763
+ throw new Error(data.error.message || 'Erreur API YouTube');
764
+ }
765
+
766
+ // Sauvegarder en cache pour 24h
767
+ this.cache.set(cacheKey, data);
768
+ // Données YouTube mises en cache pour 24h (économie API)
769
+
770
+ this.generateYouTubeFeed(container, template, data, allowShorts, language);
771
+
772
+ // Libérer le verrou
773
+ window[loadingKey] = false;
774
+ })
775
+ .catch(error => {
776
+ console.error('Erreur API YouTube:', error);
777
+ // Erreur dans le module youtube
778
+
779
+ // Libérer le verrou en cas d'erreur
780
+ window[loadingKey] = false;
781
+
782
+ // En cas d'erreur, essayer de récupérer du cache même expiré
783
+ const expiredCache = localStorage.getItem(cacheKey);
784
+ if (expiredCache) {
785
+ try {
786
+ const cachedData = JSON.parse(expiredCache);
787
+ // Utilisation du cache expiré en cas d'erreur API
788
+ this.generateYouTubeFeed(container, template, cachedData.value, allowShorts, language);
789
+ return;
790
+ } catch (e) {
791
+ // Ignorer les erreurs de parsing
868
792
  }
869
-
870
- container.innerHTML = `<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Erreur de chargement</strong><br>${error.message}</div>`;
871
- });
872
- });
793
+ }
794
+
795
+ container.innerHTML = `<div style="padding: 20px; background: #fef2f2; border: 1px solid #fecaca; border-radius: 8px; color: #dc2626;"><strong>Erreur de chargement</strong><br>${error.message}</div>`;
796
+ });
873
797
  },
874
798
 
875
799
  generateYouTubeFeed: function(container, template, data, allowShorts, language = 'fr') {
876
- if (!data.items || data.items.length === 0) {
800
+ if (!data || !data.items || data.items.length === 0) {
877
801
  container.innerHTML = '<div style="padding: 20px; text-align: center; color: #6b7280;">Aucune vidéo trouvée</div>';
878
802
  return;
879
803
  }
880
804
 
881
805
  // Les vidéos sont déjà filtrées par l'API YouTube selon allowShorts
882
806
  let videos = data.items;
883
- bbContents.utils.log(`Vidéos reçues de l'API: ${videos.length} (allowShorts: ${allowShorts})`);
807
+ // Vidéos reçues de l'API
884
808
 
885
809
  // Vider le conteneur (en préservant les éléments marquee)
886
810
  const marqueeElements = container.querySelectorAll('[data-bb-marquee-processed]');
@@ -907,7 +831,7 @@
907
831
  container.appendChild(clone);
908
832
  });
909
833
 
910
- bbContents.utils.log(`YouTube Feed généré: ${videos.length} vidéos`);
834
+ // YouTube Feed généré
911
835
  },
912
836
 
913
837
  fillVideoData: function(element, videoId, snippet, language = 'fr') {
@@ -953,10 +877,10 @@
953
877
 
954
878
  // Debug: logger la qualité utilisée (en mode debug seulement)
955
879
  if (bbContents.config.debug) {
956
- bbContents.utils.log(`Thumbnail optimisée pour ${snippet.title}: ${bestQuality}`);
880
+ // Thumbnail optimisée
957
881
  }
958
882
  } else {
959
- bbContents.utils.log('Aucune thumbnail disponible pour:', snippet.title);
883
+ // Aucune thumbnail disponible
960
884
  }
961
885
  }
962
886
 
@@ -1067,7 +991,7 @@
1067
991
  });
1068
992
 
1069
993
  if (cleaned > 0) {
1070
- bbContents.utils.log(`Cache YouTube nettoyé: ${cleaned} entrées supprimées`);
994
+ // Cache YouTube nettoyé
1071
995
  }
1072
996
  } catch (e) {
1073
997
  // Ignorer les erreurs de nettoyage
@@ -1078,6 +1002,15 @@
1078
1002
 
1079
1003
  // Exposer globalement
1080
1004
  window.bbContents = bbContents;
1005
+
1006
+ // Méthode globale pour configurer YouTube après le chargement
1007
+ window.configureYouTube = function(endpoint) {
1008
+ if (bbContents) {
1009
+ bbContents.config.youtubeEndpoint = endpoint;
1010
+ // Réinitialiser les modules YouTube
1011
+ bbContents.reinit();
1012
+ }
1013
+ };
1081
1014
 
1082
1015
  // Initialisation automatique avec délai pour éviter le blocage
1083
1016
  function initBBContents() {
@@ -1105,7 +1038,7 @@
1105
1038
  // Vérifier s'il y a des éléments non initialisés
1106
1039
  const unprocessedMarquees = document.querySelectorAll('[bb-marquee]:not([data-bb-marquee-processed])');
1107
1040
  if (unprocessedMarquees.length > 0) {
1108
- bbContents.utils.log('Éléments marquee non initialisés détectés après load, réinitialisation...');
1041
+ // Éléments marquee non initialisés détectés après load, réinitialisation
1109
1042
  bbContents.reinit();
1110
1043
  }
1111
1044
 
@@ -1113,7 +1046,7 @@
1113
1046
  const allImages = document.querySelectorAll('img');
1114
1047
  const unloadedImages = Array.from(allImages).filter(img => !img.complete || img.naturalHeight === 0);
1115
1048
  if (unloadedImages.length > 0) {
1116
- bbContents.utils.log('Images non chargées détectées:', unloadedImages.length, '- attente supplémentaire...');
1049
+ // Images non chargées détectées, attente supplémentaire
1117
1050
  setTimeout(() => {
1118
1051
  bbContents.reinit();
1119
1052
  }, 1000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bebranded/bb-contents",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Contenus additionnels français pour Webflow",
5
5
  "main": "bb-contents.js",
6
6
  "scripts": {