nocopyrightsounds-widget 1.2.0 → 1.4.0

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 (3) hide show
  1. package/README.md +37 -28
  2. package/package.json +1 -1
  3. package/src/index.js +58 -42
package/README.md CHANGED
@@ -7,18 +7,19 @@ Un lecteur musical flottant, élégant et hautement personnalisable pour intégr
7
7
 
8
8
  Conçu pour les développeurs modernes : léger, persistant entre les changements de pages, et entièrement paramétrable via JavaScript et CSS.
9
9
 
10
- ![NCS Widget Preview](https://raw.githubusercontent.com/floriangobin/nocopyrightsounds-widget/main/preview.png) *(Ajoutez une capture d'écran de votre widget dans votre dépôt GitHub et remplacez ce lien plus tard !)*
10
+ ![NCS Widget Preview](https://raw.githubusercontent.com/floriangobin/nocopyrightsounds-widget/main/preview.png) *(Ajoutez une capture d'écran de votre widget dans votre dépôt GitHub et remplacez ce lien !)*
11
11
 
12
12
  ---
13
13
 
14
14
  ## ✨ Fonctionnalités
15
15
 
16
- * ⚡ **Zéro Latence :** Algorithme de préchargement (buffering) intelligent en arrière-plan pour des transitions instantanées entre les morceaux.
17
- * 💾 **Persistance d'état :** Mémorise la piste en cours, le volume, la progression et l'état d'ouverture du widget d'une page à l'autre via `localStorage`.
18
- * 🎨 **Design Premium :** Support natif des modes clair/sombre, personnalisation des couleurs, et effet **Glassmorphism** (verre dépoli).
19
- * 🎛️ **Contrôles Complets :** Boutons Suivant/Précédent avec historique, contrôle du volume, Mute, et barre de progression cliquable.
16
+ * ⚡ **Zéro Latence :** Algorithme de préchargement (buffering) en arrière-plan pour des transitions instantanées.
17
+ * 💾 **Persistance d'état :** Mémorise la piste en cours, le volume, la progression et l'état du widget d'une page à l'autre via `localStorage`.
18
+ * 🎨 **Design Premium & Glassmorphism :** Support natif des modes clair/sombre, personnalisation des couleurs et effet de verre dépoli.
19
+ * 🎛️ **Contrôles Complets :** Boutons Suivant/Précédent avec historique, contrôle du volume, Mute, et barre de progression.
20
20
  * 🎵 **Catalogue Complet :** Navigation aléatoire intelligente parmi les 60+ genres historiques de NCS.
21
- * 👁️ **UI Modulaire :** Possibilité de masquer des éléments (téléchargement, visualizer) pour un rendu minimaliste.
21
+ * 🔘 **Bouton Réduit Sur Mesure :** Transformez l'icône flottante en cercle, en carré, changez l'émoji ou mettez-y du texte !
22
+ * 🔌 **Prêt à l'emploi (Plug & Play) :** API backend officielle intégrée par défaut. Zéro configuration requise !
22
23
 
23
24
  ---
24
25
 
@@ -40,29 +41,32 @@ npm install nocopyrightsounds-widget
40
41
 
41
42
  ## 🚀 Utilisation Rapide
42
43
 
43
- ### Exemple basique
44
+ ### Exemple basique (Zéro configuration)
44
45
  \`\`\`html
45
46
  <script type="module">
46
47
  import NCSWidget from 'https://cdn.jsdelivr.net/npm/nocopyrightsounds-widget@latest/src/index.js';
48
+
49
+ // Le widget s'occupe de tout avec les paramètres par défaut !
47
50
  const player = new NCSWidget();
48
51
  </script>
49
52
  \`\`\`
50
53
 
51
- ### Exemple Avancé (Toutes les options)
54
+ ### Exemple Avancé (Design sur mesure)
52
55
  \`\`\`javascript
53
56
  const widget = new NCSWidget({
54
- position: 'bottom-left', // 'bottom-right', 'top-left', 'top-right'
55
- offset: '30px', // Distance par rapport au bord de l'écran
56
- theme: 'dark', // 'dark' ou 'light'
57
- primaryColor: '#9d4edd', // Couleur principale (ex: Violet)
58
- glassmorphism: true, // Active l'effet de transparence floutée
59
- borderRadius: '12px', // Arrondi de la fenêtre
60
- fontFamily: "'Courier New', monospace", // Police d'écriture personnalisée
57
+ position: 'bottom-left',
58
+ theme: 'dark',
59
+ primaryColor: '#ff0055',
60
+ glassmorphism: true,
61
+ borderRadius: '12px',
61
62
  defaultGenre: '10', // Démarre sur la House (ID: 10)
62
- startVolume: 0.3, // Volume initial à 30%
63
- hideDownload: true, // Cache le bouton de téléchargement
64
- hideVisualizer: false, // Garde l'animation sonore
65
- autoOpen: true // Ouvre le lecteur automatiquement à la 1ère visite
63
+
64
+ // 🔥 Personnalisation du bouton réduit
65
+ minimizedIcon: '🎵 Play', // Texte au lieu d'un émoji
66
+ minimizedSize: '80px', // Bouton plus large
67
+ minimizedRadius: '12px', // Bords arrondis (au lieu d'un cercle parfait)
68
+ minimizedBg: '#222222', // Fond sombre
69
+ minimizedColor: '#ff0055' // Texte coloré
66
70
  });
67
71
  \`\`\`
68
72
 
@@ -72,19 +76,23 @@ const widget = new NCSWidget({
72
76
 
73
77
  | Option | Type | Défaut | Description |
74
78
  | :--- | :--- | :--- | :--- |
75
- | \`position\` | String | \`'bottom-right'\` | Position à l'écran (\`bottom-right\`, \`bottom-left\`, \`top-right\`, \`top-left\`). |
79
+ | \`position\` | String | \`'bottom-right'\` | Position (\`bottom-right\`, \`bottom-left\`, \`top-right\`, \`top-left\`). |
76
80
  | \`offset\` | String | \`'25px'\` | Marge par rapport au bord de l'écran. |
77
81
  | \`theme\` | String | \`'dark'\` | Thème de base de l'interface (\`'dark'\` ou \`'light'\`). |
78
- | \`primaryColor\` | String | \`'#1DB954'\` | Couleur principale (Bouton d'ouverture, slider, visualizer). |
79
- | \`glassmorphism\`| Boolean | \`false\` | Active un fond semi-transparent avec flou d'arrière-plan (backdrop-filter). |
82
+ | \`primaryColor\` | String | \`'#1DB954'\` | Couleur principale (Sliders, visualizer). |
83
+ | \`glassmorphism\`| Boolean | \`false\` | Active un fond semi-transparent avec flou d'arrière-plan. |
80
84
  | \`borderRadius\` | String | \`'16px'\` | Rayon des bordures du lecteur étendu. |
81
85
  | \`fontFamily\` | String | \`'system-ui...'\`| Typographie utilisée dans tout le widget. |
86
+ | \`minimizedIcon\`| String | \`'🎧'\` | Icône ou texte du bouton réduit. |
87
+ | \`minimizedSize\`| String | \`'55px'\` | Largeur/Hauteur du bouton réduit. |
88
+ | \`minimizedRadius\`| String| \`'50%'\` | Arrondi du bouton réduit (\`50%\` = rond, \`8px\` = carré arrondi). |
89
+ | \`minimizedBg\` | String | *primaryColor*| Couleur de fond spécifique au bouton réduit. |
90
+ | \`minimizedColor\`| String| \`'#ffffff'\` | Couleur de l'icône/texte du bouton réduit. |
82
91
  | \`hideDownload\` | Boolean | \`false\` | Masque l'icône de téléchargement direct. |
83
- | \`hideVisualizer\`| Boolean | \`false\` | Masque les 3 barres animées à côté du titre. |
84
- | \`autoOpen\` | Boolean | \`false\` | Déploie le widget automatiquement lors de la première visite. |
92
+ | \`hideVisualizer\`| Boolean | \`false\` | Masque les barres animées à côté du titre. |
93
+ | \`autoOpen\` | Boolean | \`false\` | Déploie le widget automatiquement à la 1ère visite. |
85
94
  | \`defaultGenre\` | String | \`'all'\` | L'ID du genre au démarrage (ex: \`'10'\` pour House). |
86
- | \`startVolume\` | Number | \`0.5\` | Volume initial entre 0.0 et 1.0 (surchargé si l'utilisateur a déjà un cache). |
87
- | \`apiUrl\` | String | *https://www.wordreference.com/definition/interne* | URL de l'API Backend. |
95
+ | \`startVolume\` | Number | \`0.5\` | Volume initial entre 0.0 et 1.0. |
88
96
 
89
97
  ---
90
98
 
@@ -104,7 +112,8 @@ Le widget expose des **Variables CSS** (Custom Properties) rattachées à l'ID `
104
112
 
105
113
  ## 🏗️ Architecture & Backend
106
114
 
107
- En raison des restrictions CORS strictes sur le web moderne, un navigateur web ne peut pas interroger directement le site de NCS. Ce widget s'appuie donc sur une API Backend Node.js qui sert de relais de données (Proxy).
115
+ En raison des restrictions CORS strictes sur le web moderne, un navigateur web ne peut pas interroger directement le site de NCS. Ce widget s'appuie donc sur une API Backend Node.js.
116
+ **Une instance publique hébergée sur Render est configurée par défaut dans le widget pour un usage "Plug & Play".**
108
117
 
109
118
  ---
110
119
 
@@ -112,4 +121,4 @@ En raison des restrictions CORS strictes sur le web moderne, un navigateur web n
112
121
 
113
122
  Distribué sous la licence MIT. Voir `LICENSE` pour plus d'informations.
114
123
 
115
- **Avertissement :** Ce projet n'est pas affilié à NoCopyrightSounds. Toutes les musiques diffusées par ce widget appartiennent à leurs créateurs respectifs et à NCS. Veuillez respecter les conditions d'utilisation de NoCopyrightSounds lors de l'utilisation de leurs œuvres.
124
+ **Avertissement :** Ce projet n'est pas affilié à NoCopyrightSounds. Toutes les musiques diffusées appartiennent à leurs créateurs respectifs et à NCS.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nocopyrightsounds-widget",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "Un widget musical persistant pour site web utilisant l'API NoCopyrightSounds",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -2,23 +2,30 @@ import { ncsGenres } from './genres.js';
2
2
 
3
3
  class NCSWidget {
4
4
  constructor(userOptions = {}) {
5
- // 🛠️ 1. Les TOUTES NOUVELLES options de personnalisation
6
5
  const defaultOptions = {
7
6
  position: 'bottom-right',
8
- apiUrl: 'https://VOTRE-URL-RENDER.onrender.com', // ⚠️ METTEZ VOTRE URL RENDER ICI
7
+ apiUrl: 'https://ncs-backend-api.onrender.com',
9
8
  theme: 'dark',
10
9
  primaryColor: '#1DB954',
11
10
  defaultGenre: 'all',
12
11
  startVolume: 0.5,
13
- offset: '25px',
12
+ offsetX: '25px', // Séparé pour plus de précision (ex: gauche/droite)
13
+ offsetY: '25px', // Séparé (ex: haut/bas)
14
14
  zIndex: 99999,
15
- // --- NOUVEAUTÉS V1.2.0 ---
16
- glassmorphism: false, // Active l'effet verre dépoli
17
- borderRadius: '16px', // Arrondi des angles du panneau
15
+ glassmorphism: false,
16
+ borderRadius: '16px',
18
17
  fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
19
- hideDownload: false, // Masquer le bouton de téléchargement
20
- hideVisualizer: false,// Masquer les barres animées
21
- autoOpen: false // Ouvrir le widget par défaut à la 1ère visite
18
+ hideDownload: false,
19
+ hideVisualizer: false,
20
+ autoOpen: false,
21
+
22
+ // 🎛️ Bouton Réduit Ultra-Configurable
23
+ minimizedIcon: '🎧',
24
+ minWidth: '55px', // Largeur séparée
25
+ minHeight: '55px', // Hauteur séparée
26
+ minRadius: '50%',
27
+ minBg: null,
28
+ minColor: '#ffffff'
22
29
  };
23
30
 
24
31
  this.options = { ...defaultOptions, ...userOptions };
@@ -43,13 +50,8 @@ class NCSWidget {
43
50
  this.savedTitle = localStorage.getItem('ncs_currentTitle') || null;
44
51
  this.savedArtists = localStorage.getItem('ncs_currentArtists') || null;
45
52
 
46
- // Logique d'auto-ouverture : on priorise le choix sauvegardé, sinon l'option autoOpen
47
53
  const savedState = localStorage.getItem('ncs_isOpen');
48
- if (savedState !== null) {
49
- this.isWidgetOpen = savedState === 'true';
50
- } else {
51
- this.isWidgetOpen = this.options.autoOpen;
52
- }
54
+ this.isWidgetOpen = savedState !== null ? savedState === 'true' : this.options.autoOpen;
53
55
  }
54
56
 
55
57
  this.initDOM();
@@ -69,10 +71,10 @@ class NCSWidget {
69
71
 
70
72
  this.container = document.createElement('div');
71
73
  this.container.id = 'ncs-persistent-widget';
74
+ // Ajout d'une classe globale si le widget est ouvert dès le départ
75
+ if(this.isWidgetOpen) this.container.classList.add('ncs-is-open');
72
76
 
73
77
  const isLight = this.options.theme === 'light';
74
-
75
- // 🎨 Gestion dynamique des couleurs pour le Glassmorphism
76
78
  const baseBgColor = isLight ? '255, 255, 255' : '24, 24, 24';
77
79
  const finalBg = this.options.glassmorphism ? `rgba(${baseBgColor}, 0.75)` : (isLight ? '#ffffff' : '#181818');
78
80
  const backdropFilter = this.options.glassmorphism ? 'blur(12px)' : 'none';
@@ -102,26 +104,33 @@ class NCSWidget {
102
104
  --ncs-radius: ${this.options.borderRadius};
103
105
  --ncs-font: ${this.options.fontFamily};
104
106
 
107
+ --ncs-min-w: ${this.options.minWidth};
108
+ --ncs-min-h: ${this.options.minHeight};
109
+ --ncs-min-radius: ${this.options.minRadius};
110
+ --ncs-min-bg: ${this.options.minBg || this.options.primaryColor};
111
+ --ncs-min-color: ${this.options.minColor};
112
+ --ncs-cover-img: url(''); /* Injecté en JS pour le design Vinyle */
113
+
105
114
  position: fixed;
106
115
  ${this.getPositionStyles()}
107
116
  z-index: ${this.options.zIndex};
108
117
  font-family: var(--ncs-font);
109
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
110
118
  }
111
119
 
112
- .ncs-minimized { width: 55px; height: 55px; border-radius: 50%; background: var(--ncs-primary); cursor: pointer; display: flex; justify-content: center; align-items: center; box-shadow: 0 6px 15px rgba(0,0,0, 0.2); font-size: 24px; transition: transform 0.2s; color: white; }
113
- .ncs-minimized:hover { transform: scale(1.1); }
114
- .ncs-minimized.hidden { display: none; }
120
+ .ncs-minimized { width: var(--ncs-min-w); height: var(--ncs-min-h); border-radius: var(--ncs-min-radius); background: var(--ncs-min-bg); cursor: pointer; display: flex; justify-content: center; align-items: center; box-shadow: 0 6px 15px rgba(0,0,0, 0.2); font-size: calc(min(var(--ncs-min-w), var(--ncs-min-h)) * 0.45); transition: all 0.3s ease; color: var(--ncs-min-color); overflow: hidden; background-size: cover; background-position: center; }
121
+ .ncs-minimized:hover { transform: scale(1.05); }
122
+ /* Transition plus douce pour l'affichage */
123
+ .ncs-minimized.hidden { opacity: 0; pointer-events: none; position: absolute; }
115
124
 
116
- .ncs-expanded { width: 320px; background: var(--ncs-bg); color: var(--ncs-text); border-radius: var(--ncs-radius); padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); display: none; border: 1px solid var(--ncs-border); backdrop-filter: ${backdropFilter}; -webkit-backdrop-filter: ${backdropFilter}; }
117
- .ncs-expanded.active { display: block; animation: ncsFadeIn 0.3s ease; }
125
+ .ncs-expanded { width: 320px; background: var(--ncs-bg); color: var(--ncs-text); border-radius: var(--ncs-radius); padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); opacity: 0; pointer-events: none; position: absolute; bottom: 0; right: 0; border: 1px solid var(--ncs-border); backdrop-filter: ${backdropFilter}; -webkit-backdrop-filter: ${backdropFilter}; transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); transform: translateY(20px) scale(0.95); }
126
+ /* Effet Tiroir par défaut lors de l'ouverture */
127
+ .ncs-expanded.active { opacity: 1; pointer-events: auto; position: relative; transform: translateY(0) scale(1); }
118
128
 
119
129
  .ncs-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
120
130
  .ncs-header strong { font-size: 14px; font-weight: 600; color: var(--ncs-text-muted); letter-spacing: 1px; text-transform: uppercase; display: flex; align-items: center; gap: 8px; }
121
131
  .ncs-close-btn { background: transparent; border: none; color: var(--ncs-text-muted); font-size: 18px; cursor: pointer; padding: 0; transition: color 0.2s; }
122
132
  .ncs-close-btn:hover { color: var(--ncs-text); }
123
133
 
124
- /* Masquage conditionnel du Visualizer */
125
134
  .ncs-visualizer { display: ${this.options.hideVisualizer ? 'none' : 'flex'}; gap: 2px; height: 12px; align-items: flex-end; opacity: 0; transition: opacity 0.3s; }
126
135
  .ncs-visualizer.playing { opacity: 1; }
127
136
  .ncs-bar { width: 3px; background: var(--ncs-primary); border-radius: 2px; animation: bounce 0.5s infinite alternate; }
@@ -153,20 +162,16 @@ class NCSWidget {
153
162
  .ncs-volume-container { display: flex; align-items: center; gap: 8px; color: var(--ncs-text-muted); flex: 1; margin-right: 15px; }
154
163
  #ncs-mute-btn { cursor: pointer; transition: transform 0.1s; user-select: none; }
155
164
  #ncs-mute-btn:hover { transform: scale(1.1); }
156
-
157
- /* Masquage conditionnel du bouton Téléchargement */
158
165
  .ncs-download-btn { display: ${this.options.hideDownload ? 'none' : 'block'}; color: var(--ncs-text-muted); text-decoration: none; font-size: 18px; transition: color 0.2s; }
159
166
  .ncs-download-btn:hover { color: var(--ncs-primary); }
160
-
161
- @keyframes ncsFadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
162
167
  `;
163
168
 
164
- const genresOptionsHTML = ncsGenres.map(genre =>
165
- `<option value="${genre.value}">${genre.label}</option>`
166
- ).join('');
169
+ const genresOptionsHTML = ncsGenres.map(genre => `<option value="${genre.value}">${genre.label}</option>`).join('');
167
170
 
168
171
  this.container.innerHTML = `
169
- <div class="ncs-minimized ${this.isWidgetOpen ? 'hidden' : ''}">🎧</div>
172
+ <div class="ncs-minimized ${this.isWidgetOpen ? 'hidden' : ''}">
173
+ <span class="ncs-min-content">${this.options.minimizedIcon}</span>
174
+ </div>
170
175
  <div class="ncs-expanded ${this.isWidgetOpen ? 'active' : ''}">
171
176
  <div class="ncs-header">
172
177
  <strong>NCS Player
@@ -182,9 +187,7 @@ class NCSWidget {
182
187
  <div class="ncs-details">
183
188
  <div id="ncs-track-name">Chargement...</div>
184
189
  <div id="ncs-artists">Artiste(s)</div>
185
- <select id="ncs-genre">
186
- ${genresOptionsHTML}
187
- </select>
190
+ <select id="ncs-genre">${genresOptionsHTML}</select>
188
191
  </div>
189
192
  </div>
190
193
 
@@ -232,12 +235,13 @@ class NCSWidget {
232
235
  }
233
236
 
234
237
  getPositionStyles() {
235
- const offset = this.options.offset;
238
+ const x = this.options.offsetX;
239
+ const y = this.options.offsetY;
236
240
  const positions = {
237
- 'bottom-right': `bottom: ${offset}; right: ${offset};`,
238
- 'bottom-left': `bottom: ${offset}; left: ${offset};`,
239
- 'top-right': `top: ${offset}; right: ${offset};`,
240
- 'top-left': `top: ${offset}; left: ${offset};`
241
+ 'bottom-right': `bottom: ${y}; right: ${x};`,
242
+ 'bottom-left': `bottom: ${y}; left: ${x};`,
243
+ 'top-right': `top: ${y}; right: ${x};`,
244
+ 'top-left': `top: ${y}; left: ${x};`
241
245
  };
242
246
  return positions[this.options.position] || positions['bottom-right'];
243
247
  }
@@ -257,11 +261,13 @@ class NCSWidget {
257
261
  this.playBtn.innerHTML = '⏸';
258
262
  this.playBtn.classList.add('paused');
259
263
  this.visualizer.classList.add('playing');
264
+ this.container.classList.add('ncs-is-playing'); // 🎵 CLASSE MAGIQUE POUR LES DESIGNERS
260
265
  });
261
266
  this.audio.addEventListener('pause', () => {
262
267
  this.playBtn.innerHTML = '▶';
263
268
  this.playBtn.classList.remove('paused');
264
269
  this.visualizer.classList.remove('playing');
270
+ this.container.classList.remove('ncs-is-playing');
265
271
  });
266
272
  this.audio.addEventListener('timeupdate', () => this.updateProgress());
267
273
  this.audio.addEventListener('loadedmetadata', () => {
@@ -314,9 +320,11 @@ class NCSWidget {
314
320
  if (isOpen) {
315
321
  this.minimized.classList.add('hidden');
316
322
  this.expanded.classList.add('active');
323
+ this.container.classList.add('ncs-is-open');
317
324
  } else {
318
325
  this.minimized.classList.remove('hidden');
319
326
  this.expanded.classList.remove('active');
327
+ this.container.classList.remove('ncs-is-open');
320
328
  }
321
329
  localStorage.setItem('ncs_isOpen', isOpen);
322
330
  }
@@ -391,7 +399,11 @@ class NCSWidget {
391
399
  this.audio.src = this.savedTrack;
392
400
  this.trackName.innerText = this.savedTitle;
393
401
  this.artistsName.innerText = this.savedArtists || "NCS Release";
394
- if (this.savedCover) this.coverImg.src = this.savedCover;
402
+ if (this.savedCover) {
403
+ this.coverImg.src = this.savedCover;
404
+ // Injecter l'image dans le CSS pour les hackers de design !
405
+ this.container.style.setProperty('--ncs-cover-img', `url('${this.savedCover}')`);
406
+ }
395
407
  this.downloadBtn.href = this.savedTrack;
396
408
 
397
409
  this.trackHistory = [{
@@ -411,7 +423,11 @@ class NCSWidget {
411
423
  this.artistsName.innerText = artistes;
412
424
  this.artistsName.title = artistes;
413
425
 
414
- if (track.coverUrl) this.coverImg.src = track.coverUrl;
426
+ if (track.coverUrl) {
427
+ this.coverImg.src = track.coverUrl;
428
+ // 💿 INJECTION POUR LE HACK VINYLE :
429
+ this.container.style.setProperty('--ncs-cover-img', `url('${track.coverUrl}')`);
430
+ }
415
431
  this.downloadBtn.href = track.audioUrl;
416
432
 
417
433
  if (addToHistory) {