@@ -6,9 +6,9 @@ import React, {
66 useMemo ,
77} from 'react' ;
88import '../styles/PickerWheel.css' ;
9- import colors from '../config/colors' ;
10- import { Trash } from '@phosphor-icons/react' ;
9+ import { TrashIcon , PlusIcon , ListBulletsIcon , ArrowsClockwiseIcon } from '@phosphor-icons/react' ;
1110import ListInputModal from './ListInputModal' ;
11+ import GenerativeArt from './GenerativeArt' ;
1212
1313const PickerWheel = ( ) => {
1414 const [ entries , setEntries ] = useState ( [ ] ) ;
@@ -23,46 +23,16 @@ const PickerWheel = () => {
2323
2424 const colorPalette = useMemo (
2525 ( ) => [
26- '#FDE2E4' ,
27- '#E2ECE9' ,
28- '#BEE1E6' ,
29- '#F0EFEB' ,
30- '#DFE7FD' ,
31- '#CDDAFD' ,
32- '#EAD5E6' ,
33- '#F4C7C3' ,
34- '#D6E2E9' ,
35- '#B9E2E6' ,
36- '#F9D8D6' ,
37- '#D4E9E6' ,
38- '#A8DADC' ,
39- '#E9E4F2' ,
40- '#D0D9FB' ,
41- '#C0CFFB' ,
42- '#E3C8DE' ,
43- '#F1BDBD' ,
44- '#C9D5DE' ,
45- '#A1D5DB' ,
46- '#F6C4C1' ,
47- '#C1E0DA' ,
48- '#92D2D2' ,
49- '#E2DDF0' ,
50- '#C3CEFA' ,
51- '#B3C4FA' ,
52- '#DBBBD1' ,
53- '#EDB3B0' ,
54- '#BCC8D3' ,
55- '#8DCED1' ,
26+ '#FDE2E4' , '#E2ECE9' , '#BEE1E6' , '#F0EFEB' , '#DFE7FD' ,
27+ '#CDDAFD' , '#EAD5E6' , '#F4C7C3' , '#D6E2E9' , '#B9E2E6' ,
28+ '#F9D8D6' , '#D4E9E6' , '#A8DADC' , '#E9E4F2' , '#D0D9FB' ,
29+ '#C0CFFB' , '#E3C8DE' , '#F1BDBD' , '#C9D5DE' , '#A1D5DB' ,
30+ '#F6C4C1' , '#C1E0DA' , '#92D2D2' , '#E2DDF0' , '#C3CEFA' ,
31+ '#B3C4FA' , '#DBBBD1' , '#EDB3B0' , '#BCC8D3' , '#8DCED1' ,
5632 ] ,
5733 [ ] ,
5834 ) ;
5935
60- const cardStyle = {
61- backgroundColor : colors [ 'app-alpha-10' ] ,
62- borderColor : colors [ 'app-alpha-50' ] ,
63- color : colors . app ,
64- } ;
65-
6636 const drawWheel = useCallback ( ( ) => {
6737 const canvas = canvasRef . current ;
6838 if ( ! canvas ) return ;
@@ -86,13 +56,13 @@ const PickerWheel = () => {
8656
8757 ctx . save ( ) ;
8858 ctx . fillStyle = '#000' ;
89- ctx . font = '30px Arial ' ;
59+ ctx . font = 'bold 24px "Space Mono" ' ;
9060 ctx . translate (
9161 width / 2 + Math . cos ( angle + arc / 2 ) * ( width / 2 - 80 ) ,
9262 height / 2 + Math . sin ( angle + arc / 2 ) * ( height / 2 - 80 ) ,
9363 ) ;
9464 ctx . rotate ( angle + arc / 2 + Math . PI / 2 ) ;
95- const text = entries [ i ] ;
65+ const text = entries [ i ] . toUpperCase ( ) ;
9666 ctx . fillText ( text , - ctx . measureText ( text ) . width / 2 , 0 ) ;
9767 ctx . restore ( ) ;
9868 }
@@ -122,9 +92,7 @@ const PickerWheel = () => {
12292 } ;
12393
12494 const handleKeyDown = ( e ) => {
125- if ( e . key === 'Enter' ) {
126- addEntry ( ) ;
127- }
95+ if ( e . key === 'Enter' ) addEntry ( ) ;
12896 } ;
12997
13098 const deleteEntry = ( index ) => {
@@ -160,7 +128,7 @@ const PickerWheel = () => {
160128 const canvas = canvasRef . current ;
161129 const ctx = canvas . getContext ( '2d' ) ;
162130 const pinX = canvas . width / 2 ;
163- const pinY = 30 ; // Position of the pin
131+ const pinY = 30 ;
164132 const pixel = ctx . getImageData ( pinX , pinY , 1 , 1 ) . data ;
165133 const pixelColor = `rgb(${ pixel [ 0 ] } , ${ pixel [ 1 ] } , ${ pixel [ 2 ] } )` ;
166134
@@ -191,127 +159,121 @@ const PickerWheel = () => {
191159 } ;
192160
193161 return (
194- < div
195- className = "group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-start relative w-full flex-grow"
196- style = { cardStyle }
197- >
198- < div
199- className = "absolute top-0 left-0 w-full h-full opacity-10"
200- style = { {
201- backgroundImage :
202- 'radial-gradient(circle, white 1px, transparent 1px)' ,
203- backgroundSize : '10px 10px' ,
204- } }
205- > </ div >
206- < div className = "relative z-10" >
207- < h1 className = "text-3xl font-arvo font-normal mb-4 text-app" >
208- { ' ' }
209- Picker Wheel{ ' ' }
210- </ h1 >
211- < hr className = "border-gray-700 mb-4" />
212- < div className = "flex gap-8" >
213- < div className = "flex flex-col items-center" >
214- < div className = "picker-wheel-container mt-8" >
215- < div className = "wheel-wrapper" >
216- < div className = "pin" > </ div >
217- < canvas
218- ref = { canvasRef }
219- width = "600"
220- height = "600"
221- className = { `wheel ${ entries . length > 1 && ! spinning ? 'slow-spin' : '' } ` }
222- > </ canvas >
162+ < div className = "w-full flex flex-col gap-12" >
163+ < div className = "relative border border-white/10 bg-white/[0.02] p-8 md:p-12 rounded-sm overflow-hidden group" >
164+ < div className = "absolute inset-0 opacity-[0.03] pointer-events-none grayscale" >
165+ < GenerativeArt seed = "picker-wheel" className = "w-full h-full" />
166+ </ div >
167+
168+ < div className = "relative z-10" >
169+ < div className = "grid grid-cols-1 lg:grid-cols-2 gap-16" >
170+ < div className = "flex flex-col items-center gap-8" >
171+ < div className = "picker-wheel-container" >
172+ < div className = "wheel-wrapper" >
173+ < div className = "pin" > </ div >
174+ < canvas
175+ ref = { canvasRef }
176+ width = "600"
177+ height = "600"
178+ className = { `wheel ${ entries . length > 1 && ! spinning ? 'slow-spin' : '' } ` }
179+ > </ canvas >
180+ < button
181+ onClick = { spin }
182+ className = "spin-button font-black uppercase tracking-widest text-xs"
183+ disabled = { spinning || entries . length < 2 }
184+ >
185+ { spinning ? '...' : winner ? winner . toUpperCase ( ) : 'SPIN' }
186+ </ button >
187+ </ div >
188+ </ div >
189+
190+ < div className = "h-12 flex items-center" >
191+ { spinning ? (
192+ < div className = "flex items-center gap-3 text-emerald-500 font-mono text-xs uppercase tracking-[0.3em]" >
193+ < ArrowsClockwiseIcon className = "animate-spin" />
194+ < span > Spinning...</ span >
195+ </ div >
196+ ) : winner ? (
197+ < div className = "flex flex-col items-center gap-1" >
198+ < span className = "text-[10px] font-mono text-gray-500 uppercase tracking-widest" > The winner is</ span >
199+ < span className = "text-3xl font-black text-white uppercase tracking-tighter italic" > { winner } </ span >
200+ </ div >
201+ ) : null }
202+ </ div >
203+ </ div >
204+
205+ < div className = "space-y-12" >
206+ < div className = "space-y-6" >
207+ < h2 className = "text-2xl font-black uppercase tracking-tighter" > Options</ h2 >
208+ < div className = "flex gap-4" >
209+ < input
210+ ref = { newEntryInputRef }
211+ type = "text"
212+ value = { newEntry }
213+ onChange = { ( e ) => setNewEntry ( e . target . value ) }
214+ onKeyDown = { handleKeyDown }
215+ placeholder = "Add an option (max 30)"
216+ className = "flex-1 bg-black/40 border border-white/10 p-4 font-mono text-sm focus:border-emerald-500 outline-none transition-colors text-white"
217+ disabled = { entries . length >= 30 }
218+ />
219+ < button
220+ onClick = { addEntry }
221+ disabled = { entries . length >= 30 }
222+ className = "px-8 py-4 bg-white text-black font-black uppercase tracking-widest text-xs hover:bg-emerald-500 transition-all disabled:opacity-20"
223+ >
224+ < PlusIcon weight = "bold" size = { 20 } />
225+ </ button >
226+ </ div >
223227 < button
224- onClick = { spin }
225- className = "spin-button"
226- disabled = { spinning || entries . length < 2 }
228+ onClick = { ( ) => setIsModalOpen ( true ) }
229+ className = "w-full py-4 border border-white/10 text-gray-500 hover:text-white hover:bg-white/5 transition-all font-mono text-[10px] uppercase tracking-widest flex items-center justify-center gap-2"
227230 >
228- { spinning ? '...' : winner ? winner : 'Spin' }
231+ < ListBulletsIcon weight = "bold" size = { 16 } />
232+ Load from List
229233 </ button >
234+ < p className = "text-[10px] font-mono text-gray-500 uppercase tracking-widest" >
235+ { entries . length } / 30 options added
236+ </ p >
237+ </ div >
238+
239+ < div className = "space-y-4" >
240+ < div className = "grid grid-cols-1 sm:grid-cols-2 gap-2 max-h-[300px] overflow-y-auto custom-scrollbar-terminal pr-4" >
241+ { entries . map ( ( entry , index ) => (
242+ < div key = { index } className = "group/item flex items-center justify-between bg-white/5 border border-white/5 p-3 hover:border-white/20 transition-all" >
243+ < span className = "text-xs font-mono uppercase truncate mr-2 text-white" > { entry } </ span >
244+ < button
245+ onClick = { ( ) => deleteEntry ( index ) }
246+ className = "p-1 text-gray-600 hover:text-red-500 transition-colors opacity-0 group-hover/item:opacity-100"
247+ >
248+ < TrashIcon size = { 14 } weight = "bold" />
249+ </ button >
250+ </ div >
251+ ) ) }
252+ { entries . length === 0 && (
253+ < div className = "col-span-full py-12 border border-dashed border-white/10 text-center" >
254+ < span className = "text-[10px] font-mono text-gray-600 uppercase tracking-widest" > No options added</ span >
255+ </ div >
256+ ) }
257+ </ div >
230258 </ div >
231- </ div >
232- < div className = "winner mt-4" >
233- { spinning
234- ? 'Spinning...'
235- : winner
236- ? `The winner is: ${ winner } `
237- : '' }
238- </ div >
239- </ div >
240- < div className = "w-full max-w-lg ml-16" >
241- < div className = "controls" >
242- < input
243- ref = { newEntryInputRef }
244- type = "text"
245- value = { newEntry }
246- onChange = { ( e ) => setNewEntry ( e . target . value ) }
247- onKeyDown = { handleKeyDown }
248- placeholder = "Add an option (max 30)"
249- className = "bg-gray-800 text-white p-2 rounded-lg flex-grow"
250- disabled = { entries . length >= 30 }
251- />
252- < button
253- onClick = { addEntry }
254- className = "flex items-center gap-2 text-lg font-arvo font-normal px-4 py-2 rounded-md border transition-colors duration-300 ease-in-out border-app/100 bg-app/50 text-white hover:bg-app/70"
255- disabled = { entries . length >= 30 }
256- >
257- Add
258- </ button >
259- </ div >
260- < button
261- onClick = { ( ) => setIsModalOpen ( true ) }
262- className = "flex items-center justify-center w-full gap-2 text-lg font-arvo font-normal px-4 py-2 mt-4 rounded-md border transition-colors duration-300 ease-in-out border-app/100 bg-app/50 text-white hover:bg-app/70"
263- >
264- Load from List
265- </ button >
266- < div className = "w-full mt-4" >
267- < h2 className = "text-2xl font-arvo font-normal mb-4" >
268- Entries ({ entries . length } )
269- </ h2 >
270- < ul className = "space-y-2" >
271- { entries . map ( ( entry , index ) => (
272- < li
273- key = { index }
274- className = "flex items-center justify-between bg-gray-800/75 p-2 rounded-lg"
275- >
276- < span className = "flex-grow text-center" > { entry } </ span >
277- < button
278- onClick = { ( ) => deleteEntry ( index ) }
279- className = "flex items-center gap-2 text-lg font-mono font-normal px-2 py-2 rounded-md border transition-colors duration-300 ease-in-out border-app/100 bg-app/50 text-white hover:bg-app/70"
280- >
281- < Trash size = { 20 } />
282- </ button >
283- </ li >
284- ) ) }
285- </ ul >
286259 </ div >
287260 </ div >
288261 </ div >
289262 </ div >
290- < div
291- className = "group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-start relative w-full flex-grow mt-8"
292- style = { cardStyle }
293- >
294- < div
295- className = "absolute top-0 left-0 w-full h-full opacity-10"
296- style = { {
297- backgroundImage :
298- 'radial-gradient(circle, white 1px, transparent 1px)' ,
299- backgroundSize : '10px 10px' ,
300- } }
301- > </ div >
302- < div className = "relative z-10" >
303- < h2 className = "text-2xl font-arvo font-normal mb-4" > How it works</ h2 >
304- < p className = "text-gray-400" >
305- ✦ Add entries one by one using the input field and "Add" button, or
306- load a list of entries using the "Load from List" button. < br />
307- ✦ The wheel will display the entries as equal divisions. < br />
308- ✦ Click the "Spin" button to spin the wheel. It will spin fast and
309- then slowly get slower, eventually stopping on a winner. < br />
310- ✦ The winner will be displayed below the wheel and in the center of
311- the wheel. < br />
312- </ p >
313- </ div >
263+
264+ < div className = "p-8 border border-white/10 bg-white/[0.01] rounded-sm" >
265+ < h2 className = "text-sm font-black uppercase tracking-widest mb-4 flex items-center gap-2" >
266+ < ArrowsClockwiseIcon weight = "bold" className = "text-emerald-500" />
267+ How it works
268+ </ h2 >
269+ < ul className = "space-y-3 text-[10px] font-mono text-gray-500 uppercase tracking-widest leading-relaxed" >
270+ < li > • Add entries one by one or load a list.</ li >
271+ < li > • The wheel divides space equally among all options.</ li >
272+ < li > • Click the center to spin.</ li >
273+ < li > • The stop position is determined by physical pixel sampling.</ li >
274+ </ ul >
314275 </ div >
276+
315277 < ListInputModal
316278 isOpen = { isModalOpen }
317279 onClose = { ( ) => setIsModalOpen ( false ) }
0 commit comments