Files
OBS-overlay/docs/plans/2026-03-20-header-meter-design.md
2026-03-20 14:56:17 -04:00

3.8 KiB
Raw Permalink Blame History

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:

#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:

@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 01), #meterTarget (target 01), #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.