All files / components/screens/combat/components/indicators StaminaWarning.tsx

75% Statements 12/16
37.5% Branches 3/8
100% Functions 2/2
75% Lines 12/16

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116                                                                                        4x       60x   30x 30x 30x                         30x 30x         30x   30x                         60x 60x                                                     4x  
/**
 * StaminaWarning Component - Visual warning for low stamina
 *
 * Displays a yellow flashing indicator when stamina drops below critical threshold (20%).
 * Uses CSS animation for attention-grabbing flash effect.
 *
 * NOTE: This component is rendered OUTSIDE the Canvas as part of the HTML overlay.
 * It does NOT use Html from drei - it's a standard React component.
 *
 * @module components/combat/StaminaWarning
 * @category Combat UI
 * @korean 체력경고
 */
 
import React, { useMemo } from "react";
import { KOREAN_COLORS } from "../../../../../types/constants";
 
export interface StaminaWarningProps {
  /**
   * Current stamina amount (0-100)
   * @korean 체력량
   */
  readonly stamina: number;
 
  /**
   * Mobile responsive mode (reduced flash intensity)
   * @korean 모바일여부
   */
  readonly isMobile: boolean;
}
 
/**
 * StaminaWarning - Yellow flash warning for critical stamina depletion
 *
 * Renders a fullscreen flashing yellow border when stamina drops below 20%.
 * Only visible when stamina is below 20. If stamina is 20 or higher, the component does not render.
 * Uses CSS keyframe animation for fast attention-grabbing flash at 60fps.
 *
 * @example
 * ```tsx
 * <StaminaWarning stamina={15} isMobile={false} /> // Renders warning
 * <StaminaWarning stamina={25} isMobile={false} /> // Does not render
 * ```
 */
export const StaminaWarning: React.FC<StaminaWarningProps> = ({
  stamina,
  isMobile,
}) => {
  const warningStyle = useMemo(() => {
    // Only show when stamina is critically low
    const criticalThreshold = 20;
    Eif (stamina >= criticalThreshold) {
      return null;
    }
 
    // Clamp stamina to 0-20 range for intensity calculation
    const clampedStamina = Math.max(0, Math.min(criticalThreshold, stamina));
 
    // Calculate urgency based on how low stamina is (20-0% -> 0-1)
    const urgency = (criticalThreshold - clampedStamina) / criticalThreshold;
 
    // Mobile uses thinner border
    const borderWidth = isMobile ? "4px" : "6px";
 
    // Use KOREAN_COLORS.WARNING_YELLOW constant
    const rgb = KOREAN_COLORS.WARNING_YELLOW;
    const warningColor = `rgb(${(rgb >> 16) & 255}, ${(rgb >> 8) & 255}, ${
      rgb & 255
    })`;
 
    // Animation speed increases with urgency
    const animationDuration = Math.max(0.6, 1.2 - urgency * 0.6);
 
    return {
      position: "fixed" as const,
      inset: borderWidth,
      pointerEvents: "none" as const,
      border: `${borderWidth} solid ${warningColor}`,
      boxShadow: `0 0 20px ${warningColor}`,
      animation: `staminaFlash ${animationDuration}s ease-in-out infinite`,
      transition: "border-color 0.3s ease-out",
      zIndex: 85, // Below balance indicator but above game content
    };
  }, [stamina, isMobile]);
 
  // Don't render if stamina is not critical
  Eif (stamina >= 20 || !warningStyle) {
    return null;
  }
 
  return (
    <>
      {/* CSS keyframe animation for flashing */}
      <style>
        {`
          @keyframes staminaFlash {
            0%, 100% {
              opacity: 0.3;
            }
            50% {
              opacity: 1;
            }
          }
        `}
      </style>
      <div
        data-testid="stamina-warning"
        style={warningStyle}
        aria-hidden="true"
      />
    </>
  );
};
 
StaminaWarning.displayName = "StaminaWarning";