import { useLayoutEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { Button } from '../../../../shared/ui/Button' import { themeToCssVars } from '../../../clickgui/themeVars' import { parseArgb } from '../../../themes/types' import { useActiveTheme } from '../../../themes/useActiveTheme' import { VoxelScene } from '../../effects/VoxelScene' import '../../styles/landing.css' const STEP = 8 /** Fallback playfield size, used before the box is measured (and under jsdom). */ const FIELD = { w: 680, h: 380 } const WIDGET = { w: 150, h: 56 } type Pos = { x: number; y: number } /** Real draggable HUD elements from the mod: features/gui/hud/draggable/impl. */ type WidgetId = 'fps' | 'coords' | 'modules' | 'keys' | 'ping' const START: Record = { fps: { x: 20, y: 20 }, coords: { x: 20, y: 310 }, modules: { x: FIELD.w - 190, y: 20 }, ping: { x: FIELD.w - 170, y: 150 }, keys: { x: FIELD.w - 140, y: FIELD.h - 120 }, } const clamp = (v: number, max: number) => Math.max(0, Math.min(v, max)) /** Pull every widget back inside the measured field (mount, resize, reset). */ function fitToField( state: Record, field: HTMLDivElement | null, widgets: Partial>, ): Record { const w = field?.clientWidth || FIELD.w const h = field?.clientHeight || FIELD.h const next = { ...state } let changed = false for (const id of Object.keys(next) as WidgetId[]) { const el = widgets[id] const maxX = Math.max(0, w - (el?.offsetWidth || WIDGET.w)) const maxY = Math.max(0, h - (el?.offsetHeight || WIDGET.h)) const x = clamp(next[id].x, maxX) const y = clamp(next[id].y, maxY) if (x !== next[id].x || y !== next[id].y) { next[id] = { x, y } changed = true } } return changed ? next : state } /** Enabled modules as they would appear in the mod's ModuleList panel. */ const MODULES_ON = ['TargetESP', 'PvpCooldowns', 'HitBubbles', 'ShaderSky'] /** Widgets read the accent through --gui-accent so it cross-fades with the active theme. */ const ACCENT = 'var(--gui-accent)' function WidgetContent({ id, accent, unitMs }: { id: WidgetId; accent: string; unitMs: string }) { switch (id) { case 'fps': return (
240 FPS
) case 'coords': return (
X 128  Y 74 {' '} Z -204
) case 'modules': return (
{MODULES_ON.map((m) => ( {m} ))}
) case 'keys': { const cap = (k: string, pressed = false) => ( {k} ) return (
{cap('W', true)}
{cap('A')} {cap('S', true)} {cap('D')}
) } case 'ping': return (
32 {unitMs}
) } } export function HudPlayground() { const { t } = useTranslation('landing') const { theme } = useActiveTheme() const { r, g, b } = parseArgb(theme.theme.accent) const fieldRef = useRef(null) const widgetRefs = useRef>>({}) const drag = useRef<{ id: WidgetId; px: number; py: number; ox: number; oy: number } | null>(null) const [pos, setPos] = useState>(START) const bounds = (el: HTMLElement) => { const field = fieldRef.current const w = field?.clientWidth || FIELD.w const h = field?.clientHeight || FIELD.h return { maxX: Math.max(0, w - (el.offsetWidth || WIDGET.w)), maxY: Math.max(0, h - (el.offsetHeight || WIDGET.h)) } } useLayoutEffect(() => { const fit = () => setPos((prev) => fitToField(prev, fieldRef.current, widgetRefs.current)) fit() const field = fieldRef.current if (!field || typeof ResizeObserver === 'undefined') return const ro = new ResizeObserver(fit) ro.observe(field) return () => ro.disconnect() }, []) const nudge = (id: WidgetId, dx: number, dy: number, el: HTMLElement) => { const { maxX, maxY } = bounds(el) setPos((prev) => ({ ...prev, [id]: { x: clamp(prev[id].x + dx, maxX), y: clamp(prev[id].y + dy, maxY) } })) } const onKeyDown = (id: WidgetId) => (e: React.KeyboardEvent) => { const deltas: Record = { ArrowLeft: [-STEP, 0], ArrowRight: [STEP, 0], ArrowUp: [0, -STEP], ArrowDown: [0, STEP], } const d = deltas[e.key] if (!d) return e.preventDefault() nudge(id, d[0], d[1], e.currentTarget) } const onPointerDown = (id: WidgetId) => (e: React.PointerEvent) => { e.currentTarget.setPointerCapture(e.pointerId) drag.current = { id, px: e.clientX, py: e.clientY, ox: pos[id].x, oy: pos[id].y } } const onPointerMove = (e: React.PointerEvent) => { const d = drag.current if (!d) return const { maxX, maxY } = bounds(e.currentTarget) setPos((prev) => ({ ...prev, [d.id]: { x: clamp(d.ox + e.clientX - d.px, maxX), y: clamp(d.oy + e.clientY - d.py, maxY) } })) } const onPointerUp = () => { drag.current = null } const widgets: { id: WidgetId; label: string }[] = [ { id: 'fps', label: t('hud.widgetFps') }, { id: 'coords', label: t('hud.widgetCoords') }, { id: 'modules', label: t('hud.widgetModules') }, { id: 'keys', label: t('hud.widgetKeys') }, { id: 'ping', label: t('hud.widgetPing') }, ] return (
{t('hud.eyebrow')}

{t('hud.title')}

{t('hud.lead')}

) }