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.
This commit is contained in:
@@ -1,79 +1,104 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { SpiritEntity } from '../lib/types'
|
||||
import { GhostGlyph } from './GhostGlyph'
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const RARITY_CLASS: Record<string, string> = {
|
||||
common: 'rarity-common',
|
||||
uncommon: 'rarity-uncommon',
|
||||
rare: 'rarity-rare',
|
||||
mythic: 'rarity-mythic',
|
||||
}
|
||||
|
||||
/** Occult dossier of the currently-summoned spirit, wax-seal vibe. */
|
||||
export function EntityCard({ entity, isNew }: EntityCardProps) {
|
||||
/**
|
||||
* 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 (
|
||||
<aside className="entity-card empty">
|
||||
<h3 className="ec-title">{t('seance.entity.title')}</h3>
|
||||
<div className="ec-none">
|
||||
<div className="ec-none-sigil">⟁</div>
|
||||
<p>{t('seance.entity.none')}</p>
|
||||
<p className="dim">{t('seance.entity.noneHint')}</p>
|
||||
<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>
|
||||
</aside>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<aside className="entity-card">
|
||||
<div className="ec-wax" aria-hidden="true">
|
||||
{entity.name.slice(0, 1).toUpperCase()}
|
||||
</div>
|
||||
<h3 className="ec-title">{t('seance.entity.title')}</h3>
|
||||
<div className="ec-head">
|
||||
<GhostGlyph hue={entity.visual.hue} form={entity.visual.form} size={64} />
|
||||
<div>
|
||||
<div className="ec-name glitch-hover">{entity.name}</div>
|
||||
<div className="ec-epithet">{entity.epithet}</div>
|
||||
<span className={`rarity-badge ${RARITY_CLASS[entity.rarity] ?? ''}`}>
|
||||
{t(`seance.entity.rarity.${entity.rarity}`)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="ec-contact">
|
||||
{isNew ? t('seance.entity.isNew') : t('seance.entity.known', { count: entity.contact_count })}
|
||||
</div>
|
||||
<div className="ec-section">
|
||||
<h4>{t('seance.entity.persona')}</h4>
|
||||
<p className="ec-persona">{entity.persona}</p>
|
||||
</div>
|
||||
{entity.quotes.length > 0 && (
|
||||
<div className="ec-section">
|
||||
<h4>{t('seance.entity.quotes')}</h4>
|
||||
<ul className="ec-quotes">
|
||||
{entity.quotes.slice(0, 4).map((q, i) => (
|
||||
<li key={i}>“{q}”</li>
|
||||
))}
|
||||
</ul>
|
||||
<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>
|
||||
)}
|
||||
<div className="ec-section">
|
||||
<h4>{t('seance.entity.voice')}</h4>
|
||||
<div className="ec-voice">
|
||||
<span>id {entity.voice.voice_id}</span>
|
||||
<span>pitch {entity.voice.pitch.toFixed(2)}</span>
|
||||
<span>rate {entity.voice.rate.toFixed(2)}</span>
|
||||
<span>noise {entity.voice.noise.toFixed(2)}</span>
|
||||
<span>echo {entity.voice.echo.toFixed(2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user