nocopyrightsounds-widget 1.0.7 → 1.1.1

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 +123 -10
  2. package/package.json +10 -3
  3. package/src/index.js +84 -102
package/README.md CHANGED
@@ -1,20 +1,133 @@
1
- # NoCopyrightSounds Widget 🎵
1
+ # 🎧 NoCopyrightSounds (NCS) Web Widget
2
2
 
3
- Un module persistant et personnalisable pour intégrer de la musique NCS sur n'importe quel site web.
3
+ [![NPM Version](https://img.shields.io/npm/v/nocopyrightsounds-widget.svg?style=flat-square&color=1DB954)](https://www.npmjs.com/package/nocopyrightsounds-widget)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
4
5
 
5
- ## Installation
6
+ Un lecteur musical flottant, élégant et hautement personnalisable pour intégrer facilement les musiques libres de droits de **NoCopyrightSounds** à n'importe quel site web.
6
7
 
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
+
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 !)*
11
+
12
+ ---
13
+
14
+ ## ✨ Fonctionnalités
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
+ * 🎨 **Thèmes & Couleurs :** Support natif des modes clair (`light`) et sombre (`dark`), avec personnalisation de la couleur principale.
19
+ * 🎛️ **Contrôles Complets :** Boutons Suivant/Précédent avec historique, contrôle du volume, Mute, et barre de progression cliquable.
20
+ * 🎵 **+60 Genres :** Navigation aléatoire intelligente parmi tout le catalogue historique de NCS (House, Dubstep, Chill, etc.).
21
+ * ⬇️ **Téléchargement :** Bouton intégré pour récupérer directement le fichier MP3 officiel.
22
+
23
+ ---
24
+
25
+ ## 📦 Installation
26
+
27
+ ### Via NPM (Recommandé pour React, Vue, Angular...)
7
28
  \`\`\`bash
8
29
  npm install nocopyrightsounds-widget
9
30
  \`\`\`
10
31
 
11
- ## Utilisation
32
+ ### Via CDN (Pour les sites HTML classiques / Vanilla JS)
33
+ \`\`\`html
34
+ <script type="module">
35
+ import NCSWidget from 'https://cdn.jsdelivr.net/npm/nocopyrightsounds-widget@latest/src/index.js';
36
+ </script>
37
+ \`\`\`
38
+
39
+ ---
12
40
 
13
- \`\`\`javascript
41
+ ## 🚀 Utilisation Rapide
42
+
43
+ ### Exemple en Vanilla JS (HTML)
44
+ \`\`\`html
45
+ <body>
46
+ <script type="module">
47
+ import NCSWidget from 'https://cdn.jsdelivr.net/npm/nocopyrightsounds-widget@latest/src/index.js';
48
+
49
+ // Initialisation basique
50
+ const player = new NCSWidget();
51
+ </script>
52
+ </body>
53
+ \`\`\`
54
+
55
+ ### Exemple dans React (Next.js, Vite...)
56
+ \`\`\`jsx
57
+ import { useEffect } from 'react';
14
58
  import NCSWidget from 'nocopyrightsounds-widget';
15
59
 
16
- const widget = new NCSWidget({
17
- position: 'bottom-right', // 'bottom-left', 'top-right', 'top-left'
18
- apiUrl: 'https://ncs-backend-api.onrender.com' // L'URL de votre API
19
- });
20
- \`\`\`
60
+ export default function App() {
61
+ useEffect(() => {
62
+ const widget = new NCSWidget({
63
+ position: 'bottom-right',
64
+ theme: 'dark',
65
+ primaryColor: '#1DB954'
66
+ });
67
+
68
+ // Nettoyage lors du démontage du composant
69
+ return () => {
70
+ const el = document.getElementById('ncs-persistent-widget');
71
+ if (el) el.remove();
72
+ };
73
+ }, []);
74
+
75
+ return (
76
+ <div>
77
+ <h1>Mon Super Site</h1>
78
+ </div>
79
+ );
80
+ }
81
+ \`\`\`
82
+
83
+ ---
84
+
85
+ ## ⚙️ Configuration (Options de l'objet)
86
+
87
+ Vous pouvez passer un objet d'options au constructeur pour personnaliser le comportement du widget :
88
+
89
+ | Option | Type | Défaut | Description |
90
+ | :--- | :--- | :--- | :--- |
91
+ | \`position\` | String | \`'bottom-right'\` | Position à l'écran (\`bottom-right\`, \`bottom-left\`, \`top-right\`, \`top-left\`). |
92
+ | \`offset\` | String | \`'25px'\` | Marge par rapport au bord de l'écran. |
93
+ | \`theme\` | String | \`'dark'\` | Thème de base de l'interface (\`'dark'\` ou \`'light'\`). |
94
+ | \`primaryColor\` | String | \`'#1DB954'\` | Couleur principale (Bouton d'ouverture, slider, visualizer). |
95
+ | \`defaultGenre\` | String | \`'all'\` | L'ID du genre au démarrage (ex: \`'10'\` pour House). |
96
+ | \`startVolume\` | Number | \`0.5\` | Volume initial entre 0.0 et 1.0 (surchargé si l'utilisateur a déjà un cache). |
97
+ | \`zIndex\` | Number | \`99999\` | Profondeur d'affichage CSS (z-index). |
98
+ | \`apiUrl\` | String | *https://www.wordreference.com/definition/interne* | URL de l'API Backend. Vous pouvez héberger la vôtre si besoin. |
99
+
100
+ ---
101
+
102
+ ## 🎨 Personnalisation CSS Avancée
103
+
104
+ Si les options du constructeur ne suffisent pas, le widget expose des **Variables CSS** (Custom Properties) rattachées à l'ID `#ncs-persistent-widget`. Vous pouvez les surcharger directement dans la feuille de style de votre site :
105
+
106
+ \`\`\`css
107
+ /* Dans le fichier style.css de votre site web */
108
+ #ncs-persistent-widget {
109
+ --ncs-bg: #000000; /* Fond du widget (Noir pur) */
110
+ --ncs-border: #333333; /* Couleur de la bordure */
111
+ --ncs-primary: #ff0055; /* Remplace le vert par du rose fluo */
112
+ --ncs-panel-bg: #111111; /* Fond des éléments internes (images, selects) */
113
+ font-family: 'Roboto', sans-serif; /* Changement de police */
114
+ border-radius: 0px; /* Retirer les coins arrondis */
115
+ }
116
+ \`\`\`
117
+
118
+ ---
119
+
120
+ ## 🏗️ Architecture & Backend
121
+
122
+ En raison des restrictions CORS strictes sur le web moderne, un navigateur web ne peut pas interroger directement le site de NCS.
123
+ Ce widget s'appuie donc sur une API Backend Node.js qui sert de relais de données (Proxy).
124
+
125
+ *Note : Une API publique par défaut est fournie avec ce widget pour un usage immédiat. Pour des environnements de production à fort trafic, il est recommandé de déployer votre propre instance du serveur relais.*
126
+
127
+ ---
128
+
129
+ ## 📄 Licence
130
+
131
+ Distribué sous la licence MIT. Voir `LICENSE` pour plus d'informations.
132
+
133
+ **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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nocopyrightsounds-widget",
3
- "version": "1.0.7",
3
+ "version": "1.1.1",
4
4
  "description": "Un widget musical persistant pour site web utilisant l'API NoCopyrightSounds",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -11,7 +11,14 @@
11
11
  "type": "git",
12
12
  "url": "https://github.com/floriangobin/nocopyrightsounds-widget.git"
13
13
  },
14
- "keywords": ["ncs", "music", "widget", "audio", "player", "nocopyrightsounds"],
14
+ "keywords": [
15
+ "ncs",
16
+ "music",
17
+ "widget",
18
+ "audio",
19
+ "player",
20
+ "nocopyrightsounds"
21
+ ],
15
22
  "author": "GOBIN Florian",
16
23
  "license": "MIT"
17
- }
24
+ }
package/src/index.js CHANGED
@@ -1,7 +1,18 @@
1
1
  class NCSWidget {
2
- constructor(options = {}) {
3
- this.position = options.position || 'bottom-right';
4
- this.apiUrl = options.apiUrl || 'https://ncs-backend-api.onrender.com'; // ⚠️ METTEZ VOTRE URL RENDER ICI
2
+ constructor(userOptions = {}) {
3
+ // 🛠️ 1. Configurations par défaut (Fusionnées avec les choix de l'utilisateur)
4
+ const defaultOptions = {
5
+ position: 'bottom-right',
6
+ apiUrl: 'https://VOTRE-URL-RENDER.onrender.com', // ⚠️ METTEZ VOTRE URL RENDER ICI
7
+ theme: 'dark', // 'dark' ou 'light'
8
+ primaryColor: '#1DB954', // Vert NCS par défaut
9
+ defaultGenre: 'all',
10
+ startVolume: 0.5,
11
+ offset: '25px', // Distance par rapport au bord de l'écran
12
+ zIndex: 99999
13
+ };
14
+
15
+ this.options = { ...defaultOptions, ...userOptions };
5
16
 
6
17
  this.audio = new Audio();
7
18
  this.isPlaying = false;
@@ -13,10 +24,9 @@ class NCSWidget {
13
24
 
14
25
  this.isBrowser = typeof window !== 'undefined';
15
26
  if (this.isBrowser) {
16
- // Le volume est à 1 (100%) par défaut s'il n'y a rien en cache
17
27
  const savedVol = localStorage.getItem('ncs_volume');
18
- this.audio.volume = savedVol !== null ? parseFloat(savedVol) : 1.0;
19
- this.lastVolume = this.audio.volume > 0 ? this.audio.volume : 1.0; // Pour le mute/unmute
28
+ this.audio.volume = savedVol !== null ? parseFloat(savedVol) : this.options.startVolume;
29
+ this.lastVolume = this.audio.volume > 0 ? this.audio.volume : this.options.startVolume;
20
30
 
21
31
  this.savedTime = localStorage.getItem('ncs_currentTime') || 0;
22
32
  this.savedTrack = localStorage.getItem('ncs_currentTrack') || null;
@@ -33,7 +43,9 @@ class NCSWidget {
33
43
  this.restoreTrack();
34
44
  this.fillQueue(this.genreSelect.value);
35
45
  } else {
36
- this.changeGenre('all');
46
+ // Utiliser le genre par défaut défini dans les options
47
+ this.genreSelect.value = this.options.defaultGenre;
48
+ this.changeGenre(this.options.defaultGenre);
37
49
  }
38
50
  }
39
51
 
@@ -43,54 +55,85 @@ class NCSWidget {
43
55
  this.container = document.createElement('div');
44
56
  this.container.id = 'ncs-persistent-widget';
45
57
 
58
+ // 🎨 2. Application du thème (Variables CSS dynamiques)
59
+ const isLight = this.options.theme === 'light';
60
+ const colors = {
61
+ bg: isLight ? '#ffffff' : '#181818',
62
+ text: isLight ? '#222222' : '#ffffff',
63
+ textMuted: isLight ? '#666666' : '#b3b3b3',
64
+ border: isLight ? '#e0e0e0' : '#282828',
65
+ panelBg: isLight ? '#f5f5f5' : '#282828',
66
+ sliderBg: isLight ? '#d3d3d3' : '#535353',
67
+ btnBg: isLight ? '#222222' : '#ffffff',
68
+ btnColor: isLight ? '#ffffff' : '#000000'
69
+ };
70
+
46
71
  const style = document.createElement('style');
47
72
  style.textContent = `
48
- #ncs-persistent-widget { position: fixed; ${this.getPositionStyles()} z-index: 99999; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
49
- .ncs-minimized { width: 55px; height: 55px; border-radius: 50%; background: linear-gradient(135deg, #1DB954, #1ed760); cursor: pointer; display: flex; justify-content: center; align-items: center; box-shadow: 0 6px 15px rgba(29, 185, 84, 0.4); font-size: 24px; transition: transform 0.2s; }
73
+ #ncs-persistent-widget {
74
+ /* Variables CSS exposées pour les développeurs */
75
+ --ncs-primary: ${this.options.primaryColor};
76
+ --ncs-bg: ${colors.bg};
77
+ --ncs-text: ${colors.text};
78
+ --ncs-text-muted: ${colors.textMuted};
79
+ --ncs-border: ${colors.border};
80
+ --ncs-panel-bg: ${colors.panelBg};
81
+ --ncs-slider-bg: ${colors.sliderBg};
82
+ --ncs-btn-bg: ${colors.btnBg};
83
+ --ncs-btn-color: ${colors.btnColor};
84
+
85
+ position: fixed;
86
+ ${this.getPositionStyles()}
87
+ z-index: ${this.options.zIndex};
88
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
89
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
90
+ }
91
+
92
+ .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; }
50
93
  .ncs-minimized:hover { transform: scale(1.1); }
51
94
  .ncs-minimized.hidden { display: none; }
52
95
 
53
- .ncs-expanded { width: 320px; background: #181818; color: white; border-radius: 16px; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); display: none; border: 1px solid #282828; }
96
+ .ncs-expanded { width: 320px; background: var(--ncs-bg); color: var(--ncs-text); border-radius: 16px; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); display: none; border: 1px solid var(--ncs-border); }
54
97
  .ncs-expanded.active { display: block; animation: ncsFadeIn 0.3s ease; }
55
98
 
56
99
  .ncs-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
57
- .ncs-header strong { font-size: 14px; font-weight: 600; color: #b3b3b3; letter-spacing: 1px; text-transform: uppercase; display: flex; align-items: center; gap: 8px; }
58
- .ncs-close-btn { background: transparent; border: none; color: #b3b3b3; font-size: 18px; cursor: pointer; padding: 0; transition: color 0.2s; }
59
- .ncs-close-btn:hover { color: white; }
100
+ .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; }
101
+ .ncs-close-btn { background: transparent; border: none; color: var(--ncs-text-muted); font-size: 18px; cursor: pointer; padding: 0; transition: color 0.2s; }
102
+ .ncs-close-btn:hover { color: var(--ncs-text); }
60
103
 
61
104
  .ncs-visualizer { display: flex; gap: 2px; height: 12px; align-items: flex-end; opacity: 0; transition: opacity 0.3s; }
62
105
  .ncs-visualizer.playing { opacity: 1; }
63
- .ncs-bar { width: 3px; background: #1DB954; border-radius: 2px; animation: bounce 0.5s infinite alternate; }
106
+ .ncs-bar { width: 3px; background: var(--ncs-primary); border-radius: 2px; animation: bounce 0.5s infinite alternate; }
64
107
  .ncs-bar:nth-child(2) { animation-delay: 0.15s; }
65
108
  .ncs-bar:nth-child(3) { animation-delay: 0.3s; }
66
109
  @keyframes bounce { from { height: 3px; } to { height: 12px; } }
67
110
 
68
111
  .ncs-track-info { display: flex; align-items: center; margin-bottom: 15px; }
69
- .ncs-cover { width: 65px; height: 65px; border-radius: 8px; background: #282828; margin-right: 15px; object-fit: cover; box-shadow: 0 4px 10px rgba(0,0,0,0.3); }
112
+ .ncs-cover { width: 65px; height: 65px; border-radius: 8px; background: var(--ncs-panel-bg); margin-right: 15px; object-fit: cover; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
70
113
  .ncs-details { flex: 1; overflow: hidden; display: flex; flex-direction: column; justify-content: center; }
71
- #ncs-track-name { font-size: 14px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px; }
72
- #ncs-artists { font-size: 11px; color: #a0a0a0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 8px; }
73
- #ncs-genre { width: 100%; padding: 4px 8px; background: #282828; color: #b3b3b3; border: 1px solid #333; border-radius: 4px; font-size: 12px; cursor: pointer; outline: none; }
114
+ #ncs-track-name { font-size: 14px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px; }
115
+ #ncs-artists { font-size: 11px; color: var(--ncs-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 8px; }
116
+ #ncs-genre { width: 100%; padding: 4px 8px; background: var(--ncs-panel-bg); color: var(--ncs-text); border: 1px solid var(--ncs-border); border-radius: 6px; font-size: 12px; cursor: pointer; outline: none; }
74
117
 
75
- .ncs-progress-container { margin-bottom: 15px; display:flex; align-items:center; gap: 10px; font-size: 11px; color: #b3b3b3; }
76
- .ncs-slider { -webkit-appearance: none; width: 100%; height: 4px; background: #535353; border-radius: 2px; outline: none; cursor: pointer; }
77
- .ncs-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: #1DB954; cursor: pointer; transition: transform 0.1s; }
118
+ .ncs-progress-container { margin-bottom: 15px; display:flex; align-items:center; gap: 10px; font-size: 11px; color: var(--ncs-text-muted); font-variant-numeric: tabular-nums; }
119
+ .ncs-slider { -webkit-appearance: none; width: 100%; height: 4px; background: var(--ncs-slider-bg); border-radius: 2px; outline: none; cursor: pointer; }
120
+ .ncs-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: var(--ncs-primary); cursor: pointer; transition: transform 0.1s; }
78
121
  .ncs-slider::-webkit-slider-thumb:hover { transform: scale(1.2); }
79
122
 
80
123
  .ncs-controls { display: flex; justify-content: center; align-items: center; gap: 15px; margin-bottom: 15px; }
81
- .ncs-btn-circle { width: 50px; height: 50px; border-radius: 50%; background: white; color: black; border: none; font-size: 20px; cursor: pointer; display:flex; justify-content:center; align-items:center; transition: transform 0.2s; padding-left: 4px; }
124
+ .ncs-btn-circle { width: 50px; height: 50px; border-radius: 50%; background: var(--ncs-btn-bg); color: var(--ncs-btn-color); border: none; font-size: 20px; cursor: pointer; display:flex; justify-content:center; align-items:center; transition: transform 0.2s; padding-left: 4px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
82
125
  .ncs-btn-circle.paused { padding-left: 0; }
83
126
  .ncs-btn-circle:hover { transform: scale(1.05); }
84
- .ncs-btn-icon { background: transparent; border: none; color: #b3b3b3; font-size: 20px; cursor: pointer; transition: color 0.2s; padding: 5px; }
85
- .ncs-btn-icon:hover { color: white; }
86
- .ncs-btn-icon:disabled { color: #333; cursor: not-allowed; }
127
+ .ncs-btn-icon { background: transparent; border: none; color: var(--ncs-text-muted); font-size: 20px; cursor: pointer; transition: color 0.2s; padding: 5px; }
128
+ .ncs-btn-icon:hover { color: var(--ncs-text); }
129
+ .ncs-btn-icon:disabled { color: var(--ncs-border); cursor: not-allowed; }
87
130
 
88
131
  .ncs-bottom-bar { display: flex; justify-content: space-between; align-items: center; }
89
- .ncs-volume-container { display: flex; align-items: center; gap: 8px; color: #b3b3b3; flex: 1; margin-right: 15px; }
132
+ .ncs-volume-container { display: flex; align-items: center; gap: 8px; color: var(--ncs-text-muted); flex: 1; margin-right: 15px; }
90
133
  #ncs-mute-btn { cursor: pointer; transition: transform 0.1s; user-select: none; }
91
134
  #ncs-mute-btn:hover { transform: scale(1.1); }
92
- .ncs-download-btn { color: #b3b3b3; text-decoration: none; font-size: 18px; transition: color 0.2s; }
93
- .ncs-download-btn:hover { color: #1DB954; }
135
+ .ncs-download-btn { color: var(--ncs-text-muted); text-decoration: none; font-size: 18px; transition: color 0.2s; }
136
+ .ncs-download-btn:hover { color: var(--ncs-primary); }
94
137
 
95
138
  @keyframes ncsFadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
96
139
  `;
@@ -115,72 +158,14 @@ class NCSWidget {
115
158
  <select id="ncs-genre">
116
159
  <option value="all">🌍 Tous les genres</option>
117
160
  <option value="31">Alternative Dance</option>
118
- <option value="32">Alternative Hip-Hop</option>
119
- <option value="33">Alternative Pop</option>
120
- <option value="23">Ambient</option>
121
- <option value="34">Anti-Pop</option>
122
- <option value="1">Bass</option>
123
- <option value="18">Bass House</option>
124
- <option value="78">Bass Music</option>
125
- <option value="26">Brazilian Phonk</option>
126
- <option value="27">Breakbeat</option>
161
+ <option value="10">House</option>
127
162
  <option value="2">Chill</option>
128
- <option value="24">Chill Bass</option>
129
- <option value="35">Chill Pop</option>
130
- <option value="85">Colour Bass</option>
131
- <option value="65">Complextro</option>
132
- <option value="36">Dance-Pop</option>
133
- <option value="66">Deep House</option>
134
- <option value="45">Disco</option>
135
- <option value="46">Disco House</option>
136
- <option value="3">Drum & Bass</option>
137
- <option value="4">Drumstep</option>
138
163
  <option value="5">Dubstep</option>
139
- <option value="6">EDM</option>
140
- <option value="47">Electro</option>
141
- <option value="48">Electro House</option>
142
164
  <option value="7">Electronic</option>
143
- <option value="39">Electronic Pop</option>
144
- <option value="83">Electronic Rock</option>
145
- <option value="17">Future Bass</option>
146
- <option value="68">Future Bounce</option>
147
- <option value="50">Future Funk</option>
148
- <option value="8">Future House</option>
149
- <option value="69">Future Rave</option>
150
- <option value="57">Future Trap</option>
151
- <option value="40">Futurepop</option>
152
- <option value="51">Garage</option>
153
- <option value="15">Glitch Hop</option>
154
- <option value="82">Hardcore</option>
165
+ <option value="3">Drum & Bass</option>
155
166
  <option value="9">Hardstyle</option>
156
- <option value="10">House</option>
157
- <option value="41">Hyperpop</option>
158
- <option value="11">Indie Dance</option>
159
- <option value="91">J-Pop</option>
160
- <option value="84">Jersey Club</option>
161
- <option value="28">Jump-Up</option>
162
- <option value="29">Liquid DnB</option>
163
- <option value="60">Lofi Hip-Hop</option>
164
- <option value="12">Melodic Dubstep</option>
165
- <option value="54">Melodic House</option>
166
- <option value="22">Midtempo Bass</option>
167
- <option value="30">Neurofunk</option>
168
- <option value="87">Nu-Jazz</option>
169
- <option value="16">Phonk</option>
170
- <option value="86">Pluggnb</option>
171
- <option value="19">Pop</option>
172
- <option value="55">Progressive House</option>
173
- <option value="88">RnB</option>
174
- <option value="89">Speed Garage</option>
175
- <option value="73">Tech House</option>
176
- <option value="80">Techno</option>
177
- <option value="81">Trance</option>
178
167
  <option value="14">Trap</option>
179
- <option value="74">Tribal House</option>
180
- <option value="21">UKG</option>
181
- <option value="92">Wave</option>
182
- <option value="90">Witch House</option>
183
- </select>
168
+ </select>
184
169
  </div>
185
170
  </div>
186
171
 
@@ -209,6 +194,7 @@ class NCSWidget {
209
194
  document.head.appendChild(style);
210
195
  document.body.appendChild(this.container);
211
196
 
197
+ // Références
212
198
  this.minimized = this.container.querySelector('.ncs-minimized');
213
199
  this.expanded = this.container.querySelector('.ncs-expanded');
214
200
  this.playBtn = this.container.querySelector('#ncs-play-pause');
@@ -228,13 +214,14 @@ class NCSWidget {
228
214
  }
229
215
 
230
216
  getPositionStyles() {
217
+ const offset = this.options.offset;
231
218
  const positions = {
232
- 'bottom-right': 'bottom: 25px; right: 25px;',
233
- 'bottom-left': 'bottom: 25px; left: 25px;',
234
- 'top-right': 'top: 25px; right: 25px;',
235
- 'top-left': 'top: 25px; left: 25px;'
219
+ 'bottom-right': `bottom: ${offset}; right: ${offset};`,
220
+ 'bottom-left': `bottom: ${offset}; left: ${offset};`,
221
+ 'top-right': `top: ${offset}; right: ${offset};`,
222
+ 'top-left': `top: ${offset}; left: ${offset};`
236
223
  };
237
- return positions[this.position] || positions['bottom-right'];
224
+ return positions[this.options.position] || positions['bottom-right'];
238
225
  }
239
226
 
240
227
  attachEvents() {
@@ -267,30 +254,26 @@ class NCSWidget {
267
254
 
268
255
  this.progressBar.addEventListener('input', (e) => { this.audio.currentTime = e.target.value; });
269
256
 
270
- // --- NOUVEAU : GESTION DU MUTE ET DU VOLUME ---
271
257
  this.volumeBar.addEventListener('input', (e) => {
272
258
  const vol = parseFloat(e.target.value);
273
259
  this.audio.volume = vol;
274
- if (vol > 0) this.lastVolume = vol; // On mémorise si c'est > 0
260
+ if (vol > 0) this.lastVolume = vol;
275
261
  this.updateMuteIcon(vol);
276
262
  localStorage.setItem('ncs_volume', vol);
277
263
  });
278
264
 
279
265
  this.muteBtn.addEventListener('click', () => {
280
266
  if (this.audio.volume > 0) {
281
- // On Mute
282
267
  this.lastVolume = this.audio.volume;
283
268
  this.audio.volume = 0;
284
269
  this.volumeBar.value = 0;
285
270
  } else {
286
- // On Unmute
287
271
  this.audio.volume = this.lastVolume || 1.0;
288
272
  this.volumeBar.value = this.audio.volume;
289
273
  }
290
274
  this.updateMuteIcon(this.audio.volume);
291
275
  localStorage.setItem('ncs_volume', this.audio.volume);
292
276
  });
293
- // ----------------------------------------------
294
277
 
295
278
  setInterval(() => {
296
279
  if (this.isPlaying && this.audio.currentTime > 0) {
@@ -346,7 +329,7 @@ class NCSWidget {
346
329
 
347
330
  async fetchSingleTrack(genre) {
348
331
  try {
349
- const response = await fetch(`${this.apiUrl}/search?genre=${genre}`);
332
+ const response = await fetch(`${this.options.apiUrl}/search?genre=${genre}`);
350
333
  const data = await response.json();
351
334
  return (data && data.length > 0) ? data[0] : null;
352
335
  } catch (error) {
@@ -406,10 +389,9 @@ class NCSWidget {
406
389
  this.audio.src = track.audioUrl;
407
390
  this.trackName.innerText = track.title;
408
391
 
409
- // Mettre à jour l'affichage des artistes
410
392
  const artistes = track.artists || "NCS Release";
411
393
  this.artistsName.innerText = artistes;
412
- this.artistsName.title = artistes; // Bulle info au survol si le texte est long
394
+ this.artistsName.title = artistes;
413
395
 
414
396
  if (track.coverUrl) this.coverImg.src = track.coverUrl;
415
397
  this.downloadBtn.href = track.audioUrl;