beba7301a1
- PvP: pestaña Campos de batalla (pvpstats) con resultado, stats por partida y fecha/hora. - Logros: rejilla por categoría con anillos de progreso + detalle por categoría (datos de Achievement.db2 + Achievement_Category.db2, iconos vía wowhead). - Bandas: progreso por dificultad (10/25/heroico) con jefes reales de DungeonEncounter.db2 y tooltip de jefes; agrupado por expansión (Clásico/TBC/WotLK). - Mazmorras: mismo sistema, Normal + Heroico, Clásico/TBC/WotLK. - Página de hermandad estilo armería: cabecera (emblema, puntos = unión de logros de miembros sin duplicar, nº miembros, fundada), pestañas Hermandad/Logros, roster con iconos de clase/raza, columna Función (rol por spec), filtros clase/función, orden por rango/puntos/nivel de objeto/nivel/nombre, paginación. - Barra lateral: mensaje diario con login (iron-session, gated por pertenencia), logros recientes y desgloses de clases y funciones. - Cabecera de personaje: enlace a la hermandad + nombre del reino. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
4.8 KiB
TypeScript
121 lines
4.8 KiB
TypeScript
export type RaidBossState = { name: string; killed: boolean }
|
||
export type RaidDifficulty = { diff: number; label: string; killed: number; total: number; bosses: RaidBossState[] }
|
||
export type RaidProgressItem = { map: number; name: string; exp: number; total: number; difficulties: RaidDifficulty[] }
|
||
|
||
/** Gradiente de cabecera por banda (sin arte del cliente). */
|
||
const RAID_THEME: Record<number, string> = {
|
||
// Classic
|
||
409: 'linear-gradient(135deg, #8a3a1a, #2e1208)',
|
||
469: 'linear-gradient(135deg, #7a2a2a, #250d0d)',
|
||
531: 'linear-gradient(135deg, #8a7a2a, #2a2408)',
|
||
509: 'linear-gradient(135deg, #6b7a2a, #202808)',
|
||
309: 'linear-gradient(135deg, #2f7a5a, #0d281c)',
|
||
// TBC
|
||
532: 'linear-gradient(135deg, #4a2f6b, #1a1028)',
|
||
565: 'linear-gradient(135deg, #6b3a2f, #281410)',
|
||
544: 'linear-gradient(135deg, #7a2f4a, #2a1018)',
|
||
548: 'linear-gradient(135deg, #2a6b6b, #0d2828)',
|
||
550: 'linear-gradient(135deg, #3a5c8a, #101f2f)',
|
||
534: 'linear-gradient(135deg, #2f6b46, #0d281a)',
|
||
564: 'linear-gradient(135deg, #5a2f6b, #1c1028)',
|
||
580: 'linear-gradient(135deg, #8a7a2f, #2a2410)',
|
||
568: 'linear-gradient(135deg, #6b5a2a, #282008)',
|
||
// WotLK
|
||
249: 'linear-gradient(135deg, #7a2d1a, #3a1710)',
|
||
533: 'linear-gradient(135deg, #2f6b46, #10281a)',
|
||
616: 'linear-gradient(135deg, #2a5c8a, #10202f)',
|
||
615: 'linear-gradient(135deg, #4a2f6b, #1a1028)',
|
||
624: 'linear-gradient(135deg, #6b5a2f, #282010)',
|
||
603: 'linear-gradient(135deg, #35566b, #101c28)',
|
||
649: 'linear-gradient(135deg, #6b4a2f, #281a10)',
|
||
631: 'linear-gradient(135deg, #3a6b8a, #0f2230)',
|
||
724: 'linear-gradient(135deg, #7a1f2f, #2a0f14)',
|
||
}
|
||
|
||
function barClass(killed: number, total: number) {
|
||
if (total > 0 && killed >= total) return 'full'
|
||
if (killed > 0) return 'partial'
|
||
return 'empty'
|
||
}
|
||
|
||
/** Gradiente determinista por mapId cuando no hay tema fijo (mazmorras). */
|
||
function themeFor(map: number) {
|
||
if (RAID_THEME[map]) return RAID_THEME[map]
|
||
const h = (map * 47) % 360
|
||
return `linear-gradient(135deg, hsl(${h} 42% 30%), hsl(${h} 48% 11%))`
|
||
}
|
||
|
||
export function ArmoryRaids({
|
||
locale,
|
||
raids,
|
||
labels,
|
||
}: {
|
||
locale: string
|
||
raids: RaidProgressItem[]
|
||
labels: { bosses: string; empty: string; classic: string; tbc: string; wotlk: string }
|
||
}) {
|
||
if (raids.length === 0) {
|
||
return (
|
||
<div className="armory-panel armory-raids">
|
||
<p className="armory-hint">{labels.empty}</p>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const groups: { exp: number; title: string; items: RaidProgressItem[] }[] = [
|
||
{ exp: 2, title: labels.wotlk, items: raids.filter((r) => r.exp === 2) },
|
||
{ exp: 1, title: labels.tbc, items: raids.filter((r) => r.exp === 1) },
|
||
{ exp: 0, title: labels.classic, items: raids.filter((r) => r.exp === 0) },
|
||
].filter((g) => g.items.length > 0)
|
||
|
||
return (
|
||
<div className="armory-panel armory-raids">
|
||
{groups.map((g) => (
|
||
<section className="armory-raid-exp" key={g.exp}>
|
||
<h3 className="armory-raid-exp-title">{g.title}</h3>
|
||
<div className="armory-raid-grid">
|
||
{g.items.map((r) => (
|
||
<div className="armory-raid-card" key={r.map}>
|
||
<div className="armory-raid-banner" style={{ background: themeFor(r.map) }}>
|
||
<span className="armory-raid-name">{r.name}</span>
|
||
<span className="armory-raid-count">
|
||
{r.total} {labels.bosses}
|
||
</span>
|
||
</div>
|
||
<div className="armory-raid-diffs">
|
||
{r.difficulties.map((d) => (
|
||
<div className="armory-raid-row" key={d.diff}>
|
||
<span className="armory-raid-diff-lbl">{d.label}</span>
|
||
<div className={`armory-raid-bar ${barClass(d.killed, d.total)}`}>
|
||
<div
|
||
className="armory-raid-bar-fill"
|
||
style={{ width: `${d.total ? Math.round((d.killed / d.total) * 100) : 0}%` }}
|
||
/>
|
||
<span className="armory-raid-bar-txt">
|
||
{d.killed}/{d.total}
|
||
</span>
|
||
</div>
|
||
<div className="armory-raid-tip" role="tooltip">
|
||
<div className="armory-raid-tip-head">
|
||
{r.name} · {d.label}
|
||
</div>
|
||
<ul>
|
||
{d.bosses.map((b, i) => (
|
||
<li key={i} className={b.killed ? 'killed' : 'alive'}>
|
||
<span className="armory-raid-tip-x">{b.killed ? '1' : '0'} ×</span> {b.name}
|
||
</li>
|
||
))}
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</section>
|
||
))}
|
||
</div>
|
||
)
|
||
}
|