Various UX improvements.

This commit is contained in:
Greyson Parrelli
2026-07-23 11:55:12 -04:00
parent 816ad13f7e
commit 547cd81a99
3 changed files with 103 additions and 15 deletions
+37 -3
View File
@@ -1,8 +1,35 @@
import { useState } from 'react'
import { useLayoutEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import type { Card } from '../types'
import { artFor } from '../petArt'
// useFitText shrinks the effect text until it fits its band, so long abilities
// (and the smaller battle cards) render in full instead of being clipped by
// the fixed card height. It never grows past the CSS size, only shrinks toward
// a small floor, and re-fits when the band resizes (breakpoints, battle mode).
function useFitText(dep: unknown) {
const ref = useRef<HTMLDivElement>(null)
useLayoutEffect(() => {
const el = ref.current
if (!el) return
const fit = () => {
el.style.fontSize = ''
let size = parseFloat(getComputedStyle(el).fontSize)
const min = 6
// Guard the loop; each step is 0.5px so ~40 covers any realistic band.
for (let i = 0; i < 40 && el.scrollHeight > el.clientHeight && size > min; i++) {
size = Math.max(min, size - 0.5)
el.style.fontSize = `${size}px`
}
}
fit()
const ro = new ResizeObserver(fit)
ro.observe(el)
return () => ro.disconnect()
}, [dep])
return ref
}
interface Props {
card: Card
size?: 'sm' | 'md' | 'lg'
@@ -39,6 +66,9 @@ export function CardView({
// preview copy itself opts out so it can't recurse.
const [hover, setHover] = useState<{ x: number; y: number } | null>(null)
const power = (card.power ?? 0) + bonus
// Shrink long ability text to fit the (fixed-height) card, re-fitting when
// the text or card size changes. Only the branch that renders attaches it.
const effectRef = useFitText(`${size}|${card.effectText ?? ''}|${card.food ?? ''}`)
const classes = [
'card',
`card-${size}`,
@@ -89,14 +119,18 @@ export function CardView({
<div className="card-name">{card.name}</div>
{card.kind === 'pet' ? (
<>
{card.effectText && <div className="card-effect">{card.effectText}</div>}
{card.effectText && (
<div className="card-effect" ref={effectRef}>
{card.effectText}
</div>
)}
<div className="card-bottom">
<span className={`card-power ${bonus > 0 ? 'is-buffed' : ''}`}>{power}</span>
{damage > 0 && !dead && <span className="card-damage">{damage}</span>}
</div>
</>
) : (
<div className="card-effect">
<div className="card-effect" ref={effectRef}>
{card.effectText ?? (card.food === 'apple' ? '+1 power (this battle)' : '')}
</div>
)}