feat: add DebugName wrappers to 56 components + redesign attunement cards + remove ScrollArea from AttunementsTab
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m31s
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m31s
This commit is contained in:
@@ -6,7 +6,6 @@ import type { AttunementDef, AttunementState } from '@/lib/game/types';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { DebugName } from '@/components/game/debug/debug-context';
|
||||
import { fmt } from '@/lib/game/stores';
|
||||
|
||||
@@ -36,36 +35,83 @@ interface AttunementCardProps {
|
||||
|
||||
function AttunementCard({ def, state }: AttunementCardProps) {
|
||||
const unlocked = !!state;
|
||||
const isStarting = def.unlocked === true;
|
||||
const xpProgress = state ? getXpProgress(state) : 0;
|
||||
const nextXp = state ? getXpForNextLevel(state.level) : 0;
|
||||
|
||||
// Style tokens derived from def.color
|
||||
const color = def.color;
|
||||
|
||||
return (
|
||||
<Card className={`bg-gray-900/60 ${unlocked ? 'border-gray-700' : 'border-gray-800 opacity-60'}`}>
|
||||
<CardContent className="p-4 space-y-3">
|
||||
<Card
|
||||
className={`relative overflow-hidden ${
|
||||
unlocked
|
||||
? 'bg-gray-900/60'
|
||||
: 'bg-gray-950/80'
|
||||
}`}
|
||||
style={{
|
||||
borderLeft: `3px solid ${unlocked ? color : `${color}33`}`,
|
||||
borderColor: unlocked ? `${color}88` : `${color}22`,
|
||||
opacity: unlocked ? 1 : 0.55,
|
||||
}}
|
||||
>
|
||||
{/* Starting badge (top-right ribbon) */}
|
||||
{isStarting && unlocked && (
|
||||
<div
|
||||
className="absolute top-3 right-3 text-[10px] font-semibold px-2 py-0.5 rounded-full"
|
||||
style={{ backgroundColor: `${color}22`, color }}
|
||||
>
|
||||
Starting
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Locked overlay pattern */}
|
||||
{!unlocked && (
|
||||
<div className="absolute inset-0 pointer-events-none" style={{ background: `repeating-linear-gradient(45deg, transparent, transparent 12px, ${color}08 12px, ${color}08 24px)` }} />
|
||||
)}
|
||||
|
||||
<CardContent className="p-4 space-y-3 relative">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xl">{def.icon}</span>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<span
|
||||
className="text-xl p-1 rounded"
|
||||
style={{ backgroundColor: `${color}18` }}
|
||||
>
|
||||
{def.icon}
|
||||
</span>
|
||||
<div>
|
||||
<h3 className="font-medium text-gray-100">{def.name}</h3>
|
||||
<h3
|
||||
className="font-semibold"
|
||||
style={{ color: unlocked ? color : `${color}99` }}
|
||||
>
|
||||
{def.name}
|
||||
</h3>
|
||||
<p className="text-xs text-gray-500">
|
||||
{ATTUNEMENT_SLOT_NAMES[def.slot] ?? def.slot}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{unlocked ? (
|
||||
<Badge className="bg-teal-900/50 text-teal-300 text-xs">
|
||||
<Badge
|
||||
className="text-xs font-bold"
|
||||
style={{ backgroundColor: `${color}25`, color, border: `1px solid ${color}44` }}
|
||||
>
|
||||
Lv.{state.level}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="outline" className="border-gray-700 text-gray-500 text-xs">
|
||||
Locked
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-xs"
|
||||
style={{ borderColor: `${color}44`, color: `${color}88` }}
|
||||
>
|
||||
🔒 Locked
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-xs text-gray-400 leading-relaxed">{def.desc}</p>
|
||||
<p className={`text-xs leading-relaxed ${unlocked ? 'text-gray-400' : 'text-gray-600'}`}>{def.desc}</p>
|
||||
|
||||
{/* XP Progress (unlocked only) */}
|
||||
{unlocked && state && (
|
||||
@@ -76,7 +122,12 @@ function AttunementCard({ def, state }: AttunementCardProps) {
|
||||
{fmt(state.experience)} / {fmt(nextXp)}
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={xpProgress} className="h-2" />
|
||||
<div className="h-2 rounded-full overflow-hidden bg-gray-800">
|
||||
<div
|
||||
className="h-full rounded-full transition-all"
|
||||
style={{ width: `${xpProgress}%`, backgroundColor: color }}
|
||||
/>
|
||||
</div>
|
||||
{state.level >= MAX_ATTUNEMENT_LEVEL && (
|
||||
<p className="text-xs text-amber-400 italic">Maximum level reached</p>
|
||||
)}
|
||||
@@ -85,13 +136,19 @@ function AttunementCard({ def, state }: AttunementCardProps) {
|
||||
|
||||
{/* Unlock condition (locked only) */}
|
||||
{!unlocked && def.unlockCondition && (
|
||||
<div className="text-xs text-gray-500 italic border-t border-gray-800 pt-2">
|
||||
<div
|
||||
className="text-xs italic pt-2"
|
||||
style={{ color: `${color}77`, borderTop: `1px solid ${color}15` }}
|
||||
>
|
||||
🔒 {def.unlockCondition}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Details grid */}
|
||||
<div className="grid grid-cols-2 gap-2 text-xs border-t border-gray-800 pt-3">
|
||||
<div
|
||||
className="grid grid-cols-2 gap-2 text-xs pt-3"
|
||||
style={{ borderTop: `1px solid ${unlocked ? `${color}22` : `${color}10`}` }}
|
||||
>
|
||||
<div>
|
||||
<span className="text-gray-500">Mana Type</span>
|
||||
<p className="text-gray-300 capitalize">
|
||||
@@ -110,21 +167,35 @@ function AttunementCard({ def, state }: AttunementCardProps) {
|
||||
)}
|
||||
<div>
|
||||
<span className="text-gray-500">Status</span>
|
||||
<p className={state?.active ? 'text-green-400' : 'text-gray-500'}>
|
||||
{state?.active ? 'Active' : unlocked ? 'Inactive' : 'Locked'}
|
||||
<p style={{ color: state?.active ? '#4ade80' : unlocked ? `${color}aa` : '#6b7280' }}>
|
||||
{state?.active ? '● Active' : unlocked ? '○ Inactive' : 'Locked'}
|
||||
</p>
|
||||
</div>
|
||||
{/* Invoker special: pact-based note */}
|
||||
{def.primaryManaType === undefined && (
|
||||
<div className="col-span-2">
|
||||
<span className="text-gray-500">Special</span>
|
||||
<p style={{ color: `${color}cc` }}>
|
||||
Gains elemental mana from each guardian pact signed
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Capabilities */}
|
||||
<div className="border-t border-gray-800 pt-3">
|
||||
<div style={{ borderTop: `1px solid ${unlocked ? `${color}22` : `${color}10`}` }} className="pt-3">
|
||||
<span className="text-xs text-gray-500 block mb-1.5">Capabilities</span>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{def.capabilities.map((cap) => (
|
||||
<Badge
|
||||
key={cap}
|
||||
variant="outline"
|
||||
className="border-gray-700 text-gray-400 text-[10px]"
|
||||
className="text-[10px]"
|
||||
style={{
|
||||
borderColor: `${color}44`,
|
||||
color: unlocked ? `${color}cc` : `${color}66`,
|
||||
backgroundColor: `${color}0a`,
|
||||
}}
|
||||
>
|
||||
{cap}
|
||||
</Badge>
|
||||
@@ -140,7 +211,12 @@ function AttunementCard({ def, state }: AttunementCardProps) {
|
||||
<Badge
|
||||
key={cat}
|
||||
variant="outline"
|
||||
className="border-gray-700 text-gray-400 text-[10px]"
|
||||
className="text-[10px]"
|
||||
style={{
|
||||
borderColor: `${color}33`,
|
||||
color: unlocked ? `${color}aa` : `${color}55`,
|
||||
backgroundColor: `${color}08`,
|
||||
}}
|
||||
>
|
||||
{cat}
|
||||
</Badge>
|
||||
@@ -174,7 +250,7 @@ export function AttunementsTab() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<div className="text-2xl font-bold text-teal-400">
|
||||
<div className="text-2xl font-bold" style={{ color: '#1ABC9C' }}>
|
||||
{unlockedCount}
|
||||
<span className="text-sm text-gray-500 font-normal">
|
||||
/{allDefs.length}
|
||||
@@ -187,17 +263,15 @@ export function AttunementsTab() {
|
||||
</Card>
|
||||
|
||||
{/* Attunement cards */}
|
||||
<ScrollArea className="h-[600px] pr-2">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{allDefs.map((def) => (
|
||||
<AttunementCard
|
||||
key={def.id}
|
||||
def={def}
|
||||
state={attunements[def.id]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{allDefs.map((def) => (
|
||||
<AttunementCard
|
||||
key={def.id}
|
||||
def={def}
|
||||
state={attunements[def.id]}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</DebugName>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user