nocopyrightsounds-widget 1.1.1 → 1.3.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.
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
- * 🎨 **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.
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
+ * 🎵 **Catalogue Complet :** Navigation aléatoire intelligente parmi les 60+ genres historiques de NCS.
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,78 +41,70 @@ npm install nocopyrightsounds-widget
40
41
 
41
42
  ## 🚀 Utilisation Rapide
42
43
 
43
- ### Exemple en Vanilla JS (HTML)
44
+ ### Exemple basique (Zéro configuration)
44
45
  \`\`\`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>
46
+ <script type="module">
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 !
50
+ const player = new NCSWidget();
51
+ </script>
53
52
  \`\`\`
54
53
 
55
- ### Exemple dans React (Next.js, Vite...)
56
- \`\`\`jsx
57
- import { useEffect } from 'react';
58
- import NCSWidget from 'nocopyrightsounds-widget';
59
-
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
- }
54
+ ### Exemple Avancé (Design sur mesure)
55
+ \`\`\`javascript
56
+ const widget = new NCSWidget({
57
+ position: 'bottom-left',
58
+ theme: 'dark',
59
+ primaryColor: '#ff0055',
60
+ glassmorphism: true,
61
+ borderRadius: '12px',
62
+ defaultGenre: '10', // Démarre sur la House (ID: 10)
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é
70
+ });
81
71
  \`\`\`
82
72
 
83
73
  ---
84
74
 
85
- ## ⚙️ Configuration (Options de l'objet)
86
-
87
- Vous pouvez passer un objet d'options au constructeur pour personnaliser le comportement du widget :
75
+ ## ⚙️ Configuration Détaillée (Options)
88
76
 
89
77
  | Option | Type | Défaut | Description |
90
78
  | :--- | :--- | :--- | :--- |
91
- | \`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\`). |
92
80
  | \`offset\` | String | \`'25px'\` | Marge par rapport au bord de l'écran. |
93
81
  | \`theme\` | String | \`'dark'\` | Thème de base de l'interface (\`'dark'\` ou \`'light'\`). |
94
- | \`primaryColor\` | String | \`'#1DB954'\` | Couleur principale (Bouton d'ouverture, slider, visualizer). |
82
+ | \`primaryColor\` | String | \`'#1DB954'\` | Couleur principale (Sliders, visualizer). |
83
+ | \`glassmorphism\`| Boolean | \`false\` | Active un fond semi-transparent avec flou d'arrière-plan. |
84
+ | \`borderRadius\` | String | \`'16px'\` | Rayon des bordures du lecteur étendu. |
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. |
91
+ | \`hideDownload\` | Boolean | \`false\` | Masque l'icône de téléchargement direct. |
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. |
95
94
  | \`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. |
95
+ | \`startVolume\` | Number | \`0.5\` | Volume initial entre 0.0 et 1.0. |
99
96
 
100
97
  ---
101
98
 
102
99
  ## 🎨 Personnalisation CSS Avancée
103
100
 
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 :
101
+ 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
102
 
106
103
  \`\`\`css
107
- /* Dans le fichier style.css de votre site web */
108
104
  #ncs-persistent-widget {
109
- --ncs-bg: #000000; /* Fond du widget (Noir pur) */
105
+ --ncs-bg: #000000; /* Fond du widget */
110
106
  --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 */
107
+ --ncs-panel-bg: #111111; /* Fond des listes et des images */
115
108
  }
116
109
  \`\`\`
117
110
 
@@ -119,10 +112,8 @@ Si les options du constructeur ne suffisent pas, le widget expose des **Variable
119
112
 
120
113
  ## 🏗️ Architecture & Backend
121
114
 
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.*
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".**
126
117
 
127
118
  ---
128
119
 
@@ -130,4 +121,4 @@ Ce widget s'appuie donc sur une API Backend Node.js qui sert de relais de donné
130
121
 
131
122
  Distribué sous la licence MIT. Voir `LICENSE` pour plus d'informations.
132
123
 
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.
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.1.1",
3
+ "version": "1.3.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/genres.js ADDED
@@ -0,0 +1,71 @@
1
+ // src/genres.js
2
+
3
+ export const ncsGenres = [
4
+ { value: "all", label: "🌍 Tous les genres" },
5
+ { value: "31", label: "Alternative Dance" },
6
+ { value: "32", label: "Alternative Hip-Hop" },
7
+ { value: "33", label: "Alternative Pop" },
8
+ { value: "23", label: "Ambient" },
9
+ { value: "34", label: "Anti-Pop" },
10
+ { value: "1", label: "Bass" },
11
+ { value: "18", label: "Bass House" },
12
+ { value: "78", label: "Bass Music" },
13
+ { value: "26", label: "Brazilian Phonk" },
14
+ { value: "27", label: "Breakbeat" },
15
+ { value: "2", label: "Chill" },
16
+ { value: "24", label: "Chill Bass" },
17
+ { value: "35", label: "Chill Pop" },
18
+ { value: "85", label: "Colour Bass" },
19
+ { value: "65", label: "Complextro" },
20
+ { value: "36", label: "Dance-Pop" },
21
+ { value: "66", label: "Deep House" },
22
+ { value: "45", label: "Disco" },
23
+ { value: "46", label: "Disco House" },
24
+ { value: "3", label: "Drum & Bass" },
25
+ { value: "4", label: "Drumstep" },
26
+ { value: "5", label: "Dubstep" },
27
+ { value: "6", label: "EDM" },
28
+ { value: "47", label: "Electro" },
29
+ { value: "48", label: "Electro House" },
30
+ { value: "7", label: "Electronic" },
31
+ { value: "39", label: "Electronic Pop" },
32
+ { value: "83", label: "Electronic Rock" },
33
+ { value: "17", label: "Future Bass" },
34
+ { value: "68", label: 'Future Bounce' },
35
+ { value: "50", label: "Future Funk" },
36
+ { value: "8", label: "Future House" },
37
+ { value: "69", label: "Future Rave" },
38
+ { value: "57", label: "Future Trap" },
39
+ { value: "40", label: "Futurepop" },
40
+ { value: "51", label: "Garage" },
41
+ { value: "15", label: "Glitch Hop" },
42
+ { value: "82", label: "Hardcore" },
43
+ { value: "9", label: "Hardstyle" },
44
+ { value: "10", label: "House" },
45
+ { value: "41", label: "Hyperpop" },
46
+ { value: "11", label: "Indie Dance" },
47
+ { value: "91", label: "J-Pop" },
48
+ { value: "84", label: "Jersey Club" },
49
+ { value: "28", label: "Jump-Up" },
50
+ { value: "29", label: "Liquid DnB" },
51
+ { value: "60", label: "Lofi Hip-Hop" },
52
+ { value: "12", label: "Melodic Dubstep" },
53
+ { value: "54", label: "Melodic House" },
54
+ { value: "22", label: "Midtempo Bass" },
55
+ { value: "30", label: "Neurofunk" },
56
+ { value: "87", label: "Nu-Jazz" },
57
+ { value: "16", label: "Phonk" },
58
+ { value: "86", label: "Pluggnb" },
59
+ { value: "19", label: "Pop" },
60
+ { value: "55", label: "Progressive House" },
61
+ { value: "88", label: "RnB" },
62
+ { value: "89", label: "Speed Garage" },
63
+ { value: "73", label: "Tech House" },
64
+ { value: "80", label: "Techno" },
65
+ { value: "81", label: "Trance" },
66
+ { value: "14", label: "Trap" },
67
+ { value: "74", label: "Tribal House" },
68
+ { value: "21", label: "UKG" },
69
+ { value: "92", label: "Wave" },
70
+ { value: "90", label: "Witch House" }
71
+ ];
package/src/index.js CHANGED
@@ -1,15 +1,29 @@
1
+ import { ncsGenres } from './genres.js';
2
+
1
3
  class NCSWidget {
2
4
  constructor(userOptions = {}) {
3
- // 🛠️ 1. Configurations par défaut (Fusionnées avec les choix de l'utilisateur)
4
5
  const defaultOptions = {
5
6
  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
7
+ apiUrl: 'https://ncs-backend-api.onrender.com', // 🔗 Votre API officielle fixée !
8
+ theme: 'dark',
9
+ primaryColor: '#1DB954',
9
10
  defaultGenre: 'all',
10
11
  startVolume: 0.5,
11
- offset: '25px', // Distance par rapport au bord de l'écran
12
- zIndex: 99999
12
+ offset: '25px',
13
+ zIndex: 99999,
14
+ glassmorphism: false,
15
+ borderRadius: '16px',
16
+ fontFamily: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
17
+ hideDownload: false,
18
+ hideVisualizer: false,
19
+ autoOpen: false,
20
+
21
+ // --- 🎛️ NOUVEAUTÉS V1.3.0 : Bouton Réduit ---
22
+ minimizedIcon: '🎧', // L'icône par défaut
23
+ minimizedSize: '55px', // Taille du bouton
24
+ minimizedRadius: '50%', // Arrondi (50% = rond, 10px = carré arrondi)
25
+ minimizedBg: null, // Surcharger la couleur de fond (utilise primaryColor sinon)
26
+ minimizedColor: '#ffffff' // Couleur de l'icône
13
27
  };
14
28
 
15
29
  this.options = { ...defaultOptions, ...userOptions };
@@ -33,7 +47,13 @@ class NCSWidget {
33
47
  this.savedCover = localStorage.getItem('ncs_currentCover') || null;
34
48
  this.savedTitle = localStorage.getItem('ncs_currentTitle') || null;
35
49
  this.savedArtists = localStorage.getItem('ncs_currentArtists') || null;
36
- this.isWidgetOpen = localStorage.getItem('ncs_isOpen') === 'true';
50
+
51
+ const savedState = localStorage.getItem('ncs_isOpen');
52
+ if (savedState !== null) {
53
+ this.isWidgetOpen = savedState === 'true';
54
+ } else {
55
+ this.isWidgetOpen = this.options.autoOpen;
56
+ }
37
57
  }
38
58
 
39
59
  this.initDOM();
@@ -43,7 +63,6 @@ class NCSWidget {
43
63
  this.restoreTrack();
44
64
  this.fillQueue(this.genreSelect.value);
45
65
  } else {
46
- // Utiliser le genre par défaut défini dans les options
47
66
  this.genreSelect.value = this.options.defaultGenre;
48
67
  this.changeGenre(this.options.defaultGenre);
49
68
  }
@@ -55,14 +74,16 @@ class NCSWidget {
55
74
  this.container = document.createElement('div');
56
75
  this.container.id = 'ncs-persistent-widget';
57
76
 
58
- // 🎨 2. Application du thème (Variables CSS dynamiques)
59
77
  const isLight = this.options.theme === 'light';
78
+ const baseBgColor = isLight ? '255, 255, 255' : '24, 24, 24';
79
+ const finalBg = this.options.glassmorphism ? `rgba(${baseBgColor}, 0.75)` : (isLight ? '#ffffff' : '#181818');
80
+ const backdropFilter = this.options.glassmorphism ? 'blur(12px)' : 'none';
81
+
60
82
  const colors = {
61
- bg: isLight ? '#ffffff' : '#181818',
62
83
  text: isLight ? '#222222' : '#ffffff',
63
84
  textMuted: isLight ? '#666666' : '#b3b3b3',
64
- border: isLight ? '#e0e0e0' : '#282828',
65
- panelBg: isLight ? '#f5f5f5' : '#282828',
85
+ border: this.options.glassmorphism ? `rgba(${isLight ? '0,0,0' : '255,255,255'}, 0.1)` : (isLight ? '#e0e0e0' : '#282828'),
86
+ panelBg: this.options.glassmorphism ? `rgba(${isLight ? '0,0,0' : '255,255,255'}, 0.05)` : (isLight ? '#f5f5f5' : '#282828'),
66
87
  sliderBg: isLight ? '#d3d3d3' : '#535353',
67
88
  btnBg: isLight ? '#222222' : '#ffffff',
68
89
  btnColor: isLight ? '#ffffff' : '#000000'
@@ -71,9 +92,8 @@ class NCSWidget {
71
92
  const style = document.createElement('style');
72
93
  style.textContent = `
