generated from nhcarrigan/template
feat: add equipment, achievements, and visual polish
- Equipment system: 12 items across weapon/armour/trinket slots with common/rare/epic/legendary rarities; starter commons auto-equipped, higher tiers drop from boss victories - Achievement system: 15 milestones with typed conditions; checked each tick and crystal rewards applied automatically - Achievement toast: slide-in notification, auto-dismisses after 4s - Floating click text: +X gold floats on each manual click - Expanded quests (9 total) and upgrades (12 total) - Upgrade panel now shows locked upgrades so players can see their progression path - formatNumber utility (K/M/B/T) used consistently across all panels - Backfill logic for existing saves to add new content gracefully - types package now emits .d.ts declarations
This commit is contained in:
@@ -1,8 +1,38 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import { useGame } from "../../context/GameContext.js";
|
||||
import { calculateClickPower } from "../../engine/tick.js";
|
||||
import { formatNumber } from "../../utils/format.js";
|
||||
|
||||
interface FloatText {
|
||||
id: number;
|
||||
x: number;
|
||||
y: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export const ClickArea = (): React.JSX.Element => {
|
||||
const { state, handleClick } = 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" />;
|
||||
|
||||
@@ -11,15 +41,26 @@ export const ClickArea = (): React.JSX.Element => {
|
||||
return (
|
||||
<section className="click-area">
|
||||
<h2>Guild Hall</h2>
|
||||
<button
|
||||
className="click-button"
|
||||
onClick={handleClick}
|
||||
type="button"
|
||||
aria-label={`Click to earn ${clickPower.toFixed(1)} gold`}
|
||||
>
|
||||
⚔️
|
||||
</button>
|
||||
<p className="click-power">+{clickPower.toFixed(1)} gold per click</p>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user