Files
qtalker---/frontend/src/components/EntityCard.tsx
Indiana f0ee7dbdab refactor: use TelemetryReadout and EntityCard components
Both were built and tested but never wired in — SeancePage.tsx was
reimplementing the same markup inline. Removes the duplication.
2026-07-21 03:45:11 +00:00

105 lines
3.4 KiB
TypeScript

import { useTranslation } from 'react-i18next'
import type { SpiritEntity } from '../lib/types'
export type EntityCardProps = {
entity: SpiritEntity | null
isNew: boolean
/** Whether the collapsible dossier body is expanded. */
open: boolean
/** Toggle the collapsed/expanded state (ignored while there's no entity). */
onToggleOpen: () => void
}
/**
* Occult dossier of the currently-summoned spirit, wax-seal vibe.
* Styling (`.spirit-dossier` / `.dossier-*` / `.rarity-badge` / …) lives in
* SeancePage.css alongside the rest of the side column.
*/
export function EntityCard({ entity, isNew, open, onToggleOpen }: EntityCardProps) {
const { t } = useTranslation()
if (!entity) {
return (
<section className="spirit-dossier empty">
<div className="dossier-body">
<p className="dossier-none">{t('seance.entity.none')}</p>
<p className="dossier-nonehint dim">{t('seance.entity.noneHint')}</p>
</div>
</section>
)
}
return (
<section className={`spirit-dossier ${open ? 'open' : 'closed'}`}>
<button type="button" className="dossier-header" onClick={onToggleOpen} aria-expanded={open}>
<span className="dossier-title">{t('seance.entity.title')}</span>
<span className="dossier-chevron" aria-hidden>
{open ? '▾' : '◂'}
</span>
</button>
{open && (
<div className="dossier-body">
<h3 className="dossier-name" style={{ color: `hsl(${entity.visual.hue} 85% 78%)` }}>
{entity.name}
</h3>
<p className="dossier-epithet">{entity.epithet}</p>
<div className="dossier-badges">
<span className={`rarity-badge rarity-${entity.rarity}`}>
{t(`seance.entity.rarity.${entity.rarity}`)}
</span>
{isNew ? (
<span className="dossier-badge badge-new">{t('seance.entity.isNew')}</span>
) : (
<span className="dossier-badge badge-known">
{t('seance.entity.known', { count: entity.contact_count })}
</span>
)}
</div>
<div className="dossier-section">
<h4>{t('seance.entity.persona')}</h4>
<p className="dossier-persona">{entity.persona}</p>
</div>
<div className="dossier-section">
<h4>{t('seance.entity.voice')}</h4>
<dl className="voice-params">
<div>
<dt>id</dt>
<dd>{entity.voice.voice_id}</dd>
</div>
<div>
<dt>pitch</dt>
<dd>{entity.voice.pitch.toFixed(2)}</dd>
</div>
<div>
<dt>rate</dt>
<dd>{entity.voice.rate.toFixed(2)}</dd>
</div>
<div>
<dt>noise</dt>
<dd>{entity.voice.noise.toFixed(2)}</dd>
</div>
<div>
<dt>echo</dt>
<dd>{entity.voice.echo.toFixed(2)}</dd>
</div>
</dl>
</div>
{entity.quotes.length > 0 && (
<div className="dossier-section">
<h4>{t('seance.entity.quotes')}</h4>
<ul className="dossier-quotes">
{entity.quotes.slice(0, 4).map((q, i) => (
<li key={i}>“{q}”</li>
))}
</ul>
</div>
)}
</div>
)}
</section>
)
}