73
94
  #ncs-persistent-widget {
74
- /* Variables CSS exposées pour les développeurs */
75
95
  --ncs-primary: ${this.options.primaryColor};
76
- --ncs-bg: ${colors.bg};
96
+ --ncs-bg: ${finalBg};
77
97
  --ncs-text: ${colors.text};
78
98
  --ncs-text-muted: ${colors.textMuted};
79
99
  --ncs-border: ${colors.border};
@@ -81,19 +101,27 @@ class NCSWidget {
81
101
  --ncs-slider-bg: ${colors.sliderBg};
82
102
  --ncs-btn-bg: ${colors.btnBg};
83
103
  --ncs-btn-color: ${colors.btnColor};
104
+ --ncs-radius: ${this.options.borderRadius};
105
+ --ncs-font: ${this.options.fontFamily};
106
+
107
+ /* Variables Bouton Réduit */
108
+ --ncs-min-size: ${this.options.minimizedSize};
109
+ --ncs-min-radius: ${this.options.minimizedRadius};
110
+ --ncs-min-bg: ${this.options.minimizedBg || this.options.primaryColor};
111
+ --ncs-min-color: ${this.options.minimizedColor};
84
112
 
85
113
  position: fixed;
86
114
  ${this.getPositionStyles()}
87
115
  z-index: ${this.options.zIndex};
88
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
116
+ font-family: var(--ncs-font);
89
117
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
90
118
  }
