Polish sprint 2/2: layouts, UX consistency, pf2e conditions
Layout: - CombatantRow: action controls (damage/AC/move/remove) grouped into one right-aligned wrapper so they wrap together instead of scattering on narrow widths. - top bar: the campaign switcher now shrinks/truncates instead of overflowing on tablet widths. - character sheet header: stat coins go full-width grid on mobile; the name truncates and scales down on small screens. UX consistency: - player Cast and resource Spend/Regain no longer fail silently — Cast shows the reason, resource −/+ disable at their bounds. - the AI encounter builder now catches load failures and reports them; level-up advisor shows the human error message, not the error-kind enum. - section headers standardized on the .smallcaps design token across 16 files (replacing an ad-hoc uppercase class). - player HP bar uses the shared Meter primitive. pf2e depth: condition-effects table gains drained, dazzled, enfeebled, fatigued. FeatCard: dropped a dead text-xl wrapper left from the emoji purge. Left as documented (functional, off-theme, refactor-risky): the native confirm/prompt in Settings cloud-conflict and the session-password flow. Gate: 293 unit + 35 e2e + 2 realtime; tsc + build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -82,6 +82,8 @@ function Assistant({ campaign }: { campaign: Campaign }) {
|
||||
await encountersRepo.save(enc);
|
||||
setActiveEncounter(enc.id);
|
||||
void navigate({ to: '/combat' });
|
||||
} catch {
|
||||
setMsg('Could not build an encounter — the monster data failed to load. Try again.');
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
@@ -96,7 +98,7 @@ function Assistant({ campaign }: { campaign: Campaign }) {
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<section>
|
||||
<h2 className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted">Encounter builder</h2>
|
||||
<h2 className="mb-2 smallcaps">Encounter builder</h2>
|
||||
<div className="rounded-lg border border-line bg-panel p-4">
|
||||
<p className="mb-3 text-sm text-muted">Build a balanced encounter for your party and jump into combat.</p>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
@@ -106,24 +108,24 @@ function Assistant({ campaign }: { campaign: Campaign }) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 className="mb-2 mt-6 text-xs font-semibold uppercase tracking-wide text-muted">Combat</h2>
|
||||
<h2 className="mb-2 mt-6 smallcaps">Combat</h2>
|
||||
<SuggestionList items={byCat('combat')} onAction={runAction} empty="No active combat." />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted">Party resources</h2>
|
||||
<h2 className="mb-2 smallcaps">Party resources</h2>
|
||||
<SuggestionList items={byCat('resources')} onAction={runAction} empty="The party looks ready." />
|
||||
|
||||
<h2 className="mb-2 mt-6 text-xs font-semibold uppercase tracking-wide text-muted">Session prep</h2>
|
||||
<h2 className="mb-2 mt-6 smallcaps">Session prep</h2>
|
||||
<SuggestionList items={byCat('planning')} onAction={runAction} empty="Nothing flagged." />
|
||||
|
||||
<h2 className="mb-2 mt-6 text-xs font-semibold uppercase tracking-wide text-muted">Session hooks</h2>
|
||||
<h2 className="mb-2 mt-6 smallcaps">Session hooks</h2>
|
||||
<SessionPrepCard campaign={campaign} characters={characters} notes={notes} quests={quests} />
|
||||
|
||||
<h2 className="mb-2 mt-6 text-xs font-semibold uppercase tracking-wide text-muted">Quick NPC</h2>
|
||||
<h2 className="mb-2 mt-6 smallcaps">Quick NPC</h2>
|
||||
<NpcGenCard campaign={campaign} />
|
||||
|
||||
<h2 className="mb-2 mt-6 text-xs font-semibold uppercase tracking-wide text-muted">Campaign insights</h2>
|
||||
<h2 className="mb-2 mt-6 smallcaps">Campaign insights</h2>
|
||||
<CampaignInsights campaign={campaign} characters={characters} encounters={encounters} quests={quests} notes={notes} />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@ export function EncounterTipCard({ campaign, encounter }: { campaign: Campaign;
|
||||
{state === 'ready' && suggestion && (
|
||||
<div className="mt-2 rounded-md border border-line bg-surface p-2">
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<span className="text-xs font-semibold uppercase tracking-wide text-muted">
|
||||
<span className="smallcaps">
|
||||
{source === 'llm' ? 'AI suggestion' : 'Suggested'}
|
||||
</span>
|
||||
<span className="text-xs text-muted">→ {suggestion.targetDifficulty}</span>
|
||||
|
||||
@@ -77,7 +77,7 @@ function SignalsBellInner({ campaign }: { campaign: Campaign }) {
|
||||
{open && (
|
||||
<div className="absolute right-0 top-11 z-50 w-80 max-w-[calc(100vw-1.5rem)] rounded-xl border border-line bg-panel p-2 shadow-xl">
|
||||
<div className="flex items-center justify-between px-2 py-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wide text-muted">Signals</span>
|
||||
<span className="smallcaps">Signals</span>
|
||||
{signals.length > 0 && (
|
||||
<button className="text-xs text-faint hover:text-ink" onClick={() => setDismissed(new Set(signals.map((s) => s.id)))}>Clear all</button>
|
||||
)}
|
||||
|
||||
@@ -43,7 +43,7 @@ export function useLevelUpAdvisor(campaign: Campaign, character: Character) {
|
||||
setState('ready');
|
||||
return;
|
||||
}
|
||||
if (!res.ok) setMessage(`AI unavailable (${res.error}); showing general routes.`);
|
||||
if (!res.ok) setMessage(`AI unavailable: ${res.message} Showing general routes.`);
|
||||
}
|
||||
setRoutes(deterministicRoutes(campaign.system, character.className));
|
||||
setSource('deterministic');
|
||||
|
||||
Reference in New Issue
Block a user