1+ ( function ( ) {
2+ const container = document . getElementById ( 'tool-color' ) ;
3+ if ( ! container ) return ;
4+
5+ const lang = container . getAttribute ( 'data-lang' ) || 'zh-cn' ;
6+
7+ const i18n = {
8+ 'zh-cn' : {
9+ labelPreview : '颜色预览' ,
10+ labelPicker : '颜色选择器' ,
11+ labelHex : 'HEX 十六进制' ,
12+ labelRgb : 'RGB (红, 绿, 蓝)' ,
13+ labelHsl : 'HSL (色相, 饱和度, 亮度)' ,
14+ placeholderHex : '#000000' ,
15+ copyBtn : '复制' ,
16+ copied : '已复制'
17+ } ,
18+ 'en' : {
19+ labelPreview : 'Color Preview' ,
20+ labelPicker : 'Color Picker' ,
21+ labelHex : 'HEX' ,
22+ labelRgb : 'RGB (Red, Green, Blue)' ,
23+ labelHsl : 'HSL (Hue, Saturation, Lightness)' ,
24+ placeholderHex : '#000000' ,
25+ copyBtn : 'Copy' ,
26+ copied : 'Copied'
27+ }
28+ } ;
29+
30+ const t = i18n [ lang ] || i18n [ 'en' ] ;
31+
32+ container . innerHTML = `
33+ <style>
34+ #tool-color .tool-container { max-width: 100%; }
35+ #tool-color .color-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 2rem; margin-top: 1.5rem; }
36+ @media (max-width: 768px) { #tool-color .color-grid { grid-template-columns: 1fr; } }
37+ #tool-color .preview-card {
38+ background: var(--card-background);
39+ border: 1px solid var(--border-color);
40+ border-radius: 12px;
41+ padding: 1.5rem;
42+ display: flex;
43+ flex-direction: column;
44+ align-items: center;
45+ gap: 1rem;
46+ }
47+ #tool-color .color-preview {
48+ width: 100%;
49+ height: 150px;
50+ border-radius: 8px;
51+ border: 1px solid var(--border-color);
52+ box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
53+ }
54+ #tool-color .input-group { margin-bottom: 1.5rem; }
55+ #tool-color label { font-weight: bold; font-size: 1.4rem; color: var(--card-text-color-main); display: block; margin-bottom: 0.5rem; }
56+ #tool-color input[type="text"], #tool-color input[type="number"] {
57+ width: 100%;
58+ padding: 1rem;
59+ border: 1px solid var(--border-color);
60+ border-radius: 6px;
61+ background: var(--body-background);
62+ color: var(--card-text-color-main);
63+ font-family: 'Fira Code', monospace;
64+ font-size: 1.4rem;
65+ outline: none;
66+ transition: border-color 0.2s;
67+ }
68+ #tool-color input:focus { border-color: var(--accent-color); }
69+ #tool-color .rgb-inputs, #tool-color .hsl-inputs { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.8rem; }
70+ #tool-color input[type="color"] {
71+ width: 100%;
72+ height: 50px;
73+ padding: 0;
74+ border: none;
75+ border-radius: 6px;
76+ cursor: pointer;
77+ background: none;
78+ }
79+ #tool-color .copy-wrapper { position: relative; display: flex; gap: 0.5rem; }
80+ #tool-color .btn-copy {
81+ padding: 0.4rem 1rem;
82+ font-size: 1.2rem;
83+ background: var(--accent-color);
84+ color: #fff;
85+ border-radius: 4px;
86+ border: none;
87+ cursor: pointer;
88+ white-space: nowrap;
89+ }
90+ </style>
91+ <div class="tool-container">
92+ <div class="color-grid">
93+ <div class="preview-card">
94+ <div class="input-group" style="width: 100%;">
95+ <label>${ t . labelPreview } </label>
96+ <div id="color-preview" class="color-preview" style="background-color: #3b82f6;"></div>
97+ </div>
98+ <div class="input-group" style="width: 100%;">
99+ <label>${ t . labelPicker } </label>
100+ <input type="color" id="color-picker" value="#3b82f6">
101+ </div>
102+ </div>
103+ <div class="inputs-card">
104+ <div class="input-group">
105+ <label>${ t . labelHex } </label>
106+ <div class="copy-wrapper">
107+ <input type="text" id="hex-input" value="#3b82f6" placeholder="${ t . placeholderHex } ">
108+ <button class="btn-copy" data-target="hex-input">${ t . copyBtn } </button>
109+ </div>
110+ </div>
111+ <div class="input-group">
112+ <label>${ t . labelRgb } </label>
113+ <div class="rgb-inputs">
114+ <input type="number" id="rgb-r" min="0" max="255" value="59">
115+ <input type="number" id="rgb-g" min="0" max="255" value="130">
116+ <input type="number" id="rgb-b" min="0" max="255" value="246">
117+ </div>
118+ <div class="copy-wrapper" style="margin-top: 0.5rem;">
119+ <input type="text" id="rgb-string" readonly value="rgb(59, 130, 246)">
120+ <button class="btn-copy" data-target="rgb-string">${ t . copyBtn } </button>
121+ </div>
122+ </div>
123+ <div class="input-group">
124+ <label>${ t . labelHsl } </label>
125+ <div class="hsl-inputs">
126+ <input type="number" id="hsl-h" min="0" max="360" value="217">
127+ <input type="number" id="hsl-s" min="0" max="100" value="91">
128+ <input type="number" id="hsl-l" min="0" max="100" value="60">
129+ </div>
130+ <div class="copy-wrapper" style="margin-top: 0.5rem;">
131+ <input type="text" id="hsl-string" readonly value="hsl(217, 91%, 60%)">
132+ <button class="btn-copy" data-target="hsl-string">${ t . copyBtn } </button>
133+ </div>
134+ </div>
135+ </div>
136+ </div>
137+ </div>
138+ ` ;
139+
140+ const picker = document . getElementById ( 'color-picker' ) ;
141+ const preview = document . getElementById ( 'color-preview' ) ;
142+ const hexInput = document . getElementById ( 'hex-input' ) ;
143+ const rgbR = document . getElementById ( 'rgb-r' ) ;
144+ const rgbG = document . getElementById ( 'rgb-g' ) ;
145+ const rgbB = document . getElementById ( 'rgb-b' ) ;
146+ const rgbString = document . getElementById ( 'rgb-string' ) ;
147+ const hslH = document . getElementById ( 'hsl-h' ) ;
148+ const hslS = document . getElementById ( 'hsl-s' ) ;
149+ const hslL = document . getElementById ( 'hsl-l' ) ;
150+ const hslString = document . getElementById ( 'hsl-string' ) ;
151+
152+ // Helper: Hex to RGB
153+ function hexToRgb ( hex ) {
154+ hex = hex . replace ( / ^ # / , '' ) ;
155+ if ( hex . length === 3 ) {
156+ hex = hex . split ( '' ) . map ( c => c + c ) . join ( '' ) ;
157+ }
158+ const bigint = parseInt ( hex , 16 ) ;
159+ return {
160+ r : ( bigint >> 16 ) & 255 ,
161+ g : ( bigint >> 8 ) & 255 ,
162+ b : bigint & 255
163+ } ;
164+ }
165+
166+ // Helper: RGB to Hex
167+ function rgbToHex ( r , g , b ) {
168+ return "#" + ( ( 1 << 24 ) + ( r << 16 ) + ( g << 8 ) + b ) . toString ( 16 ) . slice ( 1 ) . toUpperCase ( ) ;
169+ }
170+
171+ // Helper: RGB to HSL
172+ function rgbToHsl ( r , g , b ) {
173+ r /= 255 ; g /= 255 ; b /= 255 ;
174+ const max = Math . max ( r , g , b ) , min = Math . min ( r , g , b ) ;
175+ let h , s , l = ( max + min ) / 2 ;
176+ if ( max === min ) {
177+ h = s = 0 ;
178+ } else {
179+ const d = max - min ;
180+ s = l > 0.5 ? d / ( 2 - max - min ) : d / ( max + min ) ;
181+ switch ( max ) {
182+ case r : h = ( g - b ) / d + ( g < b ? 6 : 0 ) ; break ;
183+ case g : h = ( b - r ) / d + 2 ; break ;
184+ case b : h = ( r - g ) / d + 4 ; break ;
185+ }
186+ h /= 6 ;
187+ }
188+ return {
189+ h : Math . round ( h * 360 ) ,
190+ s : Math . round ( s * 100 ) ,
191+ l : Math . round ( l * 100 )
192+ } ;
193+ }
194+
195+ // Helper: HSL to RGB
196+ function hslToRgb ( h , s , l ) {
197+ h /= 360 ; s /= 100 ; l /= 100 ;
198+ let r , g , b ;
199+ if ( s === 0 ) {
200+ r = g = b = l ;
201+ } else {
202+ const hue2rgb = ( p , q , t ) => {
203+ if ( t < 0 ) t += 1 ;
204+ if ( t > 1 ) t -= 1 ;
205+ if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t ;
206+ if ( t < 1 / 2 ) return q ;
207+ if ( t < 2 / 3 ) return p + ( q - p ) * ( 2 / 3 - t ) * 6 ;
208+ return p ;
209+ } ;
210+ const q = l < 0.5 ? l * ( 1 + s ) : l + s - l * s ;
211+ const p = 2 * l - q ;
212+ r = hue2rgb ( p , q , h + 1 / 3 ) ;
213+ g = hue2rgb ( p , q , h ) ;
214+ b = hue2rgb ( p , q , h - 1 / 3 ) ;
215+ }
216+ return {
217+ r : Math . round ( r * 255 ) ,
218+ g : Math . round ( g * 255 ) ,
219+ b : Math . round ( b * 255 )
220+ } ;
221+ }
222+
223+ function updateUI ( hex , r , g , b , h , s , l ) {
224+ preview . style . backgroundColor = hex ;
225+ picker . value = hex ;
226+ hexInput . value = hex ;
227+ rgbR . value = r ;
228+ rgbG . value = g ;
229+ rgbB . value = b ;
230+ rgbString . value = `rgb(${ r } , ${ g } , ${ b } )` ;
231+ hslH . value = h ;
232+ hslS . value = s ;
233+ hslL . value = l ;
234+ hslString . value = `hsl(${ h } , ${ s } %, ${ l } %)` ;
235+ }
236+
237+ picker . addEventListener ( 'input' , ( e ) => {
238+ const hex = e . target . value . toUpperCase ( ) ;
239+ const rgb = hexToRgb ( hex ) ;
240+ const hsl = rgbToHsl ( rgb . r , rgb . g , rgb . b ) ;
241+ updateUI ( hex , rgb . r , rgb . g , rgb . b , hsl . h , hsl . s , hsl . l ) ;
242+ } ) ;
243+
244+ hexInput . addEventListener ( 'input' , ( e ) => {
245+ let hex = e . target . value ;
246+ if ( / ^ # ? [ 0 - 9 A - F ] { 6 } $ / i. test ( hex ) ) {
247+ if ( ! hex . startsWith ( '#' ) ) hex = '#' + hex ;
248+ hex = hex . toUpperCase ( ) ;
249+ const rgb = hexToRgb ( hex ) ;
250+ const hsl = rgbToHsl ( rgb . r , rgb . g , rgb . b ) ;
251+ updateUI ( hex , rgb . r , rgb . g , rgb . b , hsl . h , hsl . s , hsl . l ) ;
252+ }
253+ } ) ;
254+
255+ [ rgbR , rgbG , rgbB ] . forEach ( el => {
256+ el . addEventListener ( 'input' , ( ) => {
257+ let r = parseInt ( rgbR . value ) || 0 ;
258+ let g = parseInt ( rgbG . value ) || 0 ;
259+ let b = parseInt ( rgbB . value ) || 0 ;
260+ r = Math . min ( 255 , Math . max ( 0 , r ) ) ;
261+ g = Math . min ( 255 , Math . max ( 0 , g ) ) ;
262+ b = Math . min ( 255 , Math . max ( 0 , b ) ) ;
263+ const hex = rgbToHex ( r , g , b ) ;
264+ const hsl = rgbToHsl ( r , g , b ) ;
265+ updateUI ( hex , r , g , b , hsl . h , hsl . s , hsl . l ) ;
266+ } ) ;
267+ } ) ;
268+
269+ [ hslH , hslS , hslL ] . forEach ( el => {
270+ el . addEventListener ( 'input' , ( ) => {
271+ let h = parseInt ( hslH . value ) || 0 ;
272+ let s = parseInt ( hslS . value ) || 0 ;
273+ let l = parseInt ( hslL . value ) || 0 ;
274+ h = Math . min ( 360 , Math . max ( 0 , h ) ) ;
275+ s = Math . min ( 100 , Math . max ( 0 , s ) ) ;
276+ l = Math . min ( 100 , Math . max ( 0 , l ) ) ;
277+ const rgb = hslToRgb ( h , s , l ) ;
278+ const hex = rgbToHex ( rgb . r , rgb . g , rgb . b ) ;
279+ updateUI ( hex , rgb . r , rgb . g , rgb . b , h , s , l ) ;
280+ } ) ;
281+ } ) ;
282+
283+ // Copy buttons logic
284+ container . querySelectorAll ( '.btn-copy' ) . forEach ( btn => {
285+ btn . onclick = ( ) => {
286+ const targetId = btn . getAttribute ( 'data-target' ) ;
287+ const input = document . getElementById ( targetId ) ;
288+ input . select ( ) ;
289+ document . execCommand ( 'copy' ) ;
290+
291+ const originalText = btn . innerText ;
292+ btn . innerText = t . copied ;
293+ setTimeout ( ( ) => { btn . innerText = originalText ; } , 2000 ) ;
294+ } ;
295+ } ) ;
296+ } ) ( ) ;
0 commit comments