91
119
 
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; }
93
- .ncs-minimized:hover { transform: scale(1.1); }
120
+ .ncs-minimized { width: var(--ncs-min-size); height: var(--ncs-min-size); 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(var(--ncs-min-size) * 0.45); transition: transform 0.2s; color: var(--ncs-min-color); }
121
+ .ncs-minimized:hover { transform: scale(1.05); }
94
122
  .ncs-minimized.hidden { display: none; }
95
123
 
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); }
124
+ .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}; }
97
125
  .ncs-expanded.active { display: block; animation: ncsFadeIn 0.3s ease; }
98
126
 
99
127
  .ncs-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
@@ -101,7 +129,7 @@ class NCSWidget {
101
129
  .ncs-close-btn { background: transparent; border: none; color: var(--ncs-text-muted); font-size: 18px; cursor: pointer; padding: 0; transition: color 0.2s; }
102
130
  .ncs-close-btn:hover { color: var(--ncs-text); }
103
131
 
104
- .ncs-visualizer { display: flex; gap: 2px; height: 12px; align-items: flex-end; opacity: 0; transition: opacity 0.3s; }
132
+ .ncs-visualizer { display: ${this.options.hideVisualizer ? 'none' : 'flex'}; gap: 2px; height: 12px; align-items: flex-end; opacity: 0; transition: opacity 0.3s; }
105
133
  .ncs-visualizer.playing { opacity: 1; }
106
134
  .ncs-bar { width: 3px; background: var(--ncs-primary); border-radius: 2px; animation: bounce 0.5s infinite alternate; }
107
135
  .ncs-bar:nth-child(2) { animation-delay: 0.15s; }
@@ -109,7 +137,7 @@ class NCSWidget {
109
137
  @keyframes bounce { from { height: 3px; } to { height: 12px; } }
110
138
 
111
139
  .ncs-track-info { display: flex; align-items: center; margin-bottom: 15px; }
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); }
140
+ .ncs-cover { width: 65px; height: 65px; border-radius: calc(var(--ncs-radius) / 2); background: var(--ncs-panel-bg); margin-right: 15px; object-fit: cover; box-shadow: 0 4px 10px rgba(0,0,0,0.1); }
113
141
  .ncs-details { flex: 1; overflow: hidden; display: flex; flex-direction: column; justify-content: center; }
