Fix GameTooltip blocking issue and translate Found string

This commit is contained in:
Joan
2026-02-06 11:23:32 +01:00
parent 173d6c9117
commit 539377e63d
17 changed files with 700 additions and 222 deletions

View File

@@ -792,6 +792,8 @@ html {
line-height: 1;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
display: block;
/* Ensure consistent vertical centering regardless of cost */
margin-bottom: 0;
}
.compass-cost {
@@ -801,7 +803,12 @@ html {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.8);
display: block;
line-height: 1;
margin-top: 0.25rem;
/* Use absolute positioning to prevent layout shifts */
position: absolute;
bottom: 8px;
left: 0;
right: 0;
margin: 0;
}
.compass-btn:hover:not(:disabled) {
@@ -855,6 +862,81 @@ html {
}
}
.compass-center {
width: 80px;
height: 80px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
.compass-center-btn {
width: 100%;
height: 100%;
border-radius: 50%;
border: 2px solid var(--game-color-primary);
background: radial-gradient(circle, rgba(225, 29, 72, 0.2) 0%, rgba(20, 20, 20, 0.8) 100%);
color: #fff;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
transition: all 0.2s;
box-shadow: 0 0 15px rgba(225, 29, 72, 0.2);
padding: 0.25rem;
z-index: 2;
}
.compass-center-btn:hover:not(:disabled) {
transform: scale(1.05);
box-shadow: 0 0 20px rgba(225, 29, 72, 0.4);
background: radial-gradient(circle, rgba(225, 29, 72, 0.3) 0%, rgba(30, 30, 30, 0.9) 100%);
}
.compass-center-btn:active:not(:disabled) {
transform: scale(0.95);
}
.compass-center-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
filter: grayscale(100%);
}
.center-btn-label {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
line-height: 1.1;
text-align: center;
}
.center-btn-icon {
font-size: 1.5rem;
margin-bottom: 2px;
}
/* Header Cooldown State */
.movement-controls h3.cooldown-active {
color: var(--game-color-warning);
animation: pulse-text 2s infinite;
}
@keyframes pulse-text {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* Cooldown indicator */
.cooldown-indicator {
text-align: center;
@@ -1008,6 +1090,7 @@ body.no-scroll {
}
.interact-btn {
width: 100%;
padding: 0.5rem 1rem;
border: none;
background: rgba(255, 193, 7, 0.3);
@@ -1896,6 +1979,12 @@ body.no-scroll {
margin: auto;
}
/* Remove .large variation to enforce uniformity */
.equipment-slot.large {
min-width: 0;
grid-column: auto;
}
.equipment-slot {
background: rgba(0, 0, 0, 0.5);
border: 2px solid rgba(255, 255, 255, 0.2);
@@ -1908,9 +1997,9 @@ body.no-scroll {
/* Changed from center to space-between */
gap: 0.25rem;
/* Fixed dimensions for consistent sizing */
min-height: 100px;
min-width: 80px;
max-width: 100%;
height: 100px;
width: 100%;
box-sizing: border-box;
transition: all 0.2s;
cursor: pointer;
overflow: visible;
@@ -1942,10 +2031,6 @@ body.no-scroll {
}
.equipment-slot.large {
min-width: 150px;
}
.equipment-slot.empty {
border-color: rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.3);
@@ -4265,4 +4350,152 @@ body.no-scroll {
border-color: #9ca3af;
box-shadow: 0 0 15px rgba(75, 85, 99, 0.3);
transform: translateY(-2px);
}
/* Pickup Action Group (Split Button) */
.pickup-actions-group {
display: flex;
align-items: stretch;
position: relative;
border-radius: 6px;
overflow: visible;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.pickup-main-btn {
padding: 0.4rem 0.75rem;
background: linear-gradient(135deg, #48bb78, #38a169);
color: white;
border: none;
border-radius: 6px 0 0 6px;
font-weight: 600;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
display: flex;
align-items: center;
gap: 0.4rem;
}
.pickup-main-btn:hover {
background: linear-gradient(135deg, #38a169, #2f855a);
}
.pickup-toggle-btn {
padding: 0 0.4rem;
background: linear-gradient(135deg, #48bb78, #38a169);
border: none;
border-left: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 0 6px 6px 0;
color: white;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
}
.pickup-toggle-btn:hover {
background: linear-gradient(135deg, #38a169, #2f855a);
}
.pickup-dropdown {
position: absolute;
bottom: 100%;
right: 0;
background: var(--game-bg-panel);
border: 1px solid var(--game-border-color);
border-radius: 6px;
display: flex;
flex-direction: column;
min-width: 120px;
overflow: hidden;
z-index: 100;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
animation: fadeIn 0.1s ease-out;
margin-bottom: 4px;
}
.pickup-option {
padding: 0.6rem 1rem;
background: transparent;
border: none;
color: var(--game-text-primary);
text-align: left;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.2s;
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}
.pickup-option:last-child {
border-bottom: none;
}
.pickup-option:hover {
background: rgba(72, 187, 120, 0.2);
color: #48bb78;
}
/* Single button variant (no split) */
.pickup-btn-single {
padding: 0.4rem 1rem;
background: linear-gradient(135deg, #48bb78, #38a169);
color: white;
border: none;
border-radius: 6px;
font-weight: 600;
font-size: 0.85rem;
cursor: pointer;
transition: all 0.2s;
}
.pickup-btn-single:hover {
background: linear-gradient(135deg, #38a169, #2f855a);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(56, 161, 105, 0.3);
}
/* Pickup Action Group (Row of Buttons) */
.pickup-actions-group {
display: flex;
gap: 2px;
background: rgba(0, 0, 0, 0.2);
padding: 2px;
border-radius: 6px;
border: 1px solid rgba(72, 187, 120, 0.4);
/* Green border */
align-items: center;
}
.action-btn.pickup {
background: transparent;
color: #48bb78;
/* Green text */
border: none;
padding: 0.3rem 0.6rem;
font-size: 0.75rem;
font-weight: 600;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.action-btn.pickup:hover {
background: rgba(72, 187, 120, 0.2);
transform: translateY(-1px);
}
.action-btn.pickup.single {
/* Special styling if we want the single button to look distinct,
but keeping it consistent with group for now */
border: 1px solid rgba(72, 187, 120, 0.4);
background: rgba(72, 187, 120, 0.1);
}
.action-btn.pickup.single:hover {
background: rgba(72, 187, 120, 0.2);
}

View File

@@ -49,11 +49,20 @@ export const GameProgressBar: React.FC<GameProgressBarProps> = ({
case 'enemy_health': return 'linear-gradient(90deg, #ef4444, #b91c1c)'; // Red
case 'stamina': return 'linear-gradient(90deg, #eab308, #ca8a04)';
case 'xp': return 'linear-gradient(90deg, #8b5cf6, #7c3aed)'; // Purple for XP?
// Green if empty, yellow if half, red if full
case 'weight':
if (percentage < 65) return 'linear-gradient(90deg, #10b981, #059669)'; // Green
if (percentage < 85) return 'linear-gradient(90deg, #eab308, #ca8a04)'; // Yellow
return 'linear-gradient(90deg, #ef4444, #b91c1c)'; // Red
case 'volume':
if (percentage < 65) return 'linear-gradient(90deg, #10b981, #059669)'; // Green
if (percentage < 85) return 'linear-gradient(90deg, #eab308, #ca8a04)'; // Yellow
return 'linear-gradient(90deg, #ef4444, #b91c1c)'; // Red
case 'durability':
if (percentage < 15) return 'linear-gradient(90deg, #ef4444, #b91c1c)'; // Red
if (percentage < 50) return 'linear-gradient(90deg, #eab308, #ca8a04)'; // Yellow
return 'linear-gradient(90deg, #10b981, #059669)'; // Green
default: return undefined;
default: return 'linear-gradient(90deg, #10b981, #059669)';
}
};

View File

@@ -228,6 +228,7 @@
flex-direction: column;
padding: 1.5rem;
overflow: hidden;
background: var(--game-bg-app);
}
.inventory-search-bar {
@@ -240,6 +241,8 @@
border-radius: var(--game-radius-md);
margin-bottom: 1.5rem;
color: var(--game-text-primary);
width: 100%;
box-sizing: border-box;
}
.inventory-search-bar input {

View File

@@ -339,7 +339,7 @@ function InventoryModal({
<button className="action-btn drop" onClick={() => {
playSfx('/audio/sfx/drop.wav')
onDropItem(item.item_id, item.id, item.quantity)
}}>{t('game.dropAll')}</button>
}}>{t('common.all')}</button>
)}
</div>
</div>

View File

@@ -1,4 +1,3 @@
import type { Location, PlayerState, CombatState, Profile, WorkbenchTab } from './types'
import { useTranslation } from 'react-i18next'
import { useAudio } from '../../contexts/AudioContext'
@@ -86,6 +85,7 @@ function LocationView({
}: LocationViewProps) {
const { t } = useTranslation()
const { playSfx } = useAudio()
return (
<div className="location-view">
<div className="location-info">
@@ -312,28 +312,9 @@ function LocationView({
<div className="entity-section items-section">
<h3>{t('location.itemsOnGround')}</h3>
<div className="entity-list">
{location.items.map((item: any, i: number) => (
<div key={i} className="entity-card item-card">
{item.image_path ? (
<img
src={getAssetPath(item.image_path)}
alt={getTranslatedText(item.name)}
className="entity-icon"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
const icon = (e.target as HTMLImageElement).nextElementSibling;
if (icon) icon.classList.remove('hidden');
}}
/>
) : null}
<span className={`entity-icon ${item.image_path ? 'hidden' : ''}`}>{item.emoji || '📦'}</span>
<div className="entity-info">
<div className={`entity-name ${item.tier ? `tier-${item.tier}` : ''}`}>
{getTranslatedText(item.name) || 'Unknown Item'}
</div>
{item.quantity > 1 && <div className="entity-quantity">×{item.quantity}</div>}
</div>
<div className="item-info-btn-container">
{location.items.map((item: any, i: number) => {
return (
<div key={i} className="entity-card item-card">
<GameTooltip content={
<div className="item-info-tooltip-content">
{item.description && <div className="item-tooltip-desc">{getTranslatedText(item.description)}</div>}
@@ -368,50 +349,79 @@ function LocationView({
)}
</div>
}>
<button className="entity-action-btn info">{t('common.info')}</button>
</GameTooltip>
</div>
{item.quantity === 1 ? (
<button
className="entity-action-btn pickup"
onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, 1)
}}
>
{t('common.pickUp')}
</button>
) : (
<div className="item-pickup-btn-container">
<button className="entity-action-btn pickup">{t('common.pickUp')} </button>
<div className="item-pickup-menu">
<button className="item-pickup-option" onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, 1)
}}>{t('common.pickUp')} 1</button>
{item.quantity >= 5 && (
<button className="item-pickup-option" onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, 5)
}}>{t('common.pickUp')} 5</button>
)}
{item.quantity >= 10 && (
<button className="item-pickup-option" onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, 10)
}}>{t('common.pickUp')} 10</button>
)}
<button className="item-pickup-option" onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, item.quantity)
}}>
{t('common.pickUpAll')} ({item.quantity})
</button>
<div className="entity-content-wrapper" style={{ display: 'flex', alignItems: 'center', gap: '1rem', flex: 1, cursor: 'help' }}>
{item.image_path ? (
<img
src={getAssetPath(item.image_path)}
alt={getTranslatedText(item.name)}
className="entity-icon"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
const icon = (e.target as HTMLImageElement).nextElementSibling;
if (icon) icon.classList.remove('hidden');
}}
/>
) : null}
<span className={`entity-icon ${item.image_path ? 'hidden' : ''}`}>{item.emoji || '📦'}</span>
<div className="entity-info">
<div className={`entity-name ${item.tier ? `tier-${item.tier}` : ''}`}>
{getTranslatedText(item.name) || 'Unknown Item'}
</div>
{item.quantity > 1 && <div className="entity-quantity">×{item.quantity}</div>}
</div>
</div>
</GameTooltip>
<div className="item-actions-row">
{item.quantity === 1 ? (
<button
className="action-btn pickup single"
onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, 1)
}}
>
{t('common.pickUp')}
</button>
) : (
<div className="pickup-actions-group">
<button className="action-btn pickup" onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, 1)
}}>
x1
</button>
{item.quantity >= 5 && (
<button className="action-btn pickup" onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, 5)
}}>
x5
</button>
)}
{item.quantity >= 10 && (
<button className="action-btn pickup" onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, 10)
}}>
x10
</button>
)}
<button className="action-btn pickup" onClick={() => {
playSfx('/audio/sfx/pickup.wav')
onPickup(item.id, item.quantity)
}}>
{t('common.all') || 'All'}
</button>
</div>
)}
</div>
)}
</div>
))}
</div>
);
})}
</div>
</div>
)}

