This commit is contained in:
lubiana 2025-07-12 19:26:44 +02:00
parent e3d1512145
commit 1b67c213a4
Signed by: lubiana
SSH key fingerprint: SHA256:vW1EA0fRR3Fw+dD/sM0K+x3Il2gSry6YRYHqOeQwrfk
2 changed files with 44 additions and 15 deletions

View file

@ -1,4 +1,4 @@
import React, { useState, useCallback, useMemo } from 'react'; import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
import Card from './components/Card'; import Card from './components/Card';
import TierCard from './components/TierCard'; import TierCard from './components/TierCard';
import HistoryList from './components/HistoryList'; import HistoryList from './components/HistoryList';
@ -55,6 +55,27 @@ function App() {
const [upgrades, setUpgrades] = useState<Upgrades>({ evenDouble: false, oddDouble: false, allTripple: false, gubbleDouble: false, addFourthNumber: false }); const [upgrades, setUpgrades] = useState<Upgrades>({ evenDouble: false, oddDouble: false, allTripple: false, gubbleDouble: false, addFourthNumber: false });
const [upgradeBought, setUpgradeBought] = useState(false); const [upgradeBought, setUpgradeBought] = useState(false);
// Ref for scroll container
const tierRowRef = useRef<HTMLDivElement>(null);
// Ref for the highest unlocked card
const highestUnlockedRef = useRef<HTMLDivElement>(null);
// Find the index of the highest unlocked tier
const highestUnlockedIndex = useMemo(() => {
let maxIdx = -1;
TIERS.forEach((tier, idx) => {
if (unlockedTiers.includes(tier.name)) maxIdx = idx;
});
return maxIdx;
}, [unlockedTiers]);
// Scroll the highest unlocked card into view when unlockedTiers changes
useEffect(() => {
if (highestUnlockedRef.current && tierRowRef.current) {
highestUnlockedRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
}
}, [highestUnlockedIndex]);
// --- Tier Unlock Logic --- // --- Tier Unlock Logic ---
const handleUnlockTier = useCallback((tier: Tier) => { const handleUnlockTier = useCallback((tier: Tier) => {
setMoney(m => { setMoney(m => {
@ -121,20 +142,20 @@ function App() {
// --- Memoized winnings for current card --- // --- Memoized winnings for current card ---
const currentWinnings = useMemo(() => card ? card.fields.reduce((sum, f) => sum + (f.won || 0), 0) : 0, [card]); const currentWinnings = useMemo(() => card ? card.fields.reduce((sum, f) => sum + (f.won || 0), 0) : 0, [card]);
const formatter = Intl.NumberFormat(
'en',
{ 'notation': 'compact' }
)
// --- UI --- // --- UI ---
return ( return (
<div className="min-h-screen bg-gray-100 flex flex-col items-center "> <div className="min-h-screen bg-gray-100 flex flex-col items-center ">
<h1 className="text-3xl font-bold text-blue-600 mb-2">Scratch Card Game</h1> <div className="flex flex-row items-center gap-6 mb-6 mt-2 w-full max-w-4xl justify-center">
<div className="mb-4 text-lg">Money: <span className="font-mono font-bold">${money}</span></div> <h1 className="text-3xl font-bold text-blue-600">Scratch Card Game</h1>
<div className="mb-4 text-lg">Gubble Points: <span className="font-mono font-bold">{gubblePoints}</span></div> <div className="text-lg">Money: <span className="font-mono font-bold">${formatter.format(money)}</span></div>
<button <div className="text-lg">Gubble Points: <span className="font-mono font-bold">{formatter.format(gubblePoints)}</span></div>
className="mb-4 px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white font-bold rounded disabled:opacity-50" <button className="px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white font-bold rounded disabled:opacity-50" disabled={gubblePoints < 1} onClick={() => setIsStoreOpen(true)}>Open Gubble Point Store</button>
disabled={gubblePoints < 1} </div>
onClick={() => setIsStoreOpen(true)}
>
Open Gubble Point Store
</button>
<GubbleStore <GubbleStore
isOpen={isStoreOpen} isOpen={isStoreOpen}
onClose={handleStoreClose} onClose={handleStoreClose}
@ -142,9 +163,13 @@ function App() {
upgrades={upgrades} upgrades={upgrades}
onBuyUpgrade={handleBuyUpgrade} onBuyUpgrade={handleBuyUpgrade}
/> />
<div className="mb-8 w-full max-w-5xl overflow-x-auto whitespace-nowrap flex flex-row items-center space-x-4 scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-100"> <div ref={tierRowRef} className="mb-8 w-full max-w-5xl overflow-x-auto whitespace-nowrap flex flex-row items-center space-x-4 scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-100">
{TIERS.map((tier) => ( {TIERS.map((tier, idx) => (
<div key={tier.name} className="inline-block min-w-[8rem]"> <div
key={tier.name}
className="inline-block min-w-[8rem]"
ref={idx === highestUnlockedIndex ? highestUnlockedRef : undefined}
>
<TierCard <TierCard
tier={tier} tier={tier}
unlocked={unlockedTiers.includes(tier.name)} unlocked={unlockedTiers.includes(tier.name)}

View file

@ -19,6 +19,10 @@ type CardProps = {
}; };
const Card: React.FC<CardProps> = ({ card, setCard, setMoney, setGubblePoints, upgrades }) => { const Card: React.FC<CardProps> = ({ card, setCard, setMoney, setGubblePoints, upgrades }) => {
const formatter = Intl.NumberFormat(
'en',
{ 'notation': 'compact' }
)
return ( return (
<div className="bg-white rounded-lg shadow p-6 flex flex-col items-center mb-6 w-full max-w-md"> <div className="bg-white rounded-lg shadow p-6 flex flex-col items-center mb-6 w-full max-w-md">
<div className="mb-2 text-lg font-semibold text-gray-700">{card.tier?.name} Scratch Card</div> <div className="mb-2 text-lg font-semibold text-gray-700">{card.tier?.name} Scratch Card</div>
@ -70,7 +74,7 @@ const Card: React.FC<CardProps> = ({ card, setCard, setMoney, setGubblePoints, u
}} }}
> >
{field.scratched ? ( {field.scratched ? (
<span>{field.value}{field.won ? <span className="block text-xs font-normal text-green-700">+${field.won}</span> : ''}</span> <span>{field.value}{field.won ? <span className="block text-xs font-normal text-green-700">+${formatter.format(field.won)}</span> : ''}</span>
) : ( ) : (
<span className="text-3xl select-none">?</span> <span className="text-3xl select-none">?</span>
)} )}