114
142
  #ncs-track-name { font-size: 14px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px; }
115
143
  #ncs-artists { font-size: 11px; color: var(--ncs-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 8px; }
@@ -132,14 +160,19 @@ class NCSWidget {
132
160
  .ncs-volume-container { display: flex; align-items: center; gap: 8px; color: var(--ncs-text-muted); flex: 1; margin-right: 15px; }
133
161
  #ncs-mute-btn { cursor: pointer; transition: transform 0.1s; user-select: none; }
134
162
  #ncs-mute-btn:hover { transform: scale(1.1); }
135
- .ncs-download-btn { color: var(--ncs-text-muted); text-decoration: none; font-size: 18px; transition: color 0.2s; }
163
+
164
+ .ncs-download-btn { display: ${this.options.hideDownload ? 'none' : 'block'}; color: var(--ncs-text-muted); text-decoration: none; font-size: 18px; transition: color 0.2s; }
136
165
  .ncs-download-btn:hover { color: var(--ncs-primary); }
137
166
 
138
167
  @keyframes ncsFadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
139
168
  `;
140
169
 
170
+ const genresOptionsHTML = ncsGenres.map(genre =>
171
+ `<option value="${genre.value}">${genre.label}</option>`
172
+ ).join('');
173
+
141
174
  this.container.innerHTML = `
142
- <div class="ncs-minimized ${this.isWidgetOpen ? 'hidden' : ''}">🎧</div>
175
+ <div class="ncs-minimized ${this.isWidgetOpen ? 'hidden' : ''}">${this.options.minimizedIcon}</div>
143
176
  <div class="ncs-expanded ${this.isWidgetOpen ? 'active' : ''}">
144
177
  <div class="ncs-header">
145
178
  <strong>NCS Player
@@ -156,16 +189,8 @@ class NCSWidget {
156
189
  <div id="ncs-track-name">Chargement...</div>
157
190
  <div id="ncs-artists">Artiste(s)</div>
158
191
  <select id="ncs-genre">
159
- <option value="all">🌍 Tous les genres</option>
160
- <option value="31">Alternative Dance</option>
161
- <option value="10">House</option>
162
- <option value="2">Chill</option>
163
- <option value="5">Dubstep</option>
164
- <option value="7">Electronic</option>
165
- <option value="3">Drum & Bass</option>
166
- <option value="9">Hardstyle</option>
167
- <option value="14">Trap</option>
168
- </select>
192
+ ${genresOptionsHTML}
193
+ </select>
169
194
  </div>
170
195
  </div>
171
196
 
@@ -194,7 +219,6 @@ class NCSWidget {
194
219
  document.head.appendChild(style);
195
220
  document.body.appendChild(this.container);
196
221
 
197
- // Références
198
222
  this.minimized = this.container.querySelector('.ncs-minimized');
199
223
  this.expanded = this.container.querySelector('.ncs-expanded');
200
224
  this.playBtn = this.container.querySelector('#ncs-play-pause');