Enter to submit.
This commit is contained in:
@@ -3,38 +3,6 @@ import { createPortal } from 'react-dom'
|
|||||||
import type { Card } from '../types'
|
import type { Card } from '../types'
|
||||||
import { artFor, artUrlFor } from '../petArt'
|
import { artFor, artUrlFor } from '../petArt'
|
||||||
|
|
||||||
// The suit's "hat" — the trade-in symbol printed on the real cards, tinted to
|
|
||||||
// the suit's colour. Drawn as a tiny sun-hat SVG so it scales crisply.
|
|
||||||
const SUIT_HAT: Record<string, { fill: string; band: string }> = {
|
|
||||||
red: { fill: '#e5563f', band: '#b62c1f' },
|
|
||||||
blue: { fill: '#4d8ce3', band: '#2b5fb0' },
|
|
||||||
yellow: { fill: '#f2c24d', band: '#d9992f' },
|
|
||||||
}
|
|
||||||
|
|
||||||
function SuitHat({ suit }: { suit: string }) {
|
|
||||||
const c = SUIT_HAT[suit] ?? SUIT_HAT.yellow
|
|
||||||
return (
|
|
||||||
<svg className="card-hat" viewBox="0 0 28 18" aria-hidden>
|
|
||||||
<path
|
|
||||||
d="M7 12.5 Q7 3.5 14 3.5 Q21 3.5 21 12.5 Z"
|
|
||||||
fill={c.fill}
|
|
||||||
stroke="rgba(50,25,8,.45)"
|
|
||||||
strokeWidth="1"
|
|
||||||
/>
|
|
||||||
<path d="M7.4 11 Q14 13 20.6 11 L20.6 12.6 Q14 14.6 7.4 12.6 Z" fill={c.band} />
|
|
||||||
<ellipse
|
|
||||||
cx="14"
|
|
||||||
cy="13.4"
|
|
||||||
rx="12.6"
|
|
||||||
ry="3.7"
|
|
||||||
fill={c.fill}
|
|
||||||
stroke="rgba(50,25,8,.45)"
|
|
||||||
strokeWidth="1"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The tier shown as a die face (1–6 pips), like the real cards' corner marker.
|
// The tier shown as a die face (1–6 pips), like the real cards' corner marker.
|
||||||
const DIE_PIPS: Record<number, [number, number][]> = {
|
const DIE_PIPS: Record<number, [number, number][]> = {
|
||||||
1: [[15, 15]],
|
1: [[15, 15]],
|
||||||
@@ -57,21 +25,25 @@ function TierDie({ tier }: { tier: number }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// useFitText shrinks the effect text until it fits its band, so long abilities
|
// useFitText shrinks text until it fits its box, so long abilities (and the
|
||||||
// (and the smaller battle cards) render in full instead of being clipped by
|
// smaller battle cards) render in full instead of being clipped by the fixed
|
||||||
// the fixed card height. It never grows past the CSS size, only shrinks toward
|
// card height, and long single-line names shrink instead of wrapping. It
|
||||||
// a small floor, and re-fits when the band resizes (breakpoints, battle mode).
|
// checks both axes — the effect band wraps and overflows vertically, the
|
||||||
|
// (nowrap) name row overflows horizontally. It never grows past the CSS size,
|
||||||
|
// only shrinks toward a small floor, and re-fits when the box resizes
|
||||||
|
// (breakpoints, battle mode).
|
||||||
function useFitText(dep: unknown) {
|
function useFitText(dep: unknown) {
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const el = ref.current
|
const el = ref.current
|
||||||
if (!el) return
|
if (!el) return
|
||||||
|
const overflows = () => el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth
|
||||||
const fit = () => {
|
const fit = () => {
|
||||||
el.style.fontSize = ''
|
el.style.fontSize = ''
|
||||||
let size = parseFloat(getComputedStyle(el).fontSize)
|
let size = parseFloat(getComputedStyle(el).fontSize)
|
||||||
const min = 6
|
const min = 6
|
||||||
// Guard the loop; each step is 0.5px so ~40 covers any realistic band.
|
// Guard the loop; each step is 0.5px so ~40 covers any realistic box.
|
||||||
for (let i = 0; i < 40 && el.scrollHeight > el.clientHeight && size > min; i++) {
|
for (let i = 0; i < 40 && overflows() && size > min; i++) {
|
||||||
size = Math.max(min, size - 0.5)
|
size = Math.max(min, size - 0.5)
|
||||||
el.style.fontSize = `${size}px`
|
el.style.fontSize = `${size}px`
|
||||||
}
|
}
|
||||||
@@ -284,7 +256,7 @@ export function CardView({
|
|||||||
<div className="card-panel">
|
<div className="card-panel">
|
||||||
<div className="card-panel-head">
|
<div className="card-panel-head">
|
||||||
{card.suit ? (
|
{card.suit ? (
|
||||||
<SuitHat suit={card.suit} />
|
<span className={`suit-dot card-suit suit-${card.suit}`} aria-hidden />
|
||||||
) : (
|
) : (
|
||||||
<span className="card-panel-slot" aria-hidden />
|
<span className="card-panel-slot" aria-hidden />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ export function Home({ onSession }: { onSession: (s: Session) => void }) {
|
|||||||
maxLength={20}
|
maxLength={20}
|
||||||
placeholder="Enter your name"
|
placeholder="Enter your name"
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && !busy) run(() => createGame(name))
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@@ -65,6 +68,9 @@ export function Home({ onSession }: { onSession: (s: Session) => void }) {
|
|||||||
maxLength={5}
|
maxLength={5}
|
||||||
placeholder="CODE"
|
placeholder="CODE"
|
||||||
onChange={(e) => setCode(e.target.value.toUpperCase())}
|
onChange={(e) => setCode(e.target.value.toUpperCase())}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' && !busy && code.length === 5) run(() => joinGame(code, name))
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
|
|||||||
+14
-13
@@ -1161,10 +1161,11 @@ h3 {
|
|||||||
padding-bottom: 2px;
|
padding-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-hat {
|
/* The suit marker: a plain enamel dot (see .suit-dot), sized to sit opposite
|
||||||
width: 20px;
|
the tier die in the name row. */
|
||||||
height: 13px;
|
.card-suit {
|
||||||
display: block;
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-die {
|
.card-die {
|
||||||
@@ -1179,9 +1180,9 @@ h3 {
|
|||||||
width: 15px;
|
width: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-lg .card-hat {
|
.card-lg .card-suit {
|
||||||
width: 23px;
|
width: 16px;
|
||||||
height: 15px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-lg .card-die,
|
.card-lg .card-die,
|
||||||
@@ -1190,9 +1191,9 @@ h3 {
|
|||||||
height: 17px;
|
height: 17px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-sm .card-hat {
|
.card-sm .card-suit {
|
||||||
width: 16px;
|
width: 11px;
|
||||||
height: 10px;
|
height: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-sm .card-die,
|
.card-sm .card-die,
|
||||||
@@ -1208,9 +1209,9 @@ h3 {
|
|||||||
color: var(--cocoa);
|
color: var(--cocoa);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow-wrap: break-word;
|
/* Keep the name to a single line, like the real cards; useFitText shrinks
|
||||||
/* Cap the name at ~two lines; useFitText shrinks longer names to fit. */
|
longer names to fit the row instead of letting them wrap. */
|
||||||
max-height: 2.1em;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user