blacktrigram 0.7.68 → 0.7.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (219) hide show
  1. package/lib/App2.js.map +1 -1
  2. package/lib/assets/index.css +987 -987
  3. package/lib/audio/AudioAssetLoader.js.map +1 -1
  4. package/lib/audio/AudioAssetRegistry.js +2 -2
  5. package/lib/audio/AudioAssetRegistry.js.map +1 -1
  6. package/lib/audio/AudioCache.js.map +1 -1
  7. package/lib/audio/AudioManager.js +1 -1
  8. package/lib/audio/AudioManager.js.map +1 -1
  9. package/lib/audio/AudioMonitor.js.map +1 -1
  10. package/lib/audio/AudioPool.js.map +1 -1
  11. package/lib/audio/AudioProvider.js.map +1 -1
  12. package/lib/audio/AudioUtils.js.map +1 -1
  13. package/lib/audio/BoneImpactAudioMap.js.map +1 -1
  14. package/lib/components/screens/combat/CombatScreen3D.js +3 -3
  15. package/lib/components/screens/combat/CombatScreen3D.js.map +1 -1
  16. package/lib/components/screens/combat/components/controls/CombatControlsPanel.js.map +1 -1
  17. package/lib/components/screens/combat/components/controls/PauseMenu.js.map +1 -1
  18. package/lib/components/screens/combat/components/effects/BloodDecals3D.js.map +1 -1
  19. package/lib/components/screens/combat/components/effects/BloodParticles3D.js.map +1 -1
  20. package/lib/components/screens/combat/components/effects/BloodViscosity3D.js.map +1 -1
  21. package/lib/components/screens/combat/components/effects/CombatParticleEffects3D.js.map +1 -1
  22. package/lib/components/screens/combat/components/effects/InternalDamage3D.js.map +1 -1
  23. package/lib/components/screens/combat/components/effects/ParticleAudio3D.js.map +1 -1
  24. package/lib/components/screens/combat/components/hud/FPSMonitor.js.map +1 -1
  25. package/lib/components/screens/combat/components/indicators/BalanceIndicator.js.map +1 -1
  26. package/lib/components/screens/combat/hooks/useAICombat.js.map +1 -1
  27. package/lib/components/screens/combat/hooks/useCombatActions.js.map +1 -1
  28. package/lib/components/screens/combat/hooks/useCombatAttackMovement.js.map +1 -1
  29. package/lib/components/screens/controls/ControlsScreen3D.js.map +1 -1
  30. package/lib/components/screens/controls/components/ControlCategoryTabsOverlayHtml.js.map +1 -1
  31. package/lib/components/screens/endscreen/components/MatchStatisticsDisplayOverlayHtml.js.map +1 -1
  32. package/lib/components/screens/endscreen/components/PerformanceBreakdownOverlayHtml.js.map +1 -1
  33. package/lib/components/screens/endscreen/components/VictoryAnimation3D.js.map +1 -1
  34. package/lib/components/screens/intro/IntroScreen3D.js +1 -1
  35. package/lib/components/screens/intro/IntroScreen3D.js.map +1 -1
  36. package/lib/components/screens/intro/components/AbilityListOverlayHtml.js.map +1 -1
  37. package/lib/components/screens/intro/components/ArchetypeCardGridOverlayHtml.js.map +1 -1
  38. package/lib/components/screens/intro/components/ArchetypeDisplayOverlayHtml.js.map +1 -1
  39. package/lib/components/screens/intro/components/EnhancedArchetypeDisplayOverlayHtml.js.map +1 -1
  40. package/lib/components/screens/intro/components/MenuButtonsOverlayHtml.js.map +1 -1
  41. package/lib/components/screens/intro/components/MenuSectionOverlayHtml.js.map +1 -1
  42. package/lib/components/screens/intro/components/StatBarOverlayHtml.js.map +1 -1
  43. package/lib/components/screens/philosophy/PhilosophyScreen3D.js.map +1 -1
  44. package/lib/components/screens/training/TrainingScreen3D.js +1 -1
  45. package/lib/components/screens/training/TrainingScreen3D.js.map +1 -1
  46. package/lib/components/screens/training/components/AnatomyOverlay3D.js.map +1 -1
  47. package/lib/components/screens/training/components/FootworkDrillsOverlayHtml.js.map +1 -1
  48. package/lib/components/screens/training/components/HitFeedbackEffect3D.js.map +1 -1
  49. package/lib/components/screens/training/components/TrainingButtonsOverlayHtml.js.map +1 -1
  50. package/lib/components/screens/training/components/TrainingDummy3D.js.map +1 -1
  51. package/lib/components/screens/training/components/TrainingModeSelectorOverlayHtml.js.map +1 -1
  52. package/lib/components/screens/training/components/TrainingStatsOverlayHtml.js.map +1 -1
  53. package/lib/components/screens/training/components/VitalPointMarker3D.js.map +1 -1
  54. package/lib/components/screens/training/hooks/useAttackMovement.js.map +1 -1
  55. package/lib/components/screens/training/hooks/useTrainingActions.js.map +1 -1
  56. package/lib/components/shared/mobile/ActionButtons.js.map +1 -1
  57. package/lib/components/shared/mobile/GestureRecognizerPure.js.map +1 -1
  58. package/lib/components/shared/mobile/HapticController.js.map +1 -1
  59. package/lib/components/shared/mobile/StanceWheelPure.js.map +1 -1
  60. package/lib/components/shared/mobile/VirtualDPad.js.map +1 -1
  61. package/lib/components/shared/three/anatomy/BoneAttachedMuscles.js.map +1 -1
  62. package/lib/components/shared/three/anatomy/BoneClothing.js.map +1 -1
  63. package/lib/components/shared/three/anatomy/BoneRenderer.js.map +1 -1
  64. package/lib/components/shared/three/anatomy/Face3D.js.map +1 -1
  65. package/lib/components/shared/three/effects/ActionFeedback.js.map +1 -1
  66. package/lib/components/shared/three/effects/DamageNumbers.js.map +1 -1
  67. package/lib/components/shared/three/effects/HitEffects3D.js.map +1 -1
  68. package/lib/components/shared/three/effects/StanceTransitionEffect.js.map +1 -1
  69. package/lib/components/shared/three/effects/VitalPointMarkers3D.js.map +1 -1
  70. package/lib/components/shared/three/indicators/ElementalColorSystem.js.map +1 -1
  71. package/lib/components/shared/three/indicators/GuardIndicator.js.map +1 -1
  72. package/lib/components/shared/three/scene/CombatArena3D.js.map +1 -1
  73. package/lib/components/shared/three/ui/BodyPartHealthDisplay.js.map +1 -1
  74. package/lib/components/shared/three/ui/CombatReadinessBar.js.map +1 -1
  75. package/lib/components/shared/three/ui/HealthBar.js.map +1 -1
  76. package/lib/components/shared/three/ui/PlayerHUD.js.map +1 -1
  77. package/lib/components/shared/three/ui/SpeedIndicatorHUD.js.map +1 -1
  78. package/lib/components/shared/three/ui/StaminaBar.js.map +1 -1
  79. package/lib/components/shared/three/ui/TechniqueCard.js.map +1 -1
  80. package/lib/components/shared/three/ui/VitalPointOverlayControlsHtml.js.map +1 -1
  81. package/lib/components/shared/ui/SplashScreen.js +2 -2
  82. package/lib/components/shared/ui/VitalPointOverlayControlsPure.js.map +1 -1
  83. package/lib/components/ui/combat/BalanceIndicatorOverlayHtml.js.map +1 -1
  84. package/lib/data/techniques.js.map +1 -1
  85. package/lib/hooks/useCombatTimer.js.map +1 -1
  86. package/lib/hooks/useKeyboardControls.js.map +1 -1
  87. package/lib/hooks/useSkeletalAnimation.js +2 -2
  88. package/lib/hooks/useSkeletalAnimation.js.map +1 -1
  89. package/lib/hooks/useTechniqueSelection.js.map +1 -1
  90. package/lib/hooks/useWebGLContextLossHandler.js.map +1 -1
  91. package/lib/index.js +2 -2
  92. package/lib/systems/CombatSystem.js.map +1 -1
  93. package/lib/systems/EffectCalculator.js.map +1 -1
  94. package/lib/systems/ResponsiveScaling.js.map +1 -1
  95. package/lib/systems/TrigramSystem.js.map +1 -1
  96. package/lib/systems/ai/AIPersonality.js.map +1 -1
  97. package/lib/systems/ai/ArchetypeEnforcer.js.map +1 -1
  98. package/lib/systems/ai/DecisionTree.js.map +1 -1
  99. package/lib/systems/animation/builders/KeyframeConfig.js.map +1 -1
  100. package/lib/systems/animation/builders/KeyframeInterpolation.js.map +1 -1
  101. package/lib/systems/animation/builders/MartialArtsAnimationBuilder.js.map +1 -1
  102. package/lib/systems/animation/builders/SkeletonRig.js +1 -1
  103. package/lib/systems/animation/builders/SkeletonRig.js.map +1 -1
  104. package/lib/systems/animation/catalogs/AttackAnimations.js +1 -1
  105. package/lib/systems/animation/catalogs/AttackAnimations.js.map +1 -1
  106. package/lib/systems/animation/catalogs/BasicAnimations.js +1 -1
  107. package/lib/systems/animation/catalogs/BasicAnimations.js.map +1 -1
  108. package/lib/systems/animation/catalogs/ComboAnimations.js +1 -1
  109. package/lib/systems/animation/catalogs/ComboAnimations.js.map +1 -1
  110. package/lib/systems/animation/catalogs/DarkOpsAnimations.js +1 -1
  111. package/lib/systems/animation/catalogs/DarkOpsAnimations.js.map +1 -1
  112. package/lib/systems/animation/catalogs/DefensiveAnimations.js +2 -11
  113. package/lib/systems/animation/catalogs/DefensiveAnimations.js.map +1 -1
  114. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js +1 -1
  115. package/lib/systems/animation/catalogs/ElbowKneeAnimations.js.map +1 -1
  116. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js +1 -1
  117. package/lib/systems/animation/catalogs/FootworkSkeletalAnimations.js.map +1 -1
  118. package/lib/systems/animation/catalogs/GamRedirectionAnimations.js.map +1 -1
  119. package/lib/systems/animation/catalogs/GamStanceAnimations.js.map +1 -1
  120. package/lib/systems/animation/catalogs/GamTechniqueAnimations.js.map +1 -1
  121. package/lib/systems/animation/catalogs/GanStanceAnimations.js +1 -1
  122. package/lib/systems/animation/catalogs/GanStanceAnimations.js.map +1 -1
  123. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js +1 -1
  124. package/lib/systems/animation/catalogs/GanTechniqueAnimations.js.map +1 -1
  125. package/lib/systems/animation/catalogs/GeonStanceAnimations.js +1 -1
  126. package/lib/systems/animation/catalogs/GeonStanceAnimations.js.map +1 -1
  127. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js +1 -1
  128. package/lib/systems/animation/catalogs/GonTechniqueAnimations.js.map +1 -1
  129. package/lib/systems/animation/catalogs/GrapplingAnimations.js +1 -1
  130. package/lib/systems/animation/catalogs/GrapplingAnimations.js.map +1 -1
  131. package/lib/systems/animation/catalogs/JinStanceAnimations.js +1 -1
  132. package/lib/systems/animation/catalogs/JinStanceAnimations.js.map +1 -1
  133. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js +1 -1
  134. package/lib/systems/animation/catalogs/JinTechniqueAnimations.js.map +1 -1
  135. package/lib/systems/animation/catalogs/KickAnimations.js +1 -1
  136. package/lib/systems/animation/catalogs/KickAnimations.js.map +1 -1
  137. package/lib/systems/animation/catalogs/LiStanceAnimations.js +1 -1
  138. package/lib/systems/animation/catalogs/LiStanceAnimations.js.map +1 -1
  139. package/lib/systems/animation/catalogs/LiTechniqueAnimations.js.map +1 -1
  140. package/lib/systems/animation/catalogs/MovementAnimations.js +1 -1
  141. package/lib/systems/animation/catalogs/MovementAnimations.js.map +1 -1
  142. package/lib/systems/animation/catalogs/PunchAnimations.js +1 -1
  143. package/lib/systems/animation/catalogs/PunchAnimations.js.map +1 -1
  144. package/lib/systems/animation/catalogs/SonStanceAnimations.js +1 -1
  145. package/lib/systems/animation/catalogs/SonStanceAnimations.js.map +1 -1
  146. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js +1 -1
  147. package/lib/systems/animation/catalogs/SonTechniqueAnimations.js.map +1 -1
  148. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js +1 -1
  149. package/lib/systems/animation/catalogs/SpecializedPunchAnimations.js.map +1 -1
  150. package/lib/systems/animation/catalogs/StanceAnimations.js +1 -1
  151. package/lib/systems/animation/catalogs/StanceAnimations.js.map +1 -1
  152. package/lib/systems/animation/catalogs/StanceAttackAnimations.js +215 -272
  153. package/lib/systems/animation/catalogs/StanceAttackAnimations.js.map +1 -1
  154. package/lib/systems/animation/catalogs/StanceIdleAnimations.js +2 -11
  155. package/lib/systems/animation/catalogs/StanceIdleAnimations.js.map +1 -1
  156. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js +2 -2
  157. package/lib/systems/animation/catalogs/StanceLocomotionAnimations.js.map +1 -1
  158. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js +1 -1
  159. package/lib/systems/animation/catalogs/StepSkeletalAnimations.js.map +1 -1
  160. package/lib/systems/animation/catalogs/TaeJointLockAnimations.js.map +1 -1
  161. package/lib/systems/animation/catalogs/TaeStanceAnimations.js +1 -1
  162. package/lib/systems/animation/catalogs/TaeStanceAnimations.js.map +1 -1
  163. package/lib/systems/animation/core/AnimationOptimizations.js +1 -1
  164. package/lib/systems/animation/core/AnimationOptimizations.js.map +1 -1
  165. package/lib/systems/animation/core/AnimationPriority.js.map +1 -1
  166. package/lib/systems/animation/core/AnimationRegistry.js +11 -209
  167. package/lib/systems/animation/core/AnimationRegistry.js.map +1 -1
  168. package/lib/systems/animation/core/AnimationStateMachine.js +2 -2
  169. package/lib/systems/animation/core/AnimationStateMachine.js.map +1 -1
  170. package/lib/systems/animation/core/TechniqueAnimationMapper.js +1 -1
  171. package/lib/systems/animation/core/TechniqueAnimationMapper.js.map +1 -1
  172. package/lib/systems/animation/core/TechniqueAnimationMapping.js +1 -1
  173. package/lib/systems/animation/core/TechniqueAnimationMapping.js.map +1 -1
  174. package/lib/systems/animation/systems/MuscleActivation.js.map +1 -1
  175. package/lib/systems/bodypart/BodyPartDamageIntegration.js.map +1 -1
  176. package/lib/systems/bodypart/BodyPartHealthSystem.js.map +1 -1
  177. package/lib/systems/bodypart/BodyPartPositionMapping.js.map +1 -1
  178. package/lib/systems/bodypart/InjuryTracker.js.map +1 -1
  179. package/lib/systems/breathing/BreathingDisruptionSystem.js.map +1 -1
  180. package/lib/systems/combat/BalanceSystem.js.map +1 -1
  181. package/lib/systems/combat/BreakingStatusEffects.js.map +1 -1
  182. package/lib/systems/combat/CombatStateSystem.js.map +1 -1
  183. package/lib/systems/combat/GrappleSystem.js.map +1 -1
  184. package/lib/systems/combat/painConsciousnessUtils.js.map +1 -1
  185. package/lib/systems/combat/typeGuards.js.map +1 -1
  186. package/lib/systems/index.js +2 -2
  187. package/lib/systems/movement/integration.js.map +1 -1
  188. package/lib/systems/physics/AttackMovementPhysics.js.map +1 -1
  189. package/lib/systems/physics/CollisionDetection.js +1 -1
  190. package/lib/systems/physics/CollisionDetection.js.map +1 -1
  191. package/lib/systems/physics/KnockbackPhysics.js.map +1 -1
  192. package/lib/systems/physics/MovementPhysics.js.map +1 -1
  193. package/lib/systems/physics/PhysicalReachCalculator.js.map +1 -1
  194. package/lib/systems/physics/SpeedModifierSystem.js.map +1 -1
  195. package/lib/systems/trigram/KoreanTechniques.js.map +1 -1
  196. package/lib/systems/trigram/StanceManager.js.map +1 -1
  197. package/lib/systems/trigram/TransitionCalculator.js.map +1 -1
  198. package/lib/systems/trigram/TrigramCalculator.js.map +1 -1
  199. package/lib/systems/trigram/techniques/TechniqueConfig.js.map +1 -1
  200. package/lib/systems/trigram/techniques/index.js.map +1 -1
  201. package/lib/systems/trigram/types.js.map +1 -1
  202. package/lib/systems/vitalpoint/DamageCalculator.js.map +1 -1
  203. package/lib/systems/vitalpoint/KoreanAnatomy.js.map +1 -1
  204. package/lib/systems/vitalpoint/KoreanVitalPoints.js.map +1 -1
  205. package/lib/systems/vitalpoint/MeridianVitalPointMapping.js.map +1 -1
  206. package/lib/types/constants/designSystem.js.map +1 -1
  207. package/lib/utils/colorHelpers.js.map +1 -1
  208. package/lib/utils/colorUtils.js.map +1 -1
  209. package/lib/utils/controlMapping.js.map +1 -1
  210. package/lib/utils/deviceDetection.js.map +1 -1
  211. package/lib/utils/effectUtils.js.map +1 -1
  212. package/lib/utils/fabricTextures.js.map +1 -1
  213. package/lib/utils/htmlOverlayHelpers.js.map +1 -1
  214. package/lib/utils/performance/PerformanceMonitor.js.map +1 -1
  215. package/lib/utils/performance/PerformanceOverlay3D.js.map +1 -1
  216. package/lib/utils/safeAreaUtils.js.map +1 -1
  217. package/lib/utils/stanceHelpers.js.map +1 -1
  218. package/lib/utils/visualEffects.js.map +1 -1
  219. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"VitalPointOverlayControlsHtml.js","names":[],"sources":["../../../../../src/components/shared/three/ui/VitalPointOverlayControlsHtml.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsHtml - UI controls for vital point visualization\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/three/ui/VitalPointOverlayControlsHtml\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsHtml component\n */\nexport interface VitalPointOverlayControlsProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the Html overlay container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n\n/**\n * Convert numeric color to CSS hex string\n */\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsHtml Component\n * Provides comprehensive UI for vital point visualization control\n */\nexport const VitalPointOverlayControlsHtml: React.FC<\n VitalPointOverlayControlsProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile]\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\")\n );\n } else if (regionFilter === \"legs\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\")\n );\n } else {\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query)\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange]\n );\n\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <Html fullscreen style={{ pointerEvents: \"none\" }}>\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40, inset 0 0 20px ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: 200,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )} 0%, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_BLUE\n )} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}44`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n </Html>\n );\n};\n\nexport default VitalPointOverlayControlsHtml;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,IAAM,oBAAoB,aAAyC;CACjE,QAAQ,UAAR;EACE,KAAK,mBAAmB,QACtB,OAAO,cAAc,cAAc,YAAY;EACjD,KAAK,mBAAmB,UACtB,OAAO,cAAc,cAAc,UAAU;EAC/C,KAAK,mBAAmB,OACtB,OAAO,cAAc,cAAc,WAAW;EAChD,KAAK,mBAAmB,UACtB,OAAO,cAAc,cAAc,oBAAoB;EACzD,KAAK,mBAAmB,OACtB,OAAO,cAAc,cAAc,cAAc;EACnD,SACE,OAAO,cAAc,cAAc,SAAS;CAChD;AACF;;;;;AAMA,IAAa,iCAER,EACH,SACA,iBACA,iBACA,yBACA,cACA,sBACA,aAAa,qBACb,qBACA,YACA,oBACA,UACA,kBACA,OACA,eACA,WAAW,OACX,qBACI;CACJ,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAE9C,MAAM,CAAC,qBAAqB,0BAA0B,SAAS,EAAE;CACjE,MAAM,cAAc,uBAAuB;CAC3C,MAAM,iBAAiB,uBAAuB;CAE9C,MAAM,QAAQ,cAAc,oBAAoB,GAAG,CAAC,CAAC;CAErD,MAAM,kBAKF,eACK;EACL,KAAK,WAAW,UAAU;EAC1B,MAAM,WAAW,SAAS;CAC5B,IACA,CAAC,QAAQ,CACX;CAEA,MAAM,gBAAgB,kBAAkB;CAExC,MAAM,gBAAgB,cAAc;EAClC,IAAI,SAAS,CAAC,GAAG,mBAAmB;EAEpC,IAAI,gBAAgB,SAAS,GAC3B,SAAS,OAAO,QAAQ,OAAO,gBAAgB,SAAS,GAAG,QAAQ,CAAC;EAGtE,IAAI,iBAAiB,OACnB,IAAI,iBAAiB,QACnB,SAAS,OAAO,QACb,OACC,GAAG,GAAG,WAAW,WAAW,KAAK,GAAG,GAAG,WAAW,YAAY,CAClE;OACK,IAAI,iBAAiB,QAC1B,SAAS,OAAO,QACb,OACC,GAAG,GAAG,WAAW,WAAW,KAAK,GAAG,GAAG,WAAW,YAAY,CAClE;OACK;GACL,MAAM,SAAS,GAAG,aAAa;GAC/B,SAAS,OAAO,QAAQ,OAAO,GAAG,GAAG,WAAW,MAAM,CAAC;EACzD;EAGF,IAAI,aAAa;GACf,MAAM,QAAQ,YAAY,YAAY;GACtC,SAAS,OAAO,QACb,OACC,GAAG,MAAM,OAAO,YAAY,EAAE,SAAS,KAAK,KAC5C,GAAG,MAAM,QAAQ,YAAY,EAAE,SAAS,KAAK,KAC7C,GAAG,MAAM,UAAU,YAAY,EAAE,SAAS,KAAK,KAC/C,GAAG,GAAG,YAAY,EAAE,SAAS,KAAK,CACtC;EACF;EAEA,OAAO,OAAO;CAChB,GAAG;EAAC;EAAiB;EAAc;CAAW,CAAC;CAE/C,MAAM,uBAAuB,aAC1B,aAAiC;EAIhC,wBAHmB,gBAAgB,SAAS,QAAQ,IAChD,gBAAgB,QAAQ,MAAM,MAAM,QAAQ,IAC5C,CAAC,GAAG,iBAAiB,QAAQ,CACC;CACpC,GACA,CAAC,iBAAiB,uBAAuB,CAC3C;CAEA,MAAM,kBAAwC;EAC5C,mBAAmB;EACnB,mBAAmB;EACnB,mBAAmB;EACnB,mBAAmB;EACnB,mBAAmB;CACrB;CAEA,MAAM,gBAIA;EACJ;GAAE,OAAO;GAAO,OAAO;GAAe,QAAQ;EAAK;EACnD;GAAE,OAAO;GAAQ,OAAO;GAAQ,QAAQ;EAAK;EAC7C;GAAE,OAAO;GAAS,OAAO;GAAS,QAAQ;EAAK;EAC/C;GAAE,OAAO;GAAQ,OAAO;GAAQ,QAAQ;EAAI;EAC5C;GAAE,OAAO;GAAQ,OAAO;GAAQ,QAAQ;EAAK;CAC/C;CAEA,MAAM,aAAa,WAAW,MAAM;CACpC,MAAM,eAAe,WAAW,KAAK;CACrC,MAAM,WAAW,WAAW,KAAK;CACjC,MAAM,gBAAgB,WAAW,IAAI;CAErC,OACE,oBAAC,MAAD;EAAM,YAAA;EAAW,OAAO,EAAE,eAAe,OAAO;YAC9C,qBAAC,OAAD;GACE,OAAO;IACL,UAAU;IACV,KAAK,cAAc;IACnB,MAAM,cAAc;IACpB,OAAO,cAAc;IACrB,QAAQ,cAAc;IACtB,OAAO;IACP,YAAY,GAAG,cAAc,cAAc,kBAAkB,EAAE;IAC/D,QAAQ,aAAa,cAAc,cAAc,YAAY;IAC7D,cAAc;IACd,SAAS,WAAW,SAAS;IAC7B,YAAY,YAAY;IACxB,OAAO,cAAc,cAAc,YAAY;IAC/C,WAAW,YAAY,cACrB,cAAc,YAChB,EAAE,qBAAqB,cACrB,cAAc,kBAChB,EAAE;IACF,YAAY;IACZ,eAAe;IACf,QAAQ;GACV;GACA,eAAY;aAvBd;IA0BE,qBAAC,OAAD;KACE,OAAO;MACL,SAAS;MACT,gBAAgB;MAChB,YAAY;MACZ,cAAc;MACd,eAAe;MACf,cAAc,aAAa,cACzB,cAAc,YAChB,EAAE;MACF,YAAY,0BAA0B,cACpC,cAAc,kBAChB,EAAE,SAAS,cACT,cAAc,YAChB,EAAE,UAAU,cAAc,cAAc,kBAAkB,EAAE;KAC9D;eAfF,CAiBE,qBAAC,OAAD,EAAA,UAAA,CACE,oBAAC,OAAD;MAAK,OAAO;OAAE,UAAU,WAAW,KAAK;OAAI,YAAY;MAAO;gBAAG;KAE7D,CAAA,GACL,qBAAC,OAAD;MACE,OAAO;OACL,UAAU;OACV,OAAO,cAAc,cAAc,cAAc;OACjD,WAAW;MACb;gBALF;OAOG;OAAc;OAAI,MAAM;OAAM;MAC5B;OACF,EAAA,CAAA,GACL,oBAAC,UAAD;MACE,eAAe,YAAY,CAAC,QAAQ;MACpC,OAAO;OACL,YAAY,2BAA2B,cACrC,cAAc,oBAChB,EAAE,OAAO,cAAc,cAAc,kBAAkB,EAAE;OACzD,QAAQ,aAAa,cAAc,cAAc,YAAY;OAC7D,cAAc;OACd,SAAS;OACT,OAAO,cAAc,cAAc,YAAY;OAC/C;OACA,QAAQ;OACR,YAAY;OACZ,WAAW,aAAa,cACtB,cAAc,YAChB,EAAE;MACJ;MACA,eAAe,MAAM;OACnB,EAAE,cAAc,MAAM,YAAY;OAClC,EAAE,cAAc,MAAM,YAAY,cAAc,cAC9C,cAAc,YAChB,EAAE;MACJ;MACA,eAAe,MAAM;OACnB,EAAE,cAAc,MAAM,YAAY;OAClC,EAAE,cAAc,MAAM,YAAY,aAAa,cAC7C,cAAc,YAChB,EAAE;MACJ;MACA,eAAY;gBAEX,WAAW,MAAM;KACZ,CAAA,CACL;;IAGL,oBAAC,OAAD;KAAK,OAAO,EAAE,cAAc,OAAO;eACjC,oBAAC,UAAD;MACE,eAAe,gBAAgB,CAAC,OAAO;MACvC,OAAO;OACL,OAAO;OACP,QAAQ;OACR,YAAY,UACR,2BAA2B,cACzB,cAAc,WAChB,EAAE,OAAO,cAAc,cAAc,gBAAgB,EAAE,UACvD,2BAA2B,cACzB,cAAc,oBAChB,EAAE,OAAO,cAAc,cAAc,kBAAkB,EAAE;OAC7D,QAAQ,aACN,UACI,cAAc,cAAc,WAAW,IACvC,cAAc,cAAc,YAAY;OAE9C,cAAc;OACd,OAAO,UAAU,cAAc,cAAc,YAAY,IAAI,cAAc,cAAc,YAAY;OACrG,UAAU,WAAW,KAAK;OAC1B,YAAY;OACZ,QAAQ;OACR,YAAY;OACZ,WAAW,UACP,cAAc,cACZ,cAAc,WAChB,EAAE,sBAAsB,gBAAgB,cAAc,cAAc,EAAG,MACvE,aAAa,cAAc,cAAc,YAAY,EAAE;OAC3D,eAAe;OACf,eAAe;MACjB;MACA,eAAe,MAAM;OACnB,EAAE,cAAc,MAAM,YAAY;OAClC,EAAE,cAAc,MAAM,YAAY,UAC9B,cAAc,cAAc,cAAc,WAAW,EAAE,MACvD,cAAc,cAAc,cAAc,YAAY,EAAE;MAC9D;MACA,eAAe,MAAM;OACnB,EAAE,cAAc,MAAM,YAAY;OAClC,EAAE,cAAc,MAAM,YAAY,UAC9B,cAAc,cAAc,cAAc,WAAW,EAAE,MACvD,aAAa,cAAc,cAAc,YAAY,EAAE;MAC7D;MACA,eAAY;gBAEX,UAAU,oBAAoB;KACzB,CAAA;IACL,CAAA;IAGJ,YAAY,WACX,qBAAA,UAAA,EAAA,UAAA;KAEE,qBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,OAAO;gBAAnC,CACE,oBAAC,OAAD;OACE,OAAO;QACL;QACA,cAAc;QACd,OAAO,cAAc,cAAc,WAAW;QAC9C,YAAY;QACZ,eAAe;QACf,eAAe;OACjB;iBACD;MAEI,CAAA,GACL,oBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,UAAU;QACV,KAAK;OACP;iBAEC,gBAAgB,KAAK,aAAa;QACjC,MAAM,WAAW,gBAAgB,SAAS,QAAQ;QAClD,MAAM,gBAAgB,iBAAiB,QAAQ;QAC/C,OACE,oBAAC,UAAD;SAEE,eAAe,qBAAqB,QAAQ;SAC5C,OAAO;UACL,YAAY,WACR,2BAA2B,cAAc,OAAO,cAAc,YAC9D,GAAG,cAAc,cAAc,oBAAoB;UACvD,QAAQ,aAAa;UACrB,cAAc;UACd,SAAS;UACT,OAAO,cAAc,cAAc,YAAY;UAC/C,UAAU;UACV,QAAQ;UACR,SAAS,WAAW,IAAI;UACxB,YAAY;UACZ,YAAY,WAAW,SAAS;UAChC,WAAW,WACP,aAAa,cAAc,MAC3B;SACN;SACA,eAAe,MAAM;UACnB,EAAE,cAAc,MAAM,UAAU;UAChC,EAAE,cAAc,MAAM,YAAY;SACpC;SACA,eAAe,MAAM;UACnB,EAAE,cAAc,MAAM,UAAU,WAAW,MAAM;UACjD,EAAE,cAAc,MAAM,YAAY;SACpC;SACA,eAAa,mBAAmB;mBAE/B;QACK,GA9BD,QA8BC;OAEZ,CAAC;MACE,CAAA,CACF;;KAGL,qBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,OAAO;gBAAnC,CACE,oBAAC,OAAD;OACE,OAAO;QACL;QACA,cAAc;QACd,OAAO,cAAc,cAAc,WAAW;QAC9C,YAAY;QACZ,eAAe;QACf,eAAe;OACjB;iBACD;MAEI,CAAA,GACL,oBAAC,OAAD;OACE,OAAO;QACL,SAAS;QACT,UAAU;QACV,KAAK;OACP;iBAEC,cAAc,KAAK,WAAW;QAC7B,MAAM,WAAW,iBAAiB,OAAO;QACzC,OACE,qBAAC,UAAD;SAEE,eAAe,qBAAqB,OAAO,KAAK;SAChD,OAAO;UACL,YAAY,WACR,2BAA2B,cACzB,cAAc,YAChB,EAAE,OAAO,cACP,cAAc,WAChB,EAAE,UACF,GAAG,cAAc,cAAc,oBAAoB;UACvD,QAAQ,aAAa,cACnB,cAAc,YAChB;UACA,cAAc;UACd,SAAS;UACT,OAAO,cAAc,cAAc,YAAY;UAC/C,UAAU;UACV,QAAQ;UACR,SAAS,WAAW,IAAI;UACxB,YAAY;UACZ,YAAY,WAAW,SAAS;UAChC,WAAW,WACP,aAAa,cACX,cAAc,YAChB,EAAE,MACF;SACN;SACA,eAAe,MAAM;UACnB,EAAE,cAAc,MAAM,UAAU;UAChC,EAAE,cAAc,MAAM,YAAY;SACpC;SACA,eAAe,MAAM;UACnB,EAAE,cAAc,MAAM,UAAU,WAAW,MAAM;UACjD,EAAE,cAAc,MAAM,YAAY;SACpC;SACA,eAAa,iBAAiB,OAAO;mBApCvC;UAsCG,OAAO;UAAO;UAAI,OAAO;SACpB;WAtCD,OAAO,KAsCN;OAEZ,CAAC;MACE,CAAA,CACF;;KAGL,qBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,OAAO;gBAAnC,CACE,oBAAC,OAAD;OACE,OAAO;QACL;QACA,cAAc;QACd,OAAO,cAAc,cAAc,WAAW;QAC9C,YAAY;QACZ,eAAe;QACf,eAAe;OACjB;iBACD;MAEI,CAAA,GACL,qBAAC,OAAD;OAAK,OAAO,EAAE,UAAU,WAAW;iBAAnC,CACE,oBAAC,SAAD;QACE,MAAK;QACL,OAAO;QACP,WAAW,MAAM,eAAe,EAAE,OAAO,KAAK;QAC9C,aAAY;QACZ,OAAO;SACL,OAAO;SACP,QAAQ;SACR,YAAY,GAAG,cACb,cAAc,oBAChB;SACA,QAAQ,aAAa,cACnB,cAAc,YAChB,EAAE;SACF,cAAc;SACd,SAAS;SACT,OAAO,cAAc,cAAc,YAAY;SAC/C;SACA,YAAY,YAAY;SACxB,YAAY;SACZ,SAAS;QACX;QACA,UAAU,MAAM;SACd,EAAE,cAAc,MAAM,cAAc,cAClC,cAAc,YAChB;SACA,EAAE,cAAc,MAAM,YAAY,YAAY,cAC5C,cAAc,YAChB,EAAE;QACJ;QACA,SAAS,MAAM;SACb,EAAE,cAAc,MAAM,cAAc,GAAG,cACrC,cAAc,YAChB,EAAE;SACF,EAAE,cAAc,MAAM,YAAY;QACpC;QACA,eAAY;OACb,CAAA,GAEA,eACC,oBAAC,UAAD;QACE,eAAe,eAAe,EAAE;QAChC,OAAO;SACL,UAAU;SACV,OAAO;SACP,KAAK;SACL,WAAW;SACX,YAAY;SACZ,QAAQ;SACR,OAAO,cAAc,cAAc,cAAc;SACjD,QAAQ;SACR,UAAU;SACV,SAAS;SACT,SAAS;SACT,YAAY;SACZ,gBAAgB;SAChB,cAAc;SACd,YAAY;QACd;QACA,eAAe,MAAM;SACnB,EAAE,cAAc,MAAM,QAAQ,cAC5B,cAAc,UAChB;SACA,EAAE,cAAc,MAAM,aAAa,GAAG,cACpC,cAAc,oBAChB;QACF;QACA,eAAe,MAAM;SACnB,EAAE,cAAc,MAAM,QAAQ,cAC5B,cAAc,cAChB;SACA,EAAE,cAAc,MAAM,aAAa;QACrC;QACA,eAAY;QACZ,OAAM;kBACP;OAEO,CAAA,CAEP;QACF;;KAGL,qBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,OAAO;gBAAnC,CACE,oBAAC,OAAD;OACE,OAAO;QACL;QACA,cAAc;QACd,OAAO,cAAc,cAAc,WAAW;QAC9C,YAAY;QACZ,eAAe;QACf,eAAe;OACjB;iBACD;MAEI,CAAA,GACL,qBAAC,OAAD;OACE,OAAO;QAAE,SAAS;QAAQ,eAAe;QAAU,KAAK;OAAM;iBADhE,CAGE,qBAAC,SAAD;QACE,OAAO;SACL,SAAS;SACT,YAAY;SACZ,KAAK;SACL,QAAQ;QACV;kBANF,CAQE,oBAAC,SAAD;SACE,MAAK;SACL,SAAS;SACT,WAAW,MAAM,mBAAmB,EAAE,OAAO,OAAO;SACpD,OAAO;UACL,OAAO;UACP,QAAQ;UACR,aAAa,cAAc,cAAc,YAAY;SACvD;SACA,eAAY;QACb,CAAA,GACD,oBAAC,QAAD;SAAM,OAAO,EAAE,SAAS;mBAAG;QAAyB,CAAA,CAC/C;WACP,qBAAC,SAAD;QACE,OAAO;SACL,SAAS;SACT,YAAY;SACZ,KAAK;SACL,QAAQ;QACV;kBANF,CAQE,oBAAC,SAAD;SACE,MAAK;SACL,SAAS;SACT,WAAW,MAAM,iBAAiB,EAAE,OAAO,OAAO;SAClD,OAAO;UACL,OAAO;UACP,QAAQ;UACR,aAAa,cAAc,cAAc,YAAY;SACvD;SACA,eAAY;QACb,CAAA,GACD,oBAAC,QAAD;SAAM,OAAO,EAAE,SAAS;mBAAG;QAAwB,CAAA,CAC9C;SACJ;QACF;;MAGH,gBAAgB,SAAS,KACzB,iBAAiB,SACjB,gBAAgB,OAChB,oBAAC,OAAD;MAAK,OAAO,EAAE,cAAc,OAAO;gBACjC,oBAAC,UAAD;OACE,eAAe;QACb,wBAAwB,CAAC,CAAC;QAC1B,qBAAqB,KAAK;QAC1B,eAAe,EAAE;OACnB;OACA,OAAO;QACL,OAAO;QACP,QAAQ,eAAe;QACvB,YAAY,GAAG,cACb,cAAc,oBAChB;QACA,QAAQ,aAAa,cACnB,cAAc,aAChB;QACA,cAAc;QACd,OAAO,cAAc,cAAc,aAAa;QAChD,UAAU;QACV,QAAQ;QACR,YAAY;QACZ,YAAY;OACd;OACA,eAAe,MAAM;QACnB,EAAE,cAAc,MAAM,aAAa,GAAG,cACpC,cAAc,aAChB,EAAE;QACF,EAAE,cAAc,MAAM,YAAY;OACpC;OACA,eAAe,MAAM;QACnB,EAAE,cAAc,MAAM,aAAa,GAAG,cACpC,cAAc,oBAChB;QACA,EAAE,cAAc,MAAM,YAAY;OACpC;OACA,eAAY;iBACb;MAEO,CAAA;KACL,CAAA;KAIP,qBAAC,OAAD,EAAA,UAAA,CACE,qBAAC,OAAD;MACE,OAAO;OACL;OACA,cAAc;OACd,OAAO,cAAc,cAAc,WAAW;MAChD;gBALF;OAMC;OACc,MAAM,QAAQ,CAAC;OAAE;MAC3B;SACL,oBAAC,SAAD;MACE,MAAK;MACL,KAAI;MACJ,KAAI;MACJ,MAAK;MACL,OAAO;MACP,WAAW,MAAM,cAAc,WAAW,EAAE,OAAO,KAAK,CAAC;MACzD,OAAO;OACL,OAAO;OACP,aAAa,cAAc,cAAc,YAAY;MACvD;MACA,eAAY;KACb,CAAA,CACE,EAAA,CAAA;KAGL,qBAAC,OAAD;MACE,OAAO;OACL,WAAW;OACX,YAAY;OACZ,WAAW,aAAa,cACtB,cAAc,YAChB,EAAE;OACF,UAAU;OACV,OAAO,cAAc,cAAc,cAAc;MACnD;gBATF,CAWE,qBAAC,OAAD,EAAA,UAAA;OAAK;OACE,MAAM,SAAS;OAAK;OAAQ,MAAM,SAAS;MAC7C,EAAA,CAAA,GACL,qBAAC,OAAD,EAAA,UAAA;OAAK;OACC,MAAM,SAAS;OAAK;OAAQ,MAAM,SAAS;MAC5C,EAAA,CAAA,CACF;;IACL,EAAA,CAAA;GAED;;CACD,CAAA;AAEV"}
