84 lines
3.8 KiB
Markdown
84 lines
3.8 KiB
Markdown
|
|
# Header Text Player Meter — Design
|
|||
|
|
|
|||
|
|
## Summary
|
|||
|
|
|
|||
|
|
Add a gradient fill meter to the "ROOM CODE:" header text that visually represents lobby fill (playerCount / maxPlayers). The fill sweeps left-to-right from the configured header color (pink) to white. A pulse/glow fires when the lobby is full.
|
|||
|
|
|
|||
|
|
Also: revert the player list checkbox to unchecked by default.
|
|||
|
|
|
|||
|
|
## Requirements
|
|||
|
|
|
|||
|
|
- Smooth CSS gradient across the header text, not per-character or per-word.
|
|||
|
|
- Fill percentage = `playerCount / maxPlayers`, clamped to `[0, 1]`.
|
|||
|
|
- 0 players → 100% pink. All players → 100% white.
|
|||
|
|
- Gradient edge animates smoothly (~400ms ease) when player count changes.
|
|||
|
|
- Brief pulse/glow animation when fill reaches 100%.
|
|||
|
|
- Existing header settings (text, color, size, offset) continue to work. The configured header color becomes the "unfilled" color; white is always the "filled" color.
|
|||
|
|
- Player list disabled by default.
|
|||
|
|
|
|||
|
|
## Approach
|
|||
|
|
|
|||
|
|
CSS `background-clip: text` with a dynamic `linear-gradient`.
|
|||
|
|
|
|||
|
|
### CSS Changes (`optimized-controls.html`)
|
|||
|
|
|
|||
|
|
Replace the static `#header` color with gradient-compatible styles:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
#header {
|
|||
|
|
/* existing position, font, letter-spacing, opacity, transition stay */
|
|||
|
|
-webkit-background-clip: text;
|
|||
|
|
background-clip: text;
|
|||
|
|
-webkit-text-fill-color: transparent;
|
|||
|
|
/* drop-shadow replaces text-shadow (incompatible with transparent text) */
|
|||
|
|
filter: drop-shadow(3px 3px 8px rgba(0, 0, 0, 0.8));
|
|||
|
|
text-shadow: none;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
New keyframes for the full-lobby pulse:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
@keyframes meter-full-pulse {
|
|||
|
|
0% { filter: drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); transform: scale(1) translateY(var(--header-offset)); }
|
|||
|
|
50% { filter: drop-shadow(0 0 20px rgba(255,255,255,0.6)) drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); transform: scale(1.05) translateY(var(--header-offset)); }
|
|||
|
|
100% { filter: drop-shadow(3px 3px 8px rgba(0,0,0,0.8)); transform: scale(1) translateY(var(--header-offset)); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#header.meter-full-pulse {
|
|||
|
|
animation: meter-full-pulse 0.6s ease-out;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### JS Changes (`js/room-code-display.js`)
|
|||
|
|
|
|||
|
|
1. **Track meter state** — new private fields: `#meterFill` (current 0–1), `#meterTarget` (target 0–1), `#meterRafId`.
|
|||
|
|
2. **`#applySettings()`** — replace `header.style.color = headerColor` with `header.style.background = linear-gradient(...)` using current `#meterFill` and the configured header color.
|
|||
|
|
3. **`update(ctx)`** — compute new target from `ctx.playerCount / ctx.maxPlayers`. If different from current target, call `#animateMeterTo(newTarget)`.
|
|||
|
|
4. **`#animateMeterTo(target)`** — `requestAnimationFrame` loop that interpolates `#meterFill` toward target over ~400ms with ease-out. Each frame calls `#applyMeterGradient()`.
|
|||
|
|
5. **`#applyMeterGradient()`** — sets `header.style.background` to `linear-gradient(to right, #fff 0%, #fff ${pct}%, ${headerColor} ${pct}%, ${headerColor} 100%)` plus re-applies `background-clip` properties.
|
|||
|
|
6. **`#triggerFullPulse()`** — adds `meter-full-pulse` class, removes after `animationend` event.
|
|||
|
|
7. **`deactivate()`** — cancels RAF, resets `#meterFill` to 0.
|
|||
|
|
|
|||
|
|
### HTML Change (`optimized-controls.html`)
|
|||
|
|
|
|||
|
|
Remove `checked` from `<input type="checkbox" id="player-list-enabled" checked>`.
|
|||
|
|
|
|||
|
|
## Data Flow
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
WebSocket event (lobby.player-joined / player-count-updated)
|
|||
|
|
→ OverlayManager updates context.playerCount
|
|||
|
|
→ OverlayManager calls RoomCodeDisplay.update(ctx)
|
|||
|
|
→ RoomCodeDisplay computes target = playerCount / maxPlayers
|
|||
|
|
→ #animateMeterTo(target) runs RAF interpolation
|
|||
|
|
→ When target reaches 1.0, #triggerFullPulse() fires
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Edge Cases
|
|||
|
|
|
|||
|
|
- `maxPlayers` is 0 or missing → fill stays at 0%.
|
|||
|
|
- `playerCount > maxPlayers` → clamp to 100%.
|
|||
|
|
- Rapid successive joins → each new target interrupts the current animation, smoothly redirecting.
|
|||
|
|
- Lobby reset (new game) → `deactivate()` resets fill to 0; next `activate()` starts fresh.
|