11import React , { useState , useEffect } from 'react' ;
22import { Link } from 'react-router-dom' ;
3- import { ArrowLeftIcon , CirclesFourIcon } from '@phosphor-icons/react' ;
4- import colors from '../../config/colors' ;
3+ import {
4+ ArrowLeftIcon ,
5+ CirclesFourIcon ,
6+ ArrowsClockwiseIcon ,
7+ InfoIcon ,
8+ ShieldCheckIcon ,
9+ TrophyIcon
10+ } from '@phosphor-icons/react' ;
511import useSeo from '../../hooks/useSeo' ;
612import BreadcrumbTitle from '../../components/BreadcrumbTitle' ;
713import { useAchievements } from '../../context/AchievementContext' ;
14+ import GenerativeArt from '../../components/GenerativeArt' ;
815
9- const BUBBLE_COUNT = 100 ; // Number of bubbles
16+ const BUBBLE_COUNT = 100 ;
1017
1118const BubbleWrapPage = ( ) => {
1219 useSeo ( {
@@ -25,8 +32,6 @@ const BubbleWrapPage = () => {
2532 const { unlockAchievement } = useAchievements ( ) ;
2633 const [ bubbles , setBubbles ] = useState ( Array ( BUBBLE_COUNT ) . fill ( false ) ) ;
2734 const [ popCount , setPopCount ] = useState ( 0 ) ;
28-
29- // Audio context for simple pop sound
3035 const [ audioContext , setAudioContext ] = useState ( null ) ;
3136
3237 useEffect ( ( ) => {
@@ -48,19 +53,19 @@ const BubbleWrapPage = () => {
4853 oscillator . connect ( gainNode ) ;
4954 gainNode . connect ( audioContext . destination ) ;
5055
51- oscillator . type = 'square' ; // Change to square wave for a harsher sound
52- oscillator . frequency . value = 100 + Math . random ( ) * 200 ; // Lower frequency for more bass
56+ oscillator . type = 'square' ;
57+ oscillator . frequency . value = 100 + Math . random ( ) * 200 ;
5358
5459 const now = audioContext . currentTime ;
55- gainNode . gain . setValueAtTime ( 0.5 , now ) ; // Start louder
56- gainNode . gain . exponentialRampToValueAtTime ( 0.001 , now + 0.08 ) ; // Faster and shorter decay
60+ gainNode . gain . setValueAtTime ( 0.5 , now ) ;
61+ gainNode . gain . exponentialRampToValueAtTime ( 0.001 , now + 0.08 ) ;
5762
5863 oscillator . start ( now ) ;
59- oscillator . stop ( now + 0.08 ) ; // Very short duration
64+ oscillator . stop ( now + 0.08 ) ;
6065 } ;
6166
6267 const popBubble = ( index ) => {
63- if ( bubbles [ index ] ) return ; // Already popped
68+ if ( bubbles [ index ] ) return ;
6469
6570 const newBubbles = [ ...bubbles ] ;
6671 newBubbles [ index ] = true ;
@@ -74,84 +79,138 @@ const BubbleWrapPage = () => {
7479 setPopCount ( 0 ) ;
7580 } ;
7681
77- const cardStyle = {
78- backgroundColor : colors [ 'app-alpha-10' ] ,
79- borderColor : colors [ 'app-alpha-50' ] ,
80- color : colors . app ,
81- } ;
82+ const progress = Math . round ( ( popCount / BUBBLE_COUNT ) * 100 ) ;
8283
8384 return (
84- < div className = "py-16 sm:py-24" >
85- < div className = "mx-auto max-w-7xl px-6 lg:px-8 text-gray-300" >
86- < Link
87- to = "/apps"
88- className = "group text-primary-400 hover:underline flex items-center justify-center gap-2 text-lg mb-4"
89- >
90- < ArrowLeftIcon className = "text-xl transition-transform group-hover:-translate-x-1" />
91- Back to Apps
92- </ Link >
93- < BreadcrumbTitle title = "Bubble Wrap" slug = "pop" />
94- < hr className = "border-gray-700" />
95- < div className = "flex justify-center items-center mt-16" >
96- < div
97- className = "group bg-transparent border rounded-lg shadow-2xl p-6 flex flex-col justify-between relative transform overflow-hidden h-full w-full max-w-3xl"
98- style = { cardStyle }
99- >
100- < div
101- className = "absolute top-0 left-0 w-full h-full opacity-10"
102- style = { {
103- backgroundImage :
104- 'radial-gradient(circle, white 1px, transparent 1px)' ,
105- backgroundSize : '10px 10px' ,
106- } }
107- > </ div >
108- < div className = "relative z-10 p-1 text-center" >
109- < h1 className = "text-3xl font-arvo font-normal mb-4 text-app flex items-center justify-center gap-2" >
110- < CirclesFourIcon size = { 32 } /> Bubble Wrap
111- </ h1 >
112- < hr className = "border-gray-700 mb-6" />
113-
114- < div className = "flex justify-between items-center mb-6 px-4" >
115- < div className = "text-xl font-bold" > Popped: { popCount } </ div >
116- < button
117- onClick = { resetBubbles }
118- className = "px-4 py-2 rounded-md text-sm font-arvo font-normal border transition-colors duration-300 hover:bg-white/10"
119- style = { {
120- borderColor : cardStyle . color ,
121- color : cardStyle . color ,
122- } }
123- >
124- Get a fresh sheet
125- </ button >
126- </ div >
85+ < div className = "min-h-screen bg-[#050505] text-white selection:bg-emerald-500/30 font-sans" >
86+ < div className = "mx-auto max-w-7xl px-6 py-24 md:px-12" >
87+
88+ < header className = "mb-24" >
89+ < Link to = "/apps" className = "group mb-12 inline-flex items-center gap-2 text-xs font-mono text-gray-500 hover:text-white transition-colors uppercase tracking-[0.3em]" >
90+ < ArrowLeftIcon weight = "bold" className = "transition-transform group-hover:-translate-x-1" />
91+ < span > Applications</ span >
92+ </ Link >
93+
94+ < div className = "flex flex-col md:flex-row md:items-end justify-between gap-12" >
95+ < div className = "space-y-4" >
96+ < BreadcrumbTitle
97+ title = "Bubble Wrap"
98+ slug = "pop"
99+ variant = "brutalist"
100+ />
101+ < p className = "text-xl text-gray-400 max-w-2xl font-light leading-relaxed" >
102+ Digital stress relief. Pop virtual cells to satisfy your tactile cravings.
103+ </ p >
104+ </ div >
105+ </ div >
106+ </ header >
107+
108+ < div className = "grid grid-cols-1 lg:grid-cols-12 gap-12" >
109+
110+ { /* Controls & Configuration */ }
111+ < div className = "lg:col-span-4 space-y-8" >
112+ < div className = "border border-white/10 bg-white/[0.02] p-8 rounded-sm space-y-10" >
113+ < h3 className = "font-mono text-[10px] font-bold text-emerald-500 uppercase tracking-widest flex items-center gap-2" >
114+ < CirclesFourIcon weight = "fill" />
115+ Status
116+ </ h3 >
117+
118+ < div className = "space-y-8" >
119+ < div className = "space-y-4" >
120+ < div className = "flex justify-between items-end" >
121+ < label className = "font-mono text-[10px] text-gray-500 uppercase tracking-widest" > Completion</ label >
122+ < span className = "text-xl font-black text-emerald-500" > { progress } %</ span >
123+ </ div >
124+ < div className = "w-full bg-gray-800 h-1 rounded-sm overflow-hidden" >
125+ < div
126+ className = "bg-emerald-500 h-full transition-all duration-300"
127+ style = { { width : `${ progress } %` } }
128+ />
129+ </ div >
130+ </ div >
131+
132+ < div className = "space-y-4 pt-4 border-t border-white/5" >
133+ < div className = "flex justify-between items-end" >
134+ < label className = "font-mono text-[10px] text-gray-500 uppercase" > Popped Cells</ label >
135+ < span className = "text-xl font-black text-white" > { popCount } < span className = "text-gray-600 text-sm" > / { BUBBLE_COUNT } </ span > </ span >
136+ </ div >
137+ </ div >
127138
128- < div className = "grid grid-cols-5 sm:grid-cols-10 gap-2 justify-items-center" >
129- { bubbles . map ( ( isPopped , index ) => (
130- < button
131- key = { index }
132- onClick = { ( ) => popBubble ( index ) }
133- className = { `w-12 h-12 rounded-full border-2 transition-all duration-100 relative overflow-hidden focus:outline-none
134- ${
135- isPopped
136- ? 'bg-transparent scale-95 opacity-50 border-gray-600'
137- : 'bg-white/10 hover:bg-white/20 scale-100 cursor-pointer'
138- } `}
139- style = { {
140- borderColor : isPopped ? 'gray' : cardStyle . color ,
141- boxShadow : isPopped
142- ? 'inset 0 0 5px rgba(0,0,0,0.5)'
143- : '0 4px 6px rgba(0,0,0,0.3)' ,
144- } }
139+ < button
140+ onClick = { resetBubbles }
141+ className = "w-full py-3 border border-white/10 hover:border-white text-gray-400 hover:text-white transition-all text-xs font-black uppercase tracking-widest flex items-center justify-center gap-2"
145142 >
146- { ! isPopped && (
147- < div className = "absolute top-2 left-2 w-3 h-3 bg-white rounded-full opacity-30 blur-[1px]" > </ div >
148- ) }
143+ < ArrowsClockwiseIcon size = { 16 } weight = "bold" />
144+ Reset Sheet
149145 </ button >
150- ) ) }
151146 </ div >
152147 </ div >
148+
149+ < div className = "p-8 border border-white/10 bg-white/[0.01] rounded-sm flex items-start gap-4" >
150+ < InfoIcon size = { 24 } className = "text-gray-700 shrink-0" />
151+ < p className = "text-[10px] font-mono uppercase tracking-[0.2em] leading-relaxed text-gray-500" >
152+ Popping all cells triggers a completion event. Use this tool to reduce anxiety or procrastinate effectively.
153+ </ p >
154+ </ div >
155+ </ div >
156+
157+ { /* Game Area */ }
158+ < div className = "lg:col-span-8 space-y-12" >
159+ < div className = "relative border border-white/10 bg-white/[0.02] p-8 md:p-12 rounded-sm overflow-hidden group min-h-[600px] flex items-center justify-center" >
160+ < div className = "absolute inset-0 opacity-[0.03] pointer-events-none grayscale" >
161+ < GenerativeArt seed = "bubblewrap" className = "w-full h-full" />
162+ </ div >
163+
164+ < div className = "relative z-10 w-full max-w-2xl" >
165+ < div className = "grid grid-cols-10 gap-2 sm:gap-3 md:gap-4 justify-items-center" >
166+ { bubbles . map ( ( isPopped , index ) => (
167+ < button
168+ key = { index }
169+ onClick = { ( ) => popBubble ( index ) }
170+ className = { `w-8 h-8 sm:w-10 sm:h-10 rounded-full border-2 transition-all duration-200 relative overflow-hidden focus:outline-none group/bubble
171+ ${
172+ isPopped
173+ ? 'bg-transparent border-gray-800 scale-90'
174+ : 'bg-emerald-500/10 border-emerald-500/30 hover:bg-emerald-500/20 hover:border-emerald-500 hover:scale-105 cursor-pointer'
175+ } `}
176+ >
177+ { ! isPopped && (
178+ < >
179+ < div className = "absolute inset-0 opacity-0 group-hover/bubble:opacity-100 transition-opacity bg-emerald-400/10" />
180+ < div className = "absolute top-1.5 left-1.5 w-2 h-2 sm:w-2.5 sm:h-2.5 bg-white rounded-full opacity-20 blur-[0.5px] pointer-events-none" />
181+ </ >
182+ ) }
183+ { isPopped && (
184+ < div className = "absolute inset-0 flex items-center justify-center" >
185+ < div className = "w-1 h-1 bg-gray-800 rounded-full" />
186+ </ div >
187+ ) }
188+ </ button >
189+ ) ) }
190+ </ div >
191+ </ div >
192+ </ div >
193+
194+ < div className = "p-8 border border-white/10 bg-white/[0.01] rounded-sm flex items-center justify-between gap-6" >
195+ < div className = "flex items-center gap-4" >
196+ < ShieldCheckIcon size = { 32 } className = "text-emerald-500/50" />
197+ < span className = "text-[10px] font-mono text-gray-500 uppercase tracking-widest" > Audio Engine: Active</ span >
198+ </ div >
199+ { popCount === BUBBLE_COUNT && (
200+ < div className = "flex items-center gap-2 text-emerald-500 animate-pulse" >
201+ < TrophyIcon weight = "fill" />
202+ < span className = "text-[10px] font-mono uppercase tracking-widest font-bold" > Sheet Cleared</ span >
203+ </ div >
204+ ) }
205+ </ div >
153206 </ div >
207+
154208 </ div >
209+
210+ < footer className = "mt-32 pt-12 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-6 text-gray-600 font-mono text-[10px] uppercase tracking-[0.3em]" >
211+ < span > Fezcodex_Bubble_Wrap_v1.0.0</ span >
212+ < span className = "text-gray-800" > SIMULATION // ACTIVE</ span >
213+ </ footer >
155214 </ div >
156215 </ div >
157216 ) ;
0 commit comments