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 117 118                                                                                                3x       30x   30x   30x 30x 30x                         30x   30x   30x                         30x 30x                                                     3x  
/**
 * 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.
 *
 * Refactored to use useKoreanTheme for consistent styling.
 *
 * @module components/combat/StaminaWarning
 * @category Combat UI
 * @korean 체력경고
 */
 
import React, { useMemo } from "react";
import { useKoreanTheme } from "../../../../shared/base/useKoreanTheme";
import { hexToRgbaString } from "../../../../../utils/colorUtils";
 
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.
 * Uses useKoreanTheme for consistent color scheme.
 *
 * @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 theme = useKoreanTheme({ variant: "danger", size: "md", 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 theme WARNING_YELLOW constant with proper conversion
    const warningColor = hexToRgbaString(theme.colors.WARNING_YELLOW, 1);
    // 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, theme.colors.WARNING_YELLOW]);
 
  // 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";