|
|
|
@ -38,6 +38,9 @@ interface BuffButtonProps {
|
|
|
|
const LONG_PRESS_MS = 400;
|
|
|
|
const LONG_PRESS_MS = 400;
|
|
|
|
const TOOLTIP_AUTO_HIDE_MS = 2500;
|
|
|
|
const TOOLTIP_AUTO_HIDE_MS = 2500;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** When false, the green refill CTA in the hover tooltip is hidden (purchase still reachable from tap flow if any). */
|
|
|
|
|
|
|
|
const SHOW_BUFF_REFILL_BUTTON_IN_TOOLTIP = false;
|
|
|
|
|
|
|
|
|
|
|
|
const tooltipStyle: CSSProperties = {
|
|
|
|
const tooltipStyle: CSSProperties = {
|
|
|
|
position: 'absolute',
|
|
|
|
position: 'absolute',
|
|
|
|
bottom: '110%',
|
|
|
|
bottom: '110%',
|
|
|
|
@ -239,27 +242,42 @@ function BuffButton({ buff, meta, charge, maxCharges, onActivate, onRefill, nowM
|
|
|
|
onActivate();
|
|
|
|
onActivate();
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const style: CSSProperties = {
|
|
|
|
const dimmedFaceOpacity = isDisabled ? (isOutOfCharges && !isOnCooldown ? 0.3 : 0.55) : 1;
|
|
|
|
...buttonBase,
|
|
|
|
|
|
|
|
|
|
|
|
const hitStyle: CSSProperties = {
|
|
|
|
|
|
|
|
position: 'relative',
|
|
|
|
width: 44,
|
|
|
|
width: 44,
|
|
|
|
height: 50,
|
|
|
|
height: 50,
|
|
|
|
|
|
|
|
padding: 0,
|
|
|
|
|
|
|
|
border: 'none',
|
|
|
|
|
|
|
|
background: 'transparent',
|
|
|
|
|
|
|
|
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
|
|
|
|
|
|
transform: pressed ? 'scale(0.94)' : 'scale(1)',
|
|
|
|
|
|
|
|
transition: 'transform 80ms ease',
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const activatorFaceStyle: CSSProperties = {
|
|
|
|
|
|
|
|
...buttonBase,
|
|
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
|
|
inset: 0,
|
|
|
|
|
|
|
|
width: '100%',
|
|
|
|
|
|
|
|
height: '100%',
|
|
|
|
borderRadius: 10,
|
|
|
|
borderRadius: 10,
|
|
|
|
borderColor: isActive ? meta.color : pressed ? '#fff' : 'rgba(255,255,255,0.2)',
|
|
|
|
borderColor: isActive ? meta.color : pressed ? '#fff' : 'rgba(255,255,255,0.2)',
|
|
|
|
opacity: isDisabled ? (isOutOfCharges && !isOnCooldown ? 0.3 : 0.55) : 1,
|
|
|
|
|
|
|
|
boxShadow: isActive
|
|
|
|
boxShadow: isActive
|
|
|
|
? `0 0 12px ${meta.color}`
|
|
|
|
? `0 0 12px ${meta.color}`
|
|
|
|
: pressed
|
|
|
|
: pressed
|
|
|
|
? `0 0 10px rgba(255,255,255,0.5), inset 0 0 12px ${meta.color}66`
|
|
|
|
? `0 0 10px rgba(255,255,255,0.5), inset 0 0 12px ${meta.color}66`
|
|
|
|
: 'none',
|
|
|
|
: 'none',
|
|
|
|
transform: pressed ? 'scale(0.94)' : 'scale(1)',
|
|
|
|
opacity: dimmedFaceOpacity,
|
|
|
|
transition: 'transform 80ms ease, box-shadow 80ms ease, border-color 80ms ease, opacity 150ms ease',
|
|
|
|
transition: 'opacity 150ms ease, box-shadow 80ms ease, border-color 80ms ease',
|
|
|
|
cursor: isDisabled ? 'not-allowed' : 'pointer',
|
|
|
|
pointerEvents: 'none',
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<button
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
type="button"
|
|
|
|
style={style}
|
|
|
|
style={hitStyle}
|
|
|
|
onClick={handleClick}
|
|
|
|
onClick={handleClick}
|
|
|
|
onTouchStart={handleTouchStart}
|
|
|
|
onTouchStart={handleTouchStart}
|
|
|
|
onTouchEnd={handleTouchEnd}
|
|
|
|
onTouchEnd={handleTouchEnd}
|
|
|
|
@ -269,59 +287,62 @@ function BuffButton({ buff, meta, charge, maxCharges, onActivate, onRefill, nowM
|
|
|
|
aria-disabled={isDisabled}
|
|
|
|
aria-disabled={isDisabled}
|
|
|
|
aria-label={`${meta.label}: ${meta.desc}`}
|
|
|
|
aria-label={`${meta.label}: ${meta.desc}`}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
{/* Inner wrapper clips cooldown overlay to button bounds */}
|
|
|
|
{/* Dim only the activator (chrome + icon); tooltip is a sibling so it stays full opacity. */}
|
|
|
|
<div
|
|
|
|
<div style={activatorFaceStyle}>
|
|
|
|
style={{
|
|
|
|
{/* Inner wrapper clips cooldown overlay to button bounds */}
|
|
|
|
position: 'absolute',
|
|
|
|
<div
|
|
|
|
inset: 0,
|
|
|
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
|
|
|
borderRadius: 8,
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<RadialCooldown progress={cooldownProgress} />
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<span style={{ fontSize: 18, lineHeight: 1, position: 'relative' }}>{meta.icon}</span>
|
|
|
|
|
|
|
|
<span style={{ fontSize: 8, color: '#ccc', marginTop: 1, position: 'relative' }}>{meta.label}</span>
|
|
|
|
|
|
|
|
{isActive && !isOnCooldown && (
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
|
|
bottom: 2,
|
|
|
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
|
|
|
fontWeight: 700,
|
|
|
|
|
|
|
|
color: meta.color,
|
|
|
|
|
|
|
|
textShadow: '0 1px 2px rgba(0,0,0,0.9)',
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{Math.ceil(remainingEffectMs / 1000)}s
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
{isOnCooldown && (
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
style={{
|
|
|
|
style={{
|
|
|
|
position: 'absolute',
|
|
|
|
position: 'absolute',
|
|
|
|
fontSize: 11,
|
|
|
|
inset: 0,
|
|
|
|
fontWeight: 700,
|
|
|
|
overflow: 'hidden',
|
|
|
|
color: '#fff',
|
|
|
|
borderRadius: 8,
|
|
|
|
textShadow: '0 1px 2px rgba(0,0,0,0.8)',
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{Math.ceil(buff.cooldownRemainingMs / 1000)}s
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Charge badge */}
|
|
|
|
|
|
|
|
{hasChargeData && (
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
...chargeBadgeBase,
|
|
|
|
|
|
|
|
backgroundColor: isOutOfCharges ? '#aa2222' : 'rgba(30, 90, 180, 0.9)',
|
|
|
|
|
|
|
|
color: isOutOfCharges ? '#ffaaaa' : '#fff',
|
|
|
|
|
|
|
|
}}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
{remaining}
|
|
|
|
<RadialCooldown progress={cooldownProgress} />
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
<span style={{ fontSize: 18, lineHeight: 1, position: 'relative' }}>{meta.icon}</span>
|
|
|
|
|
|
|
|
<span style={{ fontSize: 8, color: '#ccc', marginTop: 1, position: 'relative' }}>{meta.label}</span>
|
|
|
|
|
|
|
|
{isActive && !isOnCooldown && (
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
|
|
bottom: 2,
|
|
|
|
|
|
|
|
fontSize: 9,
|
|
|
|
|
|
|
|
fontWeight: 700,
|
|
|
|
|
|
|
|
color: meta.color,
|
|
|
|
|
|
|
|
textShadow: '0 1px 2px rgba(0,0,0,0.9)',
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{Math.ceil(remainingEffectMs / 1000)}s
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
{isOnCooldown && (
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
|
|
|
fontSize: 11,
|
|
|
|
|
|
|
|
fontWeight: 700,
|
|
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
|
|
textShadow: '0 1px 2px rgba(0,0,0,0.8)',
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{Math.ceil(buff.cooldownRemainingMs / 1000)}s
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* Charge badge */}
|
|
|
|
|
|
|
|
{hasChargeData && (
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
...chargeBadgeBase,
|
|
|
|
|
|
|
|
backgroundColor: isOutOfCharges ? '#aa2222' : 'rgba(30, 90, 180, 0.9)',
|
|
|
|
|
|
|
|
color: isOutOfCharges ? '#ffaaaa' : '#fff',
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
{remaining}
|
|
|
|
|
|
|
|
</span>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{showTooltip && (
|
|
|
|
{showTooltip && (
|
|
|
|
<div style={tooltipStyle}>
|
|
|
|
<div style={tooltipStyle}>
|
|
|
|
@ -344,7 +365,7 @@ function BuffButton({ buff, meta, charge, maxCharges, onActivate, onRefill, nowM
|
|
|
|
{tr.refillsAt} {formatTimeHHMM(charge.periodEnd)}
|
|
|
|
{tr.refillsAt} {formatTimeHHMM(charge.periodEnd)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
{isOutOfCharges && onRefill && (
|
|
|
|
{SHOW_BUFF_REFILL_BUTTON_IN_TOOLTIP && isOutOfCharges && onRefill && (
|
|
|
|
<button
|
|
|
|
<button
|
|
|
|
type="button"
|
|
|
|
type="button"
|
|
|
|
onClick={(e) => {
|
|
|
|
onClick={(e) => {
|
|
|
|
|