Spaces:
Running
Running
Update pages/GameZen.tsx
Browse files- pages/GameZen.tsx +74 -28
pages/GameZen.tsx
CHANGED
|
@@ -176,6 +176,7 @@ export const GameZen: React.FC = () => {
|
|
| 176 |
// Game Logic
|
| 177 |
if (delta > 0) {
|
| 178 |
setTotalSeconds(prev => prev + delta);
|
|
|
|
| 179 |
if (vol < threshold) {
|
| 180 |
setQuietSeconds(prev => prev + delta);
|
| 181 |
}
|
|
@@ -194,7 +195,16 @@ export const GameZen: React.FC = () => {
|
|
| 194 |
|
| 195 |
// Score Calculation
|
| 196 |
const calculatedScore = totalSeconds > 0 ? Math.round((quietSeconds / totalSeconds) * 100) : 100;
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
|
| 199 |
const handleBatchGrant = async () => {
|
| 200 |
const targets = students.filter(s => !excludedStudentIds.has(s._id || String(s.id)));
|
|
@@ -232,43 +242,67 @@ export const GameZen: React.FC = () => {
|
|
| 232 |
width: '100vw',
|
| 233 |
height: '100vh',
|
| 234 |
zIndex: 999999,
|
| 235 |
-
backgroundColor: '#0f766e',
|
| 236 |
} : {};
|
| 237 |
|
| 238 |
-
//
|
| 239 |
-
const
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
| 243 |
const GameContent = (
|
| 244 |
<div
|
| 245 |
-
className={`${isFullscreen ? '' : 'h-full w-full relative'} flex flex-col
|
| 246 |
style={containerStyle}
|
| 247 |
>
|
| 248 |
-
{/* Background */}
|
| 249 |
-
<div className="absolute inset-0 bg-
|
| 250 |
-
<div className="absolute inset-0 flex items-center justify-center pointer-events-none opacity-
|
| 251 |
-
<div className={`w-[800px] h-[800px] border-[50px] rounded-full border-white transition-all duration-1000 ${
|
| 252 |
</div>
|
| 253 |
|
| 254 |
{/* HUD */}
|
| 255 |
<div className="absolute top-4 left-4 z-50 flex gap-3">
|
| 256 |
-
<div className="bg-black/30 border border-
|
| 257 |
-
<span className="text-[10px] text-
|
| 258 |
<span className="text-3xl font-mono font-black text-white">
|
| 259 |
{Math.floor(timeLeft / 60)}:{String(Math.floor(timeLeft % 60)).padStart(2, '0')}
|
| 260 |
</span>
|
| 261 |
</div>
|
| 262 |
-
<div className="bg-black/30 border border-
|
| 263 |
-
<span className="text-[10px] text-
|
| 264 |
<span className={`text-3xl font-mono font-black ${calculatedScore >= passRate ? 'text-green-400' : 'text-red-400'}`}>{calculatedScore}</span>
|
| 265 |
</div>
|
| 266 |
</div>
|
| 267 |
|
| 268 |
{/* Status Indicator */}
|
| 269 |
<div className="absolute top-4 right-4 z-50">
|
| 270 |
-
<div className={`px-
|
| 271 |
-
{
|
| 272 |
</div>
|
| 273 |
</div>
|
| 274 |
|
|
@@ -311,20 +345,20 @@ export const GameZen: React.FC = () => {
|
|
| 311 |
<div className="flex-1 relative z-10 flex flex-col items-center justify-center pb-20">
|
| 312 |
{/* Aura */}
|
| 313 |
<div
|
| 314 |
-
className=
|
| 315 |
-
style={{ opacity:
|
| 316 |
></div>
|
| 317 |
|
| 318 |
{/* Monk/Visual */}
|
| 319 |
<div
|
| 320 |
-
className=
|
| 321 |
-
style={{ transform: `translateY(${monkY}px) scale(${monkScale})` }}
|
| 322 |
>
|
| 323 |
-
{
|
| 324 |
</div>
|
| 325 |
|
| 326 |
-
{/* Levitation Base */}
|
| 327 |
-
<div className={`w-40 h-10 bg-black/
|
| 328 |
</div>
|
| 329 |
|
| 330 |
{/* Config Modal */}
|
|
@@ -346,24 +380,36 @@ export const GameZen: React.FC = () => {
|
|
| 346 |
<span>阈值设定: {threshold}</span>
|
| 347 |
</div>
|
| 348 |
<div className="w-full h-6 bg-gray-800 rounded-full overflow-hidden relative border border-gray-600">
|
|
|
|
| 349 |
<div className="absolute top-0 bottom-0 w-0.5 bg-yellow-500 z-10 shadow-[0_0_5px_yellow]" style={{left: `${threshold}%`}}></div>
|
|
|
|
|
|
|
| 350 |
<div
|
| 351 |
-
className={`h-full transition-all duration-75 ${
|
|
|
|
|
|
|
|
|
|
|
|
|
| 352 |
style={{width: `${Math.min(currentVolume, 100)}%`}}
|
| 353 |
></div>
|
| 354 |
</div>
|
| 355 |
-
<div className="mt-2">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
<input type="range" min="1" max="100" className="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer" value={threshold} onChange={e=>setThreshold(Number(e.target.value))}/>
|
| 357 |
</div>
|
| 358 |
</div>
|
| 359 |
<button
|
| 360 |
-
onClick={() => { startAudio(); setThreshold(currentVolume +
|
| 361 |
className="px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg text-xs font-bold whitespace-nowrap border border-slate-600"
|
| 362 |
>
|
| 363 |
<Volume2 size={14} className="inline mr-1"/> 自动设定
|
| 364 |
</button>
|
| 365 |
</div>
|
| 366 |
-
<p className="text-[10px] text-gray-500 mt-2">*
|
| 367 |
</div>
|
| 368 |
|
| 369 |
<div>
|
|
|
|
| 176 |
// Game Logic
|
| 177 |
if (delta > 0) {
|
| 178 |
setTotalSeconds(prev => prev + delta);
|
| 179 |
+
// Count as quiet only if below threshold (Levels 1 & 2)
|
| 180 |
if (vol < threshold) {
|
| 181 |
setQuietSeconds(prev => prev + delta);
|
| 182 |
}
|
|
|
|
| 195 |
|
| 196 |
// Score Calculation
|
| 197 |
const calculatedScore = totalSeconds > 0 ? Math.round((quietSeconds / totalSeconds) * 100) : 100;
|
| 198 |
+
|
| 199 |
+
// Determine Current State (4 Levels)
|
| 200 |
+
const getZenState = () => {
|
| 201 |
+
if (currentVolume < threshold * 0.5) return 'DEEP';
|
| 202 |
+
if (currentVolume < threshold) return 'FOCUSED';
|
| 203 |
+
if (currentVolume < threshold * 1.5) return 'RESTLESS';
|
| 204 |
+
return 'CHAOS';
|
| 205 |
+
};
|
| 206 |
+
|
| 207 |
+
const currentState = getZenState();
|
| 208 |
|
| 209 |
const handleBatchGrant = async () => {
|
| 210 |
const targets = students.filter(s => !excludedStudentIds.has(s._id || String(s.id)));
|
|
|
|
| 242 |
width: '100vw',
|
| 243 |
height: '100vh',
|
| 244 |
zIndex: 999999,
|
|
|
|
| 245 |
} : {};
|
| 246 |
|
| 247 |
+
// Visual Parameters Mapping
|
| 248 |
+
const visualParams = {
|
| 249 |
+
DEEP: {
|
| 250 |
+
bg: 'bg-gradient-to-b from-teal-900 to-teal-800',
|
| 251 |
+
monk: '🧘', monkY: -50, monkScale: 1.2,
|
| 252 |
+
text: '🌿 极静 · 入定', badge: 'bg-teal-500/20 text-teal-200 border-teal-500/50',
|
| 253 |
+
auraColor: 'bg-emerald-300', auraOpacity: 0.6, auraScale: 1.5
|
| 254 |
+
},
|
| 255 |
+
FOCUSED: {
|
| 256 |
+
bg: 'bg-gradient-to-b from-emerald-800 to-emerald-600',
|
| 257 |
+
monk: '🧘', monkY: 0, monkScale: 1.0,
|
| 258 |
+
text: '🍃 专注 · 宁静', badge: 'bg-emerald-500/20 text-emerald-100 border-emerald-500/50',
|
| 259 |
+
auraColor: 'bg-emerald-200', auraOpacity: 0.3, auraScale: 1.1
|
| 260 |
+
},
|
| 261 |
+
RESTLESS: {
|
| 262 |
+
bg: 'bg-gradient-to-b from-amber-800 to-orange-700',
|
| 263 |
+
monk: '😰', monkY: 10, monkScale: 0.95,
|
| 264 |
+
text: '🍂 浮躁 · 波动', badge: 'bg-amber-500/20 text-amber-100 border-amber-500/50',
|
| 265 |
+
auraColor: 'bg-orange-400', auraOpacity: 0.2, auraScale: 0.9
|
| 266 |
+
},
|
| 267 |
+
CHAOS: {
|
| 268 |
+
bg: 'bg-gradient-to-b from-red-900 to-red-700',
|
| 269 |
+
monk: '😖', monkY: 20, monkScale: 0.9,
|
| 270 |
+
text: '🔥 喧哗 · 破功', badge: 'bg-red-500/20 text-red-100 border-red-500/50',
|
| 271 |
+
auraColor: 'bg-red-500', auraOpacity: 0.1, auraScale: 0.8
|
| 272 |
+
}
|
| 273 |
+
};
|
| 274 |
+
|
| 275 |
+
const currentVisual = visualParams[currentState];
|
| 276 |
|
| 277 |
const GameContent = (
|
| 278 |
<div
|
| 279 |
+
className={`${isFullscreen ? '' : 'h-full w-full relative'} flex flex-col overflow-hidden select-none transition-all duration-1000 ease-in-out font-sans ${currentVisual.bg}`}
|
| 280 |
style={containerStyle}
|
| 281 |
>
|
| 282 |
+
{/* Background Overlay */}
|
| 283 |
+
<div className="absolute inset-0 bg-black/10 pointer-events-none"></div>
|
| 284 |
+
<div className="absolute inset-0 flex items-center justify-center pointer-events-none opacity-20">
|
| 285 |
+
<div className={`w-[800px] h-[800px] border-[50px] rounded-full border-white transition-all duration-1000 ${currentState === 'DEEP' ? 'scale-110 opacity-50' : 'scale-90 opacity-20'}`}></div>
|
| 286 |
</div>
|
| 287 |
|
| 288 |
{/* HUD */}
|
| 289 |
<div className="absolute top-4 left-4 z-50 flex gap-3">
|
| 290 |
+
<div className="bg-black/30 border border-white/20 rounded-xl p-3 min-w-[120px] text-center backdrop-blur-md">
|
| 291 |
+
<span className="text-[10px] text-gray-300 font-bold uppercase block">倒计时</span>
|
| 292 |
<span className="text-3xl font-mono font-black text-white">
|
| 293 |
{Math.floor(timeLeft / 60)}:{String(Math.floor(timeLeft % 60)).padStart(2, '0')}
|
| 294 |
</span>
|
| 295 |
</div>
|
| 296 |
+
<div className="bg-black/30 border border-white/20 rounded-xl p-3 min-w-[120px] text-center backdrop-blur-md">
|
| 297 |
+
<span className="text-[10px] text-gray-300 font-bold uppercase block">专注评分</span>
|
| 298 |
<span className={`text-3xl font-mono font-black ${calculatedScore >= passRate ? 'text-green-400' : 'text-red-400'}`}>{calculatedScore}</span>
|
| 299 |
</div>
|
| 300 |
</div>
|
| 301 |
|
| 302 |
{/* Status Indicator */}
|
| 303 |
<div className="absolute top-4 right-4 z-50">
|
| 304 |
+
<div className={`px-6 py-3 rounded-full font-bold text-lg shadow-lg backdrop-blur-md transition-all duration-500 border ${currentVisual.badge}`}>
|
| 305 |
+
{currentVisual.text}
|
| 306 |
</div>
|
| 307 |
</div>
|
| 308 |
|
|
|
|
| 345 |
<div className="flex-1 relative z-10 flex flex-col items-center justify-center pb-20">
|
| 346 |
{/* Aura */}
|
| 347 |
<div
|
| 348 |
+
className={`absolute w-64 h-64 rounded-full blur-[80px] transition-all duration-1000 ${currentVisual.auraColor}`}
|
| 349 |
+
style={{ opacity: currentVisual.auraOpacity, transform: `scale(${currentVisual.auraScale})` }}
|
| 350 |
></div>
|
| 351 |
|
| 352 |
{/* Monk/Visual */}
|
| 353 |
<div
|
| 354 |
+
className={`text-[150px] transition-all duration-1000 ease-in-out relative z-20 drop-shadow-2xl ${currentState === 'CHAOS' ? 'animate-bounce' : currentState === 'RESTLESS' ? 'animate-pulse' : ''}`}
|
| 355 |
+
style={{ transform: `translateY(${currentVisual.monkY}px) scale(${currentVisual.monkScale})` }}
|
| 356 |
>
|
| 357 |
+
{currentVisual.monk}
|
| 358 |
</div>
|
| 359 |
|
| 360 |
+
{/* Levitation Base (Shadow) */}
|
| 361 |
+
<div className={`w-40 h-10 bg-black/30 rounded-[100%] blur-md transition-all duration-1000 ${currentState === 'DEEP' ? 'scale-50 opacity-40 translate-y-10' : 'scale-100 opacity-80'}`}></div>
|
| 362 |
</div>
|
| 363 |
|
| 364 |
{/* Config Modal */}
|
|
|
|
| 380 |
<span>阈值设定: {threshold}</span>
|
| 381 |
</div>
|
| 382 |
<div className="w-full h-6 bg-gray-800 rounded-full overflow-hidden relative border border-gray-600">
|
| 383 |
+
{/* Threshold Marker */}
|
| 384 |
<div className="absolute top-0 bottom-0 w-0.5 bg-yellow-500 z-10 shadow-[0_0_5px_yellow]" style={{left: `${threshold}%`}}></div>
|
| 385 |
+
|
| 386 |
+
{/* Current Vol Bar */}
|
| 387 |
<div
|
| 388 |
+
className={`h-full transition-all duration-75 ${
|
| 389 |
+
currentVolume < threshold * 0.5 ? 'bg-teal-500' :
|
| 390 |
+
currentVolume < threshold ? 'bg-emerald-500' :
|
| 391 |
+
currentVolume < threshold * 1.5 ? 'bg-orange-500' : 'bg-red-600'
|
| 392 |
+
}`}
|
| 393 |
style={{width: `${Math.min(currentVolume, 100)}%`}}
|
| 394 |
></div>
|
| 395 |
</div>
|
| 396 |
+
<div className="mt-2 flex justify-between text-[10px] text-gray-500 px-1">
|
| 397 |
+
<span>静</span>
|
| 398 |
+
<span>↑ 阈值</span>
|
| 399 |
+
<span>闹</span>
|
| 400 |
+
</div>
|
| 401 |
+
<div className="mt-1">
|
| 402 |
<input type="range" min="1" max="100" className="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer" value={threshold} onChange={e=>setThreshold(Number(e.target.value))}/>
|
| 403 |
</div>
|
| 404 |
</div>
|
| 405 |
<button
|
| 406 |
+
onClick={() => { startAudio(); setThreshold(currentVolume + 10); }}
|
| 407 |
className="px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg text-xs font-bold whitespace-nowrap border border-slate-600"
|
| 408 |
>
|
| 409 |
<Volume2 size={14} className="inline mr-1"/> 自动设定
|
| 410 |
</button>
|
| 411 |
</div>
|
| 412 |
+
<p className="text-[10px] text-gray-500 mt-2">* 绿色区域为安静(计分),橙色/红色区域为喧哗(不计分)。请在安静环境下点击自动设定。</p>
|
| 413 |
</div>
|
| 414 |
|
| 415 |
<div>
|