1
+ {"version":3,"file":"VitalPointOverlayControlsHtml.js","names":[],"sources":["../../../../../src/components/shared/three/ui/VitalPointOverlayControlsHtml.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsHtml - UI controls for vital point visualization\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/three/ui/VitalPointOverlayControlsHtml\n */\n\nimport { Html } from \"@react-three/drei\";\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../../types/common\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"../../../../types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"../../../../utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsHtml component\n */\nexport interface VitalPointOverlayControlsProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the Html overlay container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n\n/**\n * Convert numeric color to CSS hex string\n */\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsHtml Component\n * Provides comprehensive UI for vital point visualization control\n */\nexport const VitalPointOverlayControlsHtml: React.FC<\n VitalPointOverlayControlsProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile]\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\")\n );\n } else if (regionFilter === \"legs\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\")\n );\n } else {\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query)\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange]\n );\n\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <Html fullscreen style={{ pointerEvents: \"none\" }}>\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40, inset 0 0 20px ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: 200,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )} 0%, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_BLUE\n )} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN\n )}44`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n </Html>\n );\n};\n\nexport default VitalPointOverlayControlsHtml;\n"],"mappings":"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"}
@@ -183,7 +183,7 @@ var SplashScreen = ({ onStart, width, height }) => {
183
183
  }),