View File

@@ -104,10 +104,81 @@ function MovementControls({
)
}
// Helper function to render center button (compass center or action)
const renderCenterButton = () => {
// Check for special directions that should go in the center
const insideDir = location.directions.includes('inside') ? 'inside' : null;
const outsideDir = location.directions.includes('outside') ? 'outside' : null;
const enterDir = location.directions.includes('enter') ? 'enter' : null;
const exitDir = location.directions.includes('exit') ? 'exit' : null;
// Priority: Inside/Outside (usually mutually exclusive) > Enter/Exit
const centerDirection = insideDir || outsideDir || enterDir || exitDir;
if (!centerDirection) {
// Default Compass Icon
return (
<div className="compass-center">
<div className="compass-icon">🧭</div>
</div>
);
}
// Action Button Logic
const stamina = getStaminaCost(centerDirection);
const destination = getDestinationName(centerDirection);
const insufficientStamina = profile ? profile.stamina < stamina : false;
const disabled = !!combatState || movementCooldown > 0 || insufficientStamina || (profile?.is_dead ?? false);
// Labels and Icons
let label = t('directions.' + centerDirection);
let icon = '🚪';
if (centerDirection === 'inside') icon = '🏠';
if (centerDirection === 'outside') icon = '🌳';
const tooltipText = profile?.is_dead ? t('messages.youAreDead') :
movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) :
combatState ? t('messages.cannotTravelCombat') :
insufficientStamina ? t('messages.notEnoughStamina', { need: stamina, have: profile?.stamina ?? 0 }) :
(
<div className="movement-tooltip">
<div className="tooltip-title">{label}</div>
<div className="tooltip-stat"> {t('game.stamina')}: {stamina}</div>
{destination && <div className="tooltip-desc">{destination}</div>}
</div>
);
return (
<div className="compass-center">
<GameTooltip content={tooltipText}>
<button
className="compass-center-btn"
onClick={() => onMove(centerDirection)}
disabled={disabled}
>
<span className="center-btn-icon" key={`icon-${centerDirection}`}>{icon}</span>
<span className="center-btn-label" key={`label-${centerDirection}`}>{label}</span>
{movementCooldown > 0 ? (
<span className="compass-cost" key="cost-timer">{movementCooldown}s</span>
) : (
<span className="compass-cost" key="cost-stamina">{stamina}</span>
)}
</button>
</GameTooltip>
</div>
);
};
return (
<>
<div className="movement-controls">
<h3>{t('game.travel')}</h3>
<h3 className={movementCooldown > 0 ? 'cooldown-active' : ''}>
{movementCooldown > 0 ? (
<span key="timer"> {t('messages.waitBeforeMovingSimple', { seconds: movementCooldown })}</span>
) : (
<span key="title">{t('game.travel')}</span>
)}
</h3>
<div className="compass-grid">
{/* Top row */}
{renderCompassButton('northwest', '↖️', 'nw')}
@@ -116,9 +187,7 @@ function MovementControls({
{/* Middle row */}
{renderCompassButton('west', '⬅️', 'w')}
<div className="compass-center">
<div className="compass-icon">🧭</div>
</div>
{renderCenterButton()}
{renderCompassButton('east', '➡️', 'e')}
{/* Bottom row */}
@@ -127,107 +196,43 @@ function MovementControls({
{renderCompassButton('southeast', '↘️', 'se')}
</div>
{/* Cooldown indicator */}
{movementCooldown > 0 && (
<div className="cooldown-indicator">
{t('messages.waitBeforeMovingSimple', { seconds: movementCooldown })}
{/* Special movements (Vertical only now, since Enter/Exit are in center) */}
{(location.directions.includes('up') || location.directions.includes('down')) && (
<div className="special-moves">
{location.directions.includes('up') && (
<GameTooltip content={movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) : combatState ? t('messages.cannotTravelCombat') : (
<div className="movement-tooltip">
<div className="tooltip-title">{t('directions.up')}</div>
<div className="tooltip-stat"> {t('game.stamina')}: {getStaminaCost('up')}</div>
</div>
)}>
<button
onClick={() => onMove('up')}
className="special-btn"
disabled={!!combatState || movementCooldown > 0}
>
{t('directions.up')} <span className="compass-cost">{movementCooldown > 0 ? `${movementCooldown}s` : `${getStaminaCost('up')}`}</span>
</button>
</GameTooltip>
)}
{location.directions.includes('down') && (
<GameTooltip content={movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) : combatState ? t('messages.cannotTravelCombat') : (
<div className="movement-tooltip">
<div className="tooltip-title">{t('directions.down')}</div>
<div className="tooltip-stat"> {t('game.stamina')}: {getStaminaCost('down')}</div>
</div>
)}>
<button
onClick={() => onMove('down')}
className="special-btn"
disabled={!!combatState || movementCooldown > 0}
>
{t('directions.down')} <span className="compass-cost">{movementCooldown > 0 ? `${movementCooldown}s` : `${getStaminaCost('down')}`}</span>
</button>
</GameTooltip>
)}
</div>
)}
{/* Special movements */}
<div className="special-moves">
{location.directions.includes('up') && (
<GameTooltip content={movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) : combatState ? t('messages.cannotTravelCombat') : (
<div className="movement-tooltip">
<div className="tooltip-title">{t('directions.up')}</div>
<div className="tooltip-stat"> {t('game.stamina')}: {getStaminaCost('up')}</div>
</div>
)}>
<button
onClick={() => onMove('up')}
className="special-btn"
disabled={!!combatState || movementCooldown > 0}
>
{t('directions.up')} <span className="compass-cost">{movementCooldown > 0 ? `${movementCooldown}s` : `${getStaminaCost('up')}`}</span>
</button>
</GameTooltip>
)}
{location.directions.includes('down') && (
<GameTooltip content={movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) : combatState ? t('messages.cannotTravelCombat') : (
<div className="movement-tooltip">
<div className="tooltip-title">{t('directions.down')}</div>
<div className="tooltip-stat"> {t('game.stamina')}: {getStaminaCost('down')}</div>
</div>
)}>
<button
onClick={() => onMove('down')}
className="special-btn"
disabled={!!combatState || movementCooldown > 0}
>
{t('directions.down')} <span className="compass-cost">{movementCooldown > 0 ? `${movementCooldown}s` : `${getStaminaCost('down')}`}</span>
</button>
</GameTooltip>
)}
{location.directions.includes('enter') && (
<GameTooltip content={movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) : combatState ? t('messages.cannotTravelCombat') : (
<div className="movement-tooltip">
<div className="tooltip-title">{t('directions.enter')}</div>
<div className="tooltip-stat"> {t('game.stamina')}: {getStaminaCost('enter')}</div>
</div>
)}>
<button
onClick={() => onMove('enter')}
className="special-btn"
disabled={!!combatState || movementCooldown > 0}
>
🚪 {t('directions.enter')} <span className="compass-cost">{movementCooldown > 0 ? `${movementCooldown}s` : `${getStaminaCost('enter')}`}</span>
</button>
</GameTooltip>
)}
{location.directions.includes('inside') && (
<GameTooltip content={movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) : combatState ? t('messages.cannotTravelCombat') : (
<div className="movement-tooltip">
<div className="tooltip-title">{t('directions.inside')}</div>
<div className="tooltip-stat"> {t('game.stamina')}: {getStaminaCost('inside')}</div>
</div>
)}>
<button
onClick={() => onMove('inside')}
className="special-btn"
disabled={!!combatState || movementCooldown > 0}
>
🚪 {t('directions.inside')} <span className="compass-cost">{movementCooldown > 0 ? `${movementCooldown}s` : `${getStaminaCost('inside')}`}</span>
</button>
</GameTooltip>
)}
{location.directions.includes('exit') && (
<GameTooltip content={movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) : combatState ? t('messages.cannotTravelCombat') : t('directions.exit')}>
<button
onClick={() => onMove('exit')}
className="special-btn"
disabled={!!combatState || movementCooldown > 0}
>
🚪 {t('directions.exit')}
</button>
</GameTooltip>
)}
{location.directions.includes('outside') && (
<GameTooltip content={movementCooldown > 0 ? t('messages.waitBeforeMoving', { seconds: movementCooldown }) : combatState ? t('messages.cannotTravelCombat') : (
<div className="movement-tooltip">
<div className="tooltip-title">{t('directions.outside')}</div>
<div className="tooltip-stat"> {t('game.stamina')}: {getStaminaCost('outside')}</div>
</div>
)}>
<button
onClick={() => onMove('outside')}
className="special-btn"
disabled={!!combatState || movementCooldown > 0}
>
🚪 {t('directions.outside')} <span className="compass-cost">{movementCooldown > 0 ? `${movementCooldown}s` : `${getStaminaCost('outside')}`}</span>
</button>
</GameTooltip>
)}
</div>
</div>
{/* Surroundings - outside movement controls */}

View File

@@ -6,6 +6,7 @@ import { getTranslatedText } from '../../utils/i18nUtils'
import InventoryModal from './InventoryModal'
import { GameProgressBar } from '../common/GameProgressBar'
import { GameTooltip } from '../common/GameTooltip'
import { useAudio } from '../../contexts/AudioContext'
interface PlayerSidebarProps {
playerState: PlayerState
@@ -40,6 +41,7 @@ function PlayerSidebar({
}: PlayerSidebarProps) {
const [showInventory, setShowInventory] = useState(false)
const { t } = useTranslation()
const { playSfx } = useAudio()
const renderEquipmentSlot = (slot: string, item: any, label: string) => {
// Construct the tooltip content if item exists
@@ -108,13 +110,13 @@ function PlayerSidebar({
) : label; // Show label if no item
return (
<GameTooltip content={tooltipContent}>
<div className={`equipment-slot game-slot ${item ? 'filled' : 'empty'}`}>
{item ? (
<>
<GameTooltip content={t('game.unequip')}>
<button className="equipment-unequip-btn game-btn game-btn-icon" onClick={(e) => { e.stopPropagation(); onUnequipItem(slot); }}></button>
</GameTooltip>
<div className={`equipment-slot game-slot ${item ? 'filled' : 'empty'}`}>
{item ? (
<>
<GameTooltip content={t('game.unequip')}>
<button className="equipment-unequip-btn game-btn game-btn-icon" onClick={(e) => { e.stopPropagation(); onUnequipItem(slot); playSfx('/audio/sfx/unequip.wav'); }}></button>
</GameTooltip>
<GameTooltip content={tooltipContent}>
<div className="equipment-item-content">
{item.image_path ? (
<img
@@ -138,19 +140,19 @@ function PlayerSidebar({
</div>
)}
</div>
</>
) : (
<>
<img
src={getAssetPath(`/images/placeholder/${slot}_placeholder.webp`)}
alt={label}
className="equipment-placeholder-img"
style={{ width: '100%', height: '100%', objectFit: 'contain', opacity: 0.5 }}
/>
</>
)}
</div>
</GameTooltip>
</GameTooltip>
</>
) : (
<GameTooltip content={label}>
<img
src={getAssetPath(`/images/placeholder/${slot}_placeholder.webp`)}
alt={label}
className="equipment-placeholder-img"
style={{ width: '100%', height: '100%', objectFit: 'contain', opacity: 0.5 }}
/>
</GameTooltip>
)}
</div>
)
}

View File

@@ -181,6 +181,8 @@
padding: 1rem;
border-bottom: 1px solid var(--game-border-color);
background: var(--game-bg-input);
width: 100%;
box-sizing: border-box;
/* Match search bar bg */
}

View File

@@ -0,0 +1,41 @@
/* Pickup Action Group (Row of Buttons) */
.pickup-actions-group {
display: flex;
gap: 2px;
background: rgba(0, 0, 0, 0.2);
padding: 2px;
border-radius: 6px;
border: 1px solid rgba(72, 187, 120, 0.4);
/* Green border */
align-items: center;
}
.action-btn.pickup {
background: transparent;
color: #48bb78;
/* Green text */
border: none;
padding: 0.3rem 0.6rem;
font-size: 0.75rem;
font-weight: 600;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
}
.action-btn.pickup:hover {
background: rgba(72, 187, 120, 0.2);
transform: translateY(-1px);
}
.action-btn.pickup.single {
/* Special styling if we want the single button to look distinct,
but keeping it consistent with group for now */
border: 1px solid rgba(72, 187, 120, 0.4);
background: rgba(72, 187, 120, 0.1);
}
.action-btn.pickup.single:hover {
background: rgba(72, 187, 120, 0.2);
}

View File

@@ -1,5 +1,6 @@
// useGameEngine - Core game state and logic hook
import { useState, useEffect, useRef, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import api from '../../../services/api'
import type {
PlayerState,
@@ -143,6 +144,7 @@ export function useGameEngine(
token: string | null,
_handleWebSocketMessage: (message: any) => Promise<void>
): [GameEngineState, GameEngineActions] {
const { t } = useTranslation()
// All state declarations
const [playerState, setPlayerState] = useState<PlayerState | null>(null)
const [location, setLocation] = useState<Location | null>(null)
@@ -843,7 +845,7 @@ export function useGameEngine(
const data = response.data
let msg = data.message
if (data.items_found && data.items_found.length > 0) {
msg += '\n\n📦 Found: ' + data.items_found.join(', ')
msg += '\n\n📦 ' + t('game.found') + ': ' + data.items_found.join(', ')
}
if (data.hp_change) {
msg += `\n❤ HP: ${data.hp_change > 0 ? '+' : ''}${data.hp_change}`

View File

@@ -21,7 +21,8 @@
"pickUpAll": "Pick Up All",
"qty": "Qty",
"enemy": "Enemy",
"you": "You"
"you": "You",
"all": "All"
},
"auth": {
"login": "Login",
@@ -63,7 +64,6 @@
"salvage": "♻️ Salvage",
"pickUp": "Pick Up",
"drop": "Drop",
"dropAll": "All",
"use": "Use",
"equip": "Equip",
"unequip": "Unequip",
@@ -90,7 +90,8 @@
"burning": "Burning",
"poisoned": "Poisoned"
},
"effectAlreadyActive": "Effect already active"
"effectAlreadyActive": "Effect already active",
"found": "Found"
},
"location": {
"recentActivity": "📜 Recent Activity",

View File

@@ -19,7 +19,8 @@
"fight": "Luchar",
"pickUp": "Recoger",
"pickUpAll": "Recoger Todo",
"qty": "Cant"
"qty": "Cant",
"all": "Todo"
},
"auth": {
"login": "Iniciar sesión",
@@ -61,7 +62,6 @@
"salvage": "♻️ Desguazar",
"pickUp": "Recoger",
"drop": "Soltar",
"dropAll": "Todo",
"use": "Usar",
"equip": "Equipar",
"unequip": "Desequipar",
@@ -88,7 +88,8 @@
"burning": "Quemadura",
"poisoned": "Envenenamiento"
},
"effectAlreadyActive": "Efecto ya activo"
"effectAlreadyActive": "Efecto ya activo",
"found": "Encontrado"
},
"location": {
"recentActivity": "📜 Actividad Reciente",

View File

@@ -172,4 +172,32 @@ img.emoji {
margin: 0 0.05em 0 0.1em;
vertical-align: -0.1em;
display: inline-block;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 12px;
height: 12px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.4);
}
::-webkit-scrollbar-thumb {
background-color: var(--game-border-active);
border: 3px solid transparent;
border-radius: 8px;
background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--game-text-secondary);
border: 2px solid transparent;
}
/* Firefox support */
* {
scrollbar-width: thin;
scrollbar-color: var(--game-border-active) rgba(0, 0, 0, 0.4);
}