generated from nhcarrigan/template
5ad2c44399
- Add user-configurable number format (suffix/scientific/engineering) - Suffix: K/M/B/T through Dc (1e33), then letter-based a/b/c... indefinitely - Scientific: 1.23e15 style via toExponential - Engineering: exponent always a multiple of 3 (1.23E15) - Stored in ProfileSettings, fetched from profile API on load - Picker UI in EditProfileModal with live examples - Cap all resource accumulation at 1e300 (RESOURCE_CAP constant) - Per-resource FULL badge with tooltip in ResourceBar - Amber notice strip when any resource is at cap - handleClick also respects the cap - Make EditProfileModal scrollable with viewport margin - Flex column layout with sticky header, scrollable form body - Bio textarea preserved as resizable with min-height - Fix ReferenceError: formatNumber not defined in BossPanel/AchievementPanel - Pass formatNumber as prop to BossCard and AchievementCard - Pass formatNumber as parameter to conditionDescription
66 lines
1.8 KiB
TypeScript
66 lines
1.8 KiB
TypeScript
import { useCallback, useRef, useState } from "react";
|
||
import { useGame } from "../../context/GameContext.js";
|
||
import { calculateClickPower } from "../../engine/tick.js";
|
||
|
||
interface FloatText {
|
||
id: number;
|
||
x: number;
|
||
y: number;
|
||
text: string;
|
||
}
|
||
|
||
export const ClickArea = (): React.JSX.Element => {
|
||
const { state, handleClick, formatNumber } = useGame();
|
||
const [floats, setFloats] = useState<FloatText[]>([]);
|
||
const nextIdRef = useRef(0);
|
||
|
||
const handleClickWithFloat = useCallback(
|
||
(e: React.MouseEvent<HTMLButtonElement>) => {
|
||
if (!state) return;
|
||
const rect = e.currentTarget.getBoundingClientRect();
|
||
const x = e.clientX - rect.left;
|
||
const y = e.clientY - rect.top;
|
||
const id = nextIdRef.current++;
|
||
const clickPower = calculateClickPower(state);
|
||
|
||
setFloats((prev) => [...prev, { id, x, y, text: `+${formatNumber(clickPower)}` }]);
|
||
handleClick();
|
||
|
||
setTimeout(() => {
|
||
setFloats((prev) => prev.filter((f) => f.id !== id));
|
||
}, 900);
|
||
},
|
||
[state, handleClick],
|
||
);
|
||
|
||
if (!state) return <div className="click-area-placeholder" />;
|
||
|
||
const clickPower = calculateClickPower(state);
|
||
|
||
return (
|
||
<section className="click-area">
|
||
<h2>Guild Hall</h2>
|
||
<div className="click-button-wrapper">
|
||
<button
|
||
className="click-button"
|
||
onClick={handleClickWithFloat}
|
||
type="button"
|
||
aria-label={`Click to earn ${formatNumber(clickPower)} gold`}
|
||
>
|
||
⚔️
|
||
</button>
|
||
{floats.map((float) => (
|
||
<span
|
||
key={float.id}
|
||
className="click-float"
|
||
style={{ left: float.x, top: float.y }}
|
||
>
|
||
{float.text}
|
||
</span>
|
||
))}
|
||
</div>
|
||
<p className="click-power">+{formatNumber(clickPower)} gold/click</p>
|
||
</section>
|
||
);
|
||
};
|