184
184
  /* @__PURE__ */ jsxs("div", {
185
185
  role: "contentinfo",
186
- "aria-label": `Application version 0.7.68`,
186
+ "aria-label": `Application version 0.7.69`,
187
187
  style: {
188
188
  position: "absolute",
189
189
  bottom: "20px",
@@ -192,7 +192,7 @@ var SplashScreen = ({ onStart, width, height }) => {
192
192
  fontSize: "10px",
193
193
  zIndex: 1
194
194
  },
195
- children: ["v", "0.7.68"]
195
+ children: ["v", "0.7.69"]
196
196
  })
197
197
  ]
198
198
  });
@@ -1 +1 @@
1
- {"version":3,"file":"VitalPointOverlayControlsPure.js","names":[],"sources":["../../../../src/components/shared/ui/VitalPointOverlayControlsPure.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsPure - Pure DOM controls for vital point visualization\n *\n * Pure DOM version (no Three.js Html wrapper) for use in HUD overlays.\n * Identical functionality to VitalPointOverlayControlsHtml but renders as pure React DOM.\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/ui/VitalPointOverlayControlsPure\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../types/common\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"@/utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsPure component\n */\nexport interface VitalPointOverlayControlsPureProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsPure Component\n * Pure DOM version - renders directly without Three.js Html wrapper\n */\nexport const VitalPointOverlayControlsPure: React.FC<\n VitalPointOverlayControlsPureProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile],\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\"),\n );\n } else if (regionFilter === \"legs\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\"),\n );\n } else {\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query),\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange],\n );\n\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40, inset 0 0 20px ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: Z_INDEX.MODAL,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.ACCENT_BLUE)} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED,\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY,\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default VitalPointOverlayControlsPure;\n"],"mappings":"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"}
1
+ {"version":3,"file":"VitalPointOverlayControlsPure.js","names":[],"sources":["../../../../src/components/shared/ui/VitalPointOverlayControlsPure.tsx"],"sourcesContent":["/**\n * VitalPointOverlayControlsPure - Pure DOM controls for vital point visualization\n *\n * Pure DOM version (no Three.js Html wrapper) for use in HUD overlays.\n * Identical functionality to VitalPointOverlayControlsHtml but renders as pure React DOM.\n *\n * Provides comprehensive controls for the 70-point vital point overlay system:\n * - Toggle overlay visibility\n * - Filter by severity level\n * - Filter by body region\n * - Search vital points\n * - Adjust marker scale\n * - Toggle labels\n * - Toggle animations\n *\n * @module components/shared/ui/VitalPointOverlayControlsPure\n */\n\nimport React, { useCallback, useMemo, useState } from \"react\";\nimport {\n KOREAN_VITAL_POINTS,\n getVitalPointsStats,\n} from \"../../../systems/vitalpoint/KoreanVitalPoints\";\nimport { VitalPointSeverity } from \"../../../types/common\";\nimport { Z_INDEX } from \"../../../types/LayoutTypes\";\nimport { FONT_FAMILY, KOREAN_COLORS } from \"@/types/constants\";\nimport { hexColorToCSS, hexToRgbaString } from \"@/utils/colorUtils\";\nimport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\nexport type { BodyRegionFilter } from \"../three/effects/VitalPointMarkers3D\";\n\n/**\n * Props for VitalPointOverlayControlsPure component\n */\nexport interface VitalPointOverlayControlsPureProps {\n /** Whether overlay is currently visible */\n readonly visible: boolean;\n /** Callback when visibility changes */\n readonly onVisibleChange: (visible: boolean) => void;\n /** Current severity filters */\n readonly severityFilters: VitalPointSeverity[];\n /** Callback when severity filters change */\n readonly onSeverityFiltersChange: (filters: VitalPointSeverity[]) => void;\n /** Current region filter */\n readonly regionFilter: BodyRegionFilter;\n /** Callback when region filter changes */\n readonly onRegionFilterChange: (filter: BodyRegionFilter) => void;\n /** Current search query */\n readonly searchQuery?: string;\n /** Callback when search query changes */\n readonly onSearchQueryChange?: (query: string) => void;\n /** Whether labels are shown */\n readonly showLabels: boolean;\n /** Callback when label visibility changes */\n readonly onShowLabelsChange: (show: boolean) => void;\n /** Whether animations are enabled */\n readonly animated: boolean;\n /** Callback when animation state changes */\n readonly onAnimatedChange: (animated: boolean) => void;\n /** Marker scale multiplier */\n readonly scale: number;\n /** Callback when scale changes */\n readonly onScaleChange: (scale: number) => void;\n /** Whether on mobile device */\n readonly isMobile?: boolean;\n /**\n * Screen position for the control panel.\n *\n * All values must be valid CSS position values, such as `\"20px\"`, `\"10%\"`, `\"1rem\"`, or `\"auto\"`.\n * These are applied directly to the `style` of the container.\n */\n readonly screenPosition?: {\n /** CSS `top` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n top?: string;\n /** CSS `left` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n left?: string;\n /** CSS `right` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n right?: string;\n /** CSS `bottom` position (e.g., \"20px\", \"10%\", \"1rem\", \"auto\") */\n bottom?: string;\n };\n}\n/**\n * Get color for severity level\n */\nconst getSeverityColor = (severity: VitalPointSeverity): string => {\n switch (severity) {\n case VitalPointSeverity.LETHAL:\n return hexColorToCSS(KOREAN_COLORS.NEGATIVE_RED);\n case VitalPointSeverity.CRITICAL:\n return hexColorToCSS(KOREAN_COLORS.HEALTH_LOW);\n case VitalPointSeverity.MAJOR:\n return hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD);\n case VitalPointSeverity.MODERATE:\n return hexColorToCSS(KOREAN_COLORS.TRIGRAM_GEON_PRIMARY);\n case VitalPointSeverity.MINOR:\n return hexColorToCSS(KOREAN_COLORS.POSITIVE_GREEN);\n default:\n return hexColorToCSS(KOREAN_COLORS.NEON_CYAN);\n }\n};\n\n/**\n * VitalPointOverlayControlsPure Component\n * Pure DOM version - renders directly without Three.js Html wrapper\n */\nexport const VitalPointOverlayControlsPure: React.FC<\n VitalPointOverlayControlsPureProps\n> = ({\n visible,\n onVisibleChange,\n severityFilters,\n onSeverityFiltersChange,\n regionFilter,\n onRegionFilterChange,\n searchQuery: externalSearchQuery,\n onSearchQueryChange,\n showLabels,\n onShowLabelsChange,\n animated,\n onAnimatedChange,\n scale,\n onScaleChange,\n isMobile = false,\n screenPosition,\n}) => {\n const [expanded, setExpanded] = useState(false);\n\n const [internalSearchQuery, setInternalSearchQuery] = useState(\"\");\n const searchQuery = externalSearchQuery ?? internalSearchQuery;\n const setSearchQuery = onSearchQueryChange ?? setInternalSearchQuery;\n\n const stats = useMemo(() => getVitalPointsStats(), []);\n\n const defaultPosition: {\n top?: string;\n left?: string;\n right?: string;\n bottom?: string;\n } = useMemo(\n () => ({\n top: isMobile ? \"180px\" : \"220px\",\n left: isMobile ? \"10px\" : \"20px\",\n }),\n [isMobile],\n );\n\n const finalPosition = screenPosition ?? defaultPosition;\n\n const filteredCount = useMemo(() => {\n let points = [...KOREAN_VITAL_POINTS];\n\n if (severityFilters.length > 0) {\n points = points.filter((vp) => severityFilters.includes(vp.severity));\n }\n\n if (regionFilter !== \"all\") {\n if (regionFilter === \"arms\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"arm_left_\") || vp.id.startsWith(\"arm_right_\"),\n );\n } else if (regionFilter === \"legs\") {\n points = points.filter(\n (vp) =>\n vp.id.startsWith(\"leg_left_\") || vp.id.startsWith(\"leg_right_\"),\n );\n } else {\n const prefix = `${regionFilter}_`;\n points = points.filter((vp) => vp.id.startsWith(prefix));\n }\n }\n\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n points = points.filter(\n (vp) =>\n vp.names.korean.toLowerCase().includes(query) ||\n vp.names.english.toLowerCase().includes(query) ||\n vp.names.romanized.toLowerCase().includes(query) ||\n vp.id.toLowerCase().includes(query),\n );\n }\n\n return points.length;\n }, [severityFilters, regionFilter, searchQuery]);\n\n const toggleSeverityFilter = useCallback(\n (severity: VitalPointSeverity) => {\n const newFilters = severityFilters.includes(severity)\n ? severityFilters.filter((s) => s !== severity)\n : [...severityFilters, severity];\n onSeverityFiltersChange(newFilters);\n },\n [severityFilters, onSeverityFiltersChange],\n );\n\n const severityOptions: VitalPointSeverity[] = [\n VitalPointSeverity.LETHAL,\n VitalPointSeverity.CRITICAL,\n VitalPointSeverity.MAJOR,\n VitalPointSeverity.MODERATE,\n VitalPointSeverity.MINOR,\n ];\n\n const regionOptions: {\n value: BodyRegionFilter;\n label: string;\n korean: string;\n }[] = [\n { value: \"all\", label: \"All Regions\", korean: \"전체\" },\n { value: \"head\", label: \"Head\", korean: \"머리\" },\n { value: \"torso\", label: \"Torso\", korean: \"몸통\" },\n { value: \"arms\", label: \"Arms\", korean: \"팔\" },\n { value: \"legs\", label: \"Legs\", korean: \"다리\" },\n ];\n\n const panelWidth = isMobile ? 280 : 350;\n const buttonHeight = isMobile ? 32 : 36;\n const fontSize = isMobile ? 11 : 13;\n const smallFontSize = isMobile ? 9 : 10;\n\n return (\n <div\n style={{\n position: \"absolute\",\n top: finalPosition.top,\n left: finalPosition.left,\n right: finalPosition.right,\n bottom: finalPosition.bottom,\n width: panelWidth,\n background: `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}f0`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"12px\",\n padding: isMobile ? \"12px\" : \"16px\",\n fontFamily: FONT_FAMILY.KOREAN,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n boxShadow: `0 0 30px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40, inset 0 0 20px ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}80`,\n transition: \"all 0.3s ease\",\n pointerEvents: \"all\",\n zIndex: Z_INDEX.MODAL,\n }}\n data-testid=\"vital-point-overlay-controls\"\n >\n {/* Header with toggle */}\n <div\n style={{\n display: \"flex\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n marginBottom: \"12px\",\n paddingBottom: \"12px\",\n borderBottom: `1px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}40`,\n background: `linear-gradient(90deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_DARK,\n )}00 0%, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}10 50%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)}00 100%)`,\n }}\n >\n <div>\n <div style={{ fontSize: isMobile ? 14 : 16, fontWeight: \"bold\" }}>\n 급소 오버레이 | Vital Points\n </div>\n <div\n style={{\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n marginTop: \"2px\",\n }}\n >\n {filteredCount} / {stats.total} 표시 | Showing\n </div>\n </div>\n <button\n onClick={() => setExpanded(!expanded)}\n style={{\n background: `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}`,\n borderRadius: \"6px\",\n padding: \"8px 14px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n boxShadow: `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"scale(1.05)\";\n e.currentTarget.style.boxShadow = `0 4px 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"scale(1)\";\n e.currentTarget.style.boxShadow = `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}30`;\n }}\n data-testid=\"toggle-expand-button\"\n >\n {expanded ? \"▼\" : \"▶\"}\n </button>\n </div>\n\n {/* Main toggle */}\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => onVisibleChange(!visible)}\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: visible\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.SECONDARY_YELLOW)} 100%)`\n : `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_DARK)} 100%)`,\n border: `2px solid ${\n visible\n ? hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)\n : hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)\n }`,\n borderRadius: \"8px\",\n color: visible ? hexColorToCSS(KOREAN_COLORS.KOREAN_BLACK) : hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: isMobile ? 13 : 15,\n fontWeight: \"bold\",\n cursor: \"pointer\",\n transition: \"all 0.3s ease\",\n boxShadow: visible\n ? `0 4px 16px ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_GOLD,\n )}60, inset 0 2px 4px ${hexToRgbaString(KOREAN_COLORS.TEXT_PRIMARY, 0.2)}`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`,\n textTransform: \"uppercase\",\n letterSpacing: \"0.5px\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.transform = \"translateY(-2px)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 6px 20px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}80`\n : `0 4px 12px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}50`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.transform = \"translateY(0)\";\n e.currentTarget.style.boxShadow = visible\n ? `0 4px 16px ${hexColorToCSS(KOREAN_COLORS.ACCENT_GOLD)}60`\n : `0 2px 8px ${hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN)}30`;\n }}\n data-testid=\"toggle-visibility-button\"\n >\n {visible ? \"✓ 활성화 | Enabled\" : \"비활성화 | Disabled\"}\n </button>\n </div>\n\n {/* Expanded controls */}\n {expanded && visible && (\n <>\n {/* Severity filters */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 심각도 필터 | Severity Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {severityOptions.map((severity) => {\n const isActive = severityFilters.includes(severity);\n const severityColor = getSeverityColor(severity);\n return (\n <button\n key={severity}\n onClick={() => toggleSeverityFilter(severity)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${severityColor} 0%, ${severityColor}cc 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${severityColor}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${severityColor}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`severity-filter-${severity}`}\n >\n {severity}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Region filter */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 부위 필터 | Region Filter\n </div>\n <div\n style={{\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: \"8px\",\n }}\n >\n {regionOptions.map((option) => {\n const isActive = regionFilter === option.value;\n return (\n <button\n key={option.value}\n onClick={() => onRegionFilterChange(option.value)}\n style={{\n background: isActive\n ? `linear-gradient(135deg, ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )} 0%, ${hexColorToCSS(KOREAN_COLORS.ACCENT_BLUE)} 100%)`\n : `${hexColorToCSS(KOREAN_COLORS.UI_BACKGROUND_MEDIUM)}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}`,\n borderRadius: \"6px\",\n padding: \"6px 12px\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n opacity: isActive ? 1 : 0.6,\n transition: \"all 0.2s ease\",\n fontWeight: isActive ? \"bold\" : \"normal\",\n boxShadow: isActive\n ? `0 2px 8px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}50`\n : \"none\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.opacity = \"1\";\n e.currentTarget.style.transform = \"scale(1.05)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.opacity = isActive ? \"1\" : \"0.6\";\n e.currentTarget.style.transform = \"scale(1)\";\n }}\n data-testid={`region-filter-${option.value}`}\n >\n {option.korean} | {option.label}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Search box with clear button */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 검색 | Search\n </div>\n <div style={{ position: \"relative\" }}>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder=\"급소 이름... | Point name...\"\n style={{\n width: \"100%\",\n height: buttonHeight,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n borderRadius: \"8px\",\n padding: \"0 40px 0 14px\", // Add right padding for clear button\n color: hexColorToCSS(KOREAN_COLORS.TEXT_PRIMARY),\n fontSize,\n fontFamily: FONT_FAMILY.KOREAN,\n transition: \"all 0.2s ease\",\n outline: \"none\",\n }}\n onFocus={(e) => {\n e.currentTarget.style.borderColor = hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n );\n e.currentTarget.style.boxShadow = `0 0 12px ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n }}\n onBlur={(e) => {\n e.currentTarget.style.borderColor = `${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`;\n e.currentTarget.style.boxShadow = \"none\";\n }}\n data-testid=\"search-input\"\n />\n {/* Clear button */}\n {searchQuery && (\n <button\n onClick={() => setSearchQuery(\"\")}\n style={{\n position: \"absolute\",\n right: \"8px\",\n top: \"50%\",\n transform: \"translateY(-50%)\",\n background: \"transparent\",\n border: \"none\",\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n cursor: \"pointer\",\n fontSize: \"16px\",\n padding: \"4px\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n borderRadius: \"4px\",\n transition: \"all 0.2s ease\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.ACCENT_RED,\n );\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.color = hexColorToCSS(\n KOREAN_COLORS.TEXT_SECONDARY,\n );\n e.currentTarget.style.background = \"transparent\";\n }}\n data-testid=\"search-clear-button\"\n title=\"Clear search\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n\n {/* Display options */}\n <div style={{ marginBottom: \"14px\" }}>\n <div\n style={{\n fontSize,\n marginBottom: \"8px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n fontWeight: \"600\",\n textTransform: \"uppercase\",\n letterSpacing: \"1px\",\n }}\n >\n 표시 옵션 | Display Options\n </div>\n <div\n style={{ display: \"flex\", flexDirection: \"column\", gap: \"8px\" }}\n >\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={showLabels}\n onChange={(e) => onShowLabelsChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"show-labels-checkbox\"\n />\n <span style={{ fontSize }}>라벨 표시 | Show Labels</span>\n </label>\n <label\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: \"8px\",\n cursor: \"pointer\",\n }}\n >\n <input\n type=\"checkbox\"\n checked={animated}\n onChange={(e) => onAnimatedChange(e.target.checked)}\n style={{\n width: \"16px\",\n height: \"16px\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"animated-checkbox\"\n />\n <span style={{ fontSize }}>애니메이션 | Animations</span>\n </label>\n </div>\n </div>\n\n {/* Reset filters button */}\n {(severityFilters.length > 0 ||\n regionFilter !== \"all\" ||\n searchQuery !== \"\") && (\n <div style={{ marginBottom: \"14px\" }}>\n <button\n onClick={() => {\n onSeverityFiltersChange([]);\n onRegionFilterChange(\"all\");\n setSearchQuery(\"\");\n }}\n style={{\n width: \"100%\",\n height: buttonHeight - 4,\n background: `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`,\n border: `2px solid ${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}`,\n borderRadius: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_ORANGE),\n fontSize: smallFontSize,\n cursor: \"pointer\",\n transition: \"all 0.2s ease\",\n fontWeight: \"bold\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.ACCENT_ORANGE,\n )}20`;\n e.currentTarget.style.transform = \"translateY(-1px)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = `${hexColorToCSS(\n KOREAN_COLORS.UI_BACKGROUND_MEDIUM,\n )}`;\n e.currentTarget.style.transform = \"translateY(0)\";\n }}\n data-testid=\"reset-filters-button\"\n >\n 🔄 필터 초기화 | Reset Filters\n </button>\n </div>\n )}\n\n {/* Scale slider */}\n <div>\n <div\n style={{\n fontSize,\n marginBottom: \"6px\",\n color: hexColorToCSS(KOREAN_COLORS.ACCENT_CYAN),\n }}\n >\n 크기 | Scale: {scale.toFixed(1)}x\n </div>\n <input\n type=\"range\"\n min=\"0.5\"\n max=\"2.0\"\n step=\"0.1\"\n value={scale}\n onChange={(e) => onScaleChange(parseFloat(e.target.value))}\n style={{\n width: \"100%\",\n accentColor: hexColorToCSS(KOREAN_COLORS.PRIMARY_CYAN),\n }}\n data-testid=\"scale-slider\"\n />\n </div>\n\n {/* Statistics */}\n <div\n style={{\n marginTop: \"12px\",\n paddingTop: \"12px\",\n borderTop: `1px solid ${hexColorToCSS(\n KOREAN_COLORS.PRIMARY_CYAN,\n )}40`,\n fontSize: smallFontSize,\n color: hexColorToCSS(KOREAN_COLORS.TEXT_SECONDARY),\n }}\n >\n <div>\n 머리: {stats.byRegion.head} | 몸통: {stats.byRegion.torso}\n </div>\n <div>\n 팔: {stats.byRegion.arms} | 다리: {stats.byRegion.legs}\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n\nexport default VitalPointOverlayControlsPure;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"BalanceIndicatorOverlayHtml.js","names":[],"sources":["../../../../src/components/ui/combat/BalanceIndicatorOverlayHtml.tsx"],"sourcesContent":["/**\n * BalanceIndicatorOverlayHtml Component - 3D Html overlay for balance state\n *\n * Displays balance state with vulnerability indicators using Html from @react-three/drei.\n * Shows:\n * - Current balance percentage\n * - Vulnerability state (red border + shake effect)\n * - Bilingual Korean/English tooltips\n * - Stance transition vulnerability\n * - Rapid change penalty indicator\n *\n * @module components/ui/combat/BalanceIndicatorOverlayHtml\n * @category Combat UI\n * @korean 균형표시오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { Html } from \"@react-three/drei\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport type { BalancePlayerState } from \"@/systems/combat/BalanceSystem\";\n\nexport interface BalanceIndicatorOverlayHtmlProps {\n /**\n * Player state with balance and transition data\n * @korean 플레이어상태\n */\n readonly player: BalancePlayerState;\n\n /**\n * Current game time in milliseconds\n * @korean 현재시간\n */\n readonly currentTime: number;\n\n /**\n * Position in 3D space [x, y, z]\n * @korean 3D위치\n */\n readonly position?: [number, number, number];\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile?: boolean;\n}\n\n/**\n * Get balance color based on percentage\n */\nfunction getBalanceColor(balance: number): string {\n if (balance >= 80) return `#${KOREAN_COLORS.POSITIVE_GREEN.toString(16).padStart(6, \"0\")}`;\n if (balance >= 50) return `#${KOREAN_COLORS.WARNING_YELLOW.toString(16).padStart(6, \"0\")}`;\n if (balance >= 20) return `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`;\n return `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Get balance state label (bilingual)\n */\nfunction getBalanceLabel(balance: number): {\n korean: string;\n english: string;\n} {\n if (balance >= 80)\n return { korean: \"안정\", english: \"Stable\" };\n if (balance >= 50)\n return { korean: \"불안정\", english: \"Unsteady\" };\n if (balance >= 20)\n return { korean: \"균형상실\", english: \"Off-Balance\" };\n return { korean: \"낙하중\", english: \"Falling\" };\n}\n\n/**\n * BalanceIndicatorOverlayHtml - 3D Html overlay for balance visualization\n *\n * Renders above player character in 3D space using Html from @react-three/drei.\n * Shows balance percentage, vulnerability state, and active modifiers.\n *\n * Features:\n * - Red border + shake animation when vulnerable\n * - Bilingual Korean/English labels\n * - Transition vulnerability indicator\n * - Rapid change penalty warning\n *\n * @example\n * ```tsx\n * <BalanceIndicatorOverlayHtml\n * player={playerState}\n * currentTime={Date.now()}\n * position={[0, 2.5, 0]}\n * isMobile={false}\n * />\n * ```\n */\nexport const BalanceIndicatorOverlayHtml: React.FC<\n BalanceIndicatorOverlayHtmlProps\n> = ({ player, currentTime, position = [0, 2, 0], isMobile = false }) => {\n const isVulnerable = useMemo(() => {\n const isTransitioning = player.transitionState?.isTransitioning ?? false;\n const isLowBalance = player.balance < 50; // Off-balance or worse\n const hasPenalty =\n player.rapidChangePenaltyEnd !== undefined &&\n currentTime < player.rapidChangePenaltyEnd;\n return isTransitioning ?? isLowBalance ?? hasPenalty;\n }, [player.balance, player.transitionState, player.rapidChangePenaltyEnd, currentTime]);\n\n const balanceColor = useMemo(\n () => getBalanceColor(player.balance),\n [player.balance]\n );\n\n const balanceLabel = useMemo(\n () => getBalanceLabel(player.balance),\n [player.balance]\n );\n\n const vulnerableColorHex = `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n\n const containerStyle = useMemo(() => {\n const baseStyle: React.CSSProperties = {\n border: isVulnerable\n ? `2px solid ${vulnerableColorHex}`\n : `2px solid ${balanceColor}`,\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n backgroundColor: \"rgba(10, 10, 10, 0.85)\",\n borderRadius: \"8px\",\n boxShadow: isVulnerable\n ? `0 0 16px ${vulnerableColorHex}`\n : `0 0 12px ${balanceColor}`,\n transition: \"all 0.3s ease-out\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? \"12px\" : \"14px\",\n minWidth: isMobile ? \"140px\" : \"180px\",\n textAlign: \"center\" as const,\n userSelect: \"none\" as const,\n pointerEvents: \"none\" as const,\n };\n\n if (isVulnerable) {\n baseStyle.animation = \"shake 0.3s infinite\";\n }\n\n return baseStyle;\n }, [isVulnerable, balanceColor, vulnerableColorHex, isMobile]);\n\n const balancePercentage = Math.round(player.balance);\n\n return (\n <>\n {/* Inject keyframes for shake animation */}\n <Html position={position} center>\n <style>\n {`\n @keyframes shake {\n 0%, 100% { transform: translateX(0); }\n 25% { transform: translateX(-2px); }\n 75% { transform: translateX(2px); }\n }\n `}\n </style>\n <div style={containerStyle} data-testid=\"balance-indicator-overlay\">\n {/* Balance percentage and label */}\n <div\n style={{\n color: balanceColor,\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n }}\n >\n 균형 | Balance: {balancePercentage}%\n </div>\n\n {/* Balance state label */}\n <div\n style={{\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"10px\" : \"12px\",\n }}\n >\n {balanceLabel.korean} | {balanceLabel.english}\n </div>\n\n {/* Vulnerability indicators */}\n {player.transitionState?.isTransitioning && (\n <div\n style={{\n color: vulnerableColorHex,\n fontSize: isMobile ? \"10px\" : \"11px\",\n marginTop: \"4px\",\n fontWeight: \"bold\",\n }}\n >\n 취약 | Vulnerable!\n </div>\n )}\n\n {/* Rapid change penalty indicator */}\n {player.rapidChangePenaltyEnd && currentTime < player.rapidChangePenaltyEnd && (\n <div\n style={{\n color: `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"9px\" : \"10px\",\n marginTop: \"2px\",\n }}\n >\n 급속변경 벌칙 | Rapid Change Penalty\n </div>\n )}\n </div>\n </Html>\n </>\n );\n};\n\nBalanceIndicatorOverlayHtml.displayName = \"BalanceIndicatorOverlayHtml\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"BalanceIndicatorOverlayHtml.js","names":[],"sources":["../../../../src/components/ui/combat/BalanceIndicatorOverlayHtml.tsx"],"sourcesContent":["/**\n * BalanceIndicatorOverlayHtml Component - 3D Html overlay for balance state\n *\n * Displays balance state with vulnerability indicators using Html from @react-three/drei.\n * Shows:\n * - Current balance percentage\n * - Vulnerability state (red border + shake effect)\n * - Bilingual Korean/English tooltips\n * - Stance transition vulnerability\n * - Rapid change penalty indicator\n *\n * @module components/ui/combat/BalanceIndicatorOverlayHtml\n * @category Combat UI\n * @korean 균형표시오버레이\n */\n\nimport React, { useMemo } from \"react\";\nimport { Html } from \"@react-three/drei\";\nimport { KOREAN_COLORS, FONT_FAMILY } from \"@/types/constants\";\nimport type { BalancePlayerState } from \"@/systems/combat/BalanceSystem\";\n\nexport interface BalanceIndicatorOverlayHtmlProps {\n /**\n * Player state with balance and transition data\n * @korean 플레이어상태\n */\n readonly player: BalancePlayerState;\n\n /**\n * Current game time in milliseconds\n * @korean 현재시간\n */\n readonly currentTime: number;\n\n /**\n * Position in 3D space [x, y, z]\n * @korean 3D위치\n */\n readonly position?: [number, number, number];\n\n /**\n * Mobile responsive mode\n * @korean 모바일여부\n */\n readonly isMobile?: boolean;\n}\n\n/**\n * Get balance color based on percentage\n */\nfunction getBalanceColor(balance: number): string {\n if (balance >= 80) return `#${KOREAN_COLORS.POSITIVE_GREEN.toString(16).padStart(6, \"0\")}`;\n if (balance >= 50) return `#${KOREAN_COLORS.WARNING_YELLOW.toString(16).padStart(6, \"0\")}`;\n if (balance >= 20) return `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`;\n return `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n}\n\n/**\n * Get balance state label (bilingual)\n */\nfunction getBalanceLabel(balance: number): {\n korean: string;\n english: string;\n} {\n if (balance >= 80)\n return { korean: \"안정\", english: \"Stable\" };\n if (balance >= 50)\n return { korean: \"불안정\", english: \"Unsteady\" };\n if (balance >= 20)\n return { korean: \"균형상실\", english: \"Off-Balance\" };\n return { korean: \"낙하중\", english: \"Falling\" };\n}\n\n/**\n * BalanceIndicatorOverlayHtml - 3D Html overlay for balance visualization\n *\n * Renders above player character in 3D space using Html from @react-three/drei.\n * Shows balance percentage, vulnerability state, and active modifiers.\n *\n * Features:\n * - Red border + shake animation when vulnerable\n * - Bilingual Korean/English labels\n * - Transition vulnerability indicator\n * - Rapid change penalty warning\n *\n * @example\n * ```tsx\n * <BalanceIndicatorOverlayHtml\n * player={playerState}\n * currentTime={Date.now()}\n * position={[0, 2.5, 0]}\n * isMobile={false}\n * />\n * ```\n */\nexport const BalanceIndicatorOverlayHtml: React.FC<\n BalanceIndicatorOverlayHtmlProps\n> = ({ player, currentTime, position = [0, 2, 0], isMobile = false }) => {\n const isVulnerable = useMemo(() => {\n const isTransitioning = player.transitionState?.isTransitioning ?? false;\n const isLowBalance = player.balance < 50; // Off-balance or worse\n const hasPenalty =\n player.rapidChangePenaltyEnd !== undefined &&\n currentTime < player.rapidChangePenaltyEnd;\n return isTransitioning ?? isLowBalance ?? hasPenalty;\n }, [player.balance, player.transitionState, player.rapidChangePenaltyEnd, currentTime]);\n\n const balanceColor = useMemo(\n () => getBalanceColor(player.balance),\n [player.balance]\n );\n\n const balanceLabel = useMemo(\n () => getBalanceLabel(player.balance),\n [player.balance]\n );\n\n const vulnerableColorHex = `#${KOREAN_COLORS.ACCENT_RED.toString(16).padStart(6, \"0\")}`;\n\n const containerStyle = useMemo(() => {\n const baseStyle: React.CSSProperties = {\n border: isVulnerable\n ? `2px solid ${vulnerableColorHex}`\n : `2px solid ${balanceColor}`,\n padding: isMobile ? \"6px 10px\" : \"8px 12px\",\n backgroundColor: \"rgba(10, 10, 10, 0.85)\",\n borderRadius: \"8px\",\n boxShadow: isVulnerable\n ? `0 0 16px ${vulnerableColorHex}`\n : `0 0 12px ${balanceColor}`,\n transition: \"all 0.3s ease-out\",\n fontFamily: FONT_FAMILY.KOREAN,\n fontSize: isMobile ? \"12px\" : \"14px\",\n minWidth: isMobile ? \"140px\" : \"180px\",\n textAlign: \"center\" as const,\n userSelect: \"none\" as const,\n pointerEvents: \"none\" as const,\n };\n\n if (isVulnerable) {\n baseStyle.animation = \"shake 0.3s infinite\";\n }\n\n return baseStyle;\n }, [isVulnerable, balanceColor, vulnerableColorHex, isMobile]);\n\n const balancePercentage = Math.round(player.balance);\n\n return (\n <>\n {/* Inject keyframes for shake animation */}\n <Html position={position} center>\n <style>\n {`\n @keyframes shake {\n 0%, 100% { transform: translateX(0); }\n 25% { transform: translateX(-2px); }\n 75% { transform: translateX(2px); }\n }\n `}\n </style>\n <div style={containerStyle} data-testid=\"balance-indicator-overlay\">\n {/* Balance percentage and label */}\n <div\n style={{\n color: balanceColor,\n fontWeight: \"bold\",\n marginBottom: \"4px\",\n }}\n >\n 균형 | Balance: {balancePercentage}%\n </div>\n\n {/* Balance state label */}\n <div\n style={{\n color: `#${KOREAN_COLORS.TEXT_SECONDARY.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"10px\" : \"12px\",\n }}\n >\n {balanceLabel.korean} | {balanceLabel.english}\n </div>\n\n {/* Vulnerability indicators */}\n {player.transitionState?.isTransitioning && (\n <div\n style={{\n color: vulnerableColorHex,\n fontSize: isMobile ? \"10px\" : \"11px\",\n marginTop: \"4px\",\n fontWeight: \"bold\",\n }}\n >\n 취약 | Vulnerable!\n </div>\n )}\n\n {/* Rapid change penalty indicator */}\n {player.rapidChangePenaltyEnd && currentTime < player.rapidChangePenaltyEnd && (\n <div\n style={{\n color: `#${KOREAN_COLORS.WARNING_ORANGE.toString(16).padStart(6, \"0\")}`,\n fontSize: isMobile ? \"9px\" : \"10px\",\n marginTop: \"2px\",\n }}\n >\n 급속변경 벌칙 | Rapid Change Penalty\n </div>\n )}\n </div>\n </Html>\n </>\n );\n};\n\nBalanceIndicatorOverlayHtml.displayName = \"BalanceIndicatorOverlayHtml\";\n"],"mappings":"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"}