refactor: Enhance character sheet and roster management
This commit is contained in:
parent
b363d0850a
commit
dbfa9929dd
3 changed files with 354 additions and 147 deletions
|
|
@ -16,6 +16,8 @@ export class RosterManager {
|
|||
this.roster = []; // List of active Explorer objects (Data only)
|
||||
/** @type {ExplorerData[]} */
|
||||
this.graveyard = []; // List of dead units
|
||||
/** @type {ExplorerData[]} */
|
||||
this.candidates = []; // Pool of recruitable units
|
||||
/** @type {number} */
|
||||
this.rosterLimit = 12;
|
||||
}
|
||||
|
|
@ -27,6 +29,20 @@ export class RosterManager {
|
|||
load(saveData) {
|
||||
this.roster = saveData.roster || [];
|
||||
this.graveyard = saveData.graveyard || [];
|
||||
this.candidates = saveData.candidates || [];
|
||||
|
||||
// DATA MIGRATION: Fix legacy portrait paths
|
||||
const fixPortrait = (unit) => {
|
||||
if (unit.portrait && unit.portrait.includes("class_")) {
|
||||
// Replace "class_" with "" in the filename part
|
||||
// e.g. assets/images/portraits/class_weaver.png -> assets/images/portraits/weaver.png
|
||||
// Use regex to be safe: /class_([a-z]+)\.png/
|
||||
unit.portrait = unit.portrait.replace(/class_/g, "");
|
||||
}
|
||||
};
|
||||
|
||||
this.roster.forEach(fixPortrait);
|
||||
this.candidates.forEach(fixPortrait);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -37,6 +53,7 @@ export class RosterManager {
|
|||
return {
|
||||
roster: this.roster,
|
||||
graveyard: this.graveyard,
|
||||
candidates: this.candidates,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -98,7 +115,80 @@ export class RosterManager {
|
|||
* Clears the roster and graveyard. Used when starting a new game.
|
||||
*/
|
||||
clear() {
|
||||
this.roster = [];
|
||||
this.graveyard = [];
|
||||
this.candidates = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new set of candidates for recruitment.
|
||||
* @param {number} count - Number of candidates to generate
|
||||
*/
|
||||
async generateCandidates(count = 3) {
|
||||
this.candidates = [];
|
||||
|
||||
// Lazy import name generator
|
||||
const { generateCharacterName } = await import("../utils/nameGenerator.js");
|
||||
|
||||
// Available classes (basic pool)
|
||||
const classes = [
|
||||
"CLASS_VANGUARD",
|
||||
"CLASS_WEAVER",
|
||||
"CLASS_SCAVENGER",
|
||||
"CLASS_TINKER",
|
||||
];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const randomClass = classes[Math.floor(Math.random() * classes.length)];
|
||||
const name = generateCharacterName();
|
||||
|
||||
const candidate = {
|
||||
id: `CANDIDATE_${Date.now()}_${i}`,
|
||||
name: name,
|
||||
className: randomClass, // ID used for lookup
|
||||
activeClassId: randomClass,
|
||||
level: 1,
|
||||
status: "READY",
|
||||
hiringCost: 100, // Fixed cost for now
|
||||
portrait: `assets/images/portraits/${randomClass
|
||||
.replace("CLASS_", "")
|
||||
.toLowerCase()}.png`,
|
||||
};
|
||||
|
||||
this.candidates.push(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hires a candidate, moving them to the roster.
|
||||
* @param {string} candidateId
|
||||
* @param {Object} wallet - Reference to wallet to deduct funds (if handled here, but manager acts on data)
|
||||
* @returns {Promise<boolean>} success
|
||||
*/
|
||||
async hireCandidate(candidateId) {
|
||||
if (this.roster.length >= this.rosterLimit) {
|
||||
console.warn("Roster full.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const index = this.candidates.findIndex((c) => c.id === candidateId);
|
||||
if (index === -1) return false;
|
||||
|
||||
const candidate = this.candidates[index];
|
||||
|
||||
// Recruit logic specific to candidate -> unit conversion
|
||||
// (We can reused recruitUnit but we need to ensure ID is unique/updated if needed, though candidate ID is fine usually)
|
||||
// Actually, recruitUnit generates a new ID. Let's use that to be safe and consistent.
|
||||
|
||||
await this.recruitUnit({
|
||||
name: candidate.name,
|
||||
className: candidate.className,
|
||||
activeClassId: candidate.activeClassId,
|
||||
portrait: candidate.portrait,
|
||||
});
|
||||
|
||||
// Remove from candidates
|
||||
this.candidates.splice(index, 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -433,8 +433,8 @@ export class CharacterSheet extends LitElement {
|
|||
}
|
||||
|
||||
.equipment-slot {
|
||||
width: clamp(50px, 7cqw, 70px);
|
||||
height: clamp(50px, 7cqw, 70px);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border: 2px solid #555;
|
||||
display: flex;
|
||||
|
|
@ -443,6 +443,7 @@ export class CharacterSheet extends LitElement {
|
|||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.equipment-slot:hover {
|
||||
|
|
@ -508,7 +509,6 @@ export class CharacterSheet extends LitElement {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.slot-label {
|
||||
|
|
@ -591,6 +591,7 @@ export class CharacterSheet extends LitElement {
|
|||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.item-card:hover {
|
||||
|
|
@ -630,7 +631,6 @@ export class CharacterSheet extends LitElement {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.skills-container {
|
||||
|
|
|
|||
|
|
@ -33,11 +33,12 @@ export class BarracksScreen extends LitElement {
|
|||
font-family: var(--font-family);
|
||||
display: grid;
|
||||
grid-template-columns: 60% 40%;
|
||||
grid-template-rows: auto 1fr;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
grid-template-areas:
|
||||
"header header"
|
||||
"roster detail";
|
||||
gap: var(--spacing-lg);
|
||||
"tabs tabs"
|
||||
"content content";
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.header {
|
||||
|
|
@ -118,9 +119,8 @@ export class BarracksScreen extends LitElement {
|
|||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
/* Roster List */
|
||||
.roster-list {
|
||||
grid-area: roster;
|
||||
/* grid-area: roster; - Removed to allow nesting */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-md);
|
||||
|
|
@ -244,7 +244,7 @@ export class BarracksScreen extends LitElement {
|
|||
|
||||
/* Detail Sidebar */
|
||||
.detail-sidebar {
|
||||
grid-area: detail;
|
||||
/* grid-area: detail; - Removed to allow nesting */
|
||||
background: var(--color-bg-panel);
|
||||
border: var(--border-width-medium) solid var(--color-border-default);
|
||||
padding: var(--spacing-lg);
|
||||
|
|
@ -339,7 +339,9 @@ export class BarracksScreen extends LitElement {
|
|||
filter: { type: String },
|
||||
sort: { type: String },
|
||||
healingCostPerHp: { type: Number },
|
||||
|
||||
wallet: { type: Object },
|
||||
viewMode: { type: String }, // "ROSTER" | "RECRUIT"
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -351,12 +353,23 @@ export class BarracksScreen extends LitElement {
|
|||
this.sort = "LEVEL_DESC";
|
||||
this.healingCostPerHp = 0.5; // 1 HP = 0.5 Shards
|
||||
this.wallet = { aetherShards: 0, ancientCores: 0 };
|
||||
this.viewMode = "ROSTER";
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this._loadRoster();
|
||||
this._loadWallet();
|
||||
|
||||
// Auto-generate candidates if none exist
|
||||
if (
|
||||
!gameStateManager.rosterManager.candidates ||
|
||||
gameStateManager.rosterManager.candidates.length === 0
|
||||
) {
|
||||
gameStateManager.rosterManager
|
||||
.generateCandidates(4)
|
||||
.then(() => this.requestUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
_loadRoster() {
|
||||
|
|
@ -455,7 +468,9 @@ export class BarracksScreen extends LitElement {
|
|||
let portrait = unitData.portrait || unitData.image;
|
||||
if (!portrait) {
|
||||
// Default portrait path based on class
|
||||
portrait = `assets/images/portraits/${activeClassId.toLowerCase()}.png`;
|
||||
portrait = `assets/images/portraits/${activeClassId
|
||||
.replace("CLASS_", "")
|
||||
.toLowerCase()}.png`;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -688,7 +703,9 @@ export class BarracksScreen extends LitElement {
|
|||
explorer.portrait = rosterData.portrait || rosterData.image;
|
||||
} else {
|
||||
// Default portrait path
|
||||
explorer.portrait = `assets/images/portraits/${activeClassId.toLowerCase()}.png`;
|
||||
explorer.portrait = `assets/images/portraits/${activeClassId
|
||||
.replace("CLASS_", "")
|
||||
.toLowerCase()}.png`;
|
||||
}
|
||||
|
||||
// Generate skill tree if gameLoop isn't running
|
||||
|
|
@ -844,109 +861,232 @@ export class BarracksScreen extends LitElement {
|
|||
`;
|
||||
}
|
||||
|
||||
_renderDetailSidebar() {
|
||||
const unit = this._getSelectedUnit();
|
||||
if (!unit) {
|
||||
return html`
|
||||
<div class="detail-sidebar">
|
||||
<div class="empty-state">
|
||||
<p>Select a unit to view details</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
_renderRecruitView() {
|
||||
const candidates = gameStateManager.rosterManager.candidates || [];
|
||||
|
||||
if (candidates.length === 0) {
|
||||
return html`<div class="empty-state">
|
||||
No recruits available at this time.
|
||||
</div>`;
|
||||
}
|
||||
|
||||
const healCost = this._calculateHealCost(unit);
|
||||
return html`
|
||||
<div class="roster-list">
|
||||
${candidates.map(
|
||||
(candidate) => html`
|
||||
<div class="unit-card ready">
|
||||
<div class="unit-portrait">
|
||||
<img
|
||||
src="${candidate.portrait ||
|
||||
"assets/images/portraits/default.png"}"
|
||||
alt="Portrait"
|
||||
/>
|
||||
</div>
|
||||
<div class="unit-info">
|
||||
<div class="unit-name">${candidate.name}</div>
|
||||
<div class="unit-meta">
|
||||
<span class="unit-class"
|
||||
>${candidate.className.replace("CLASS_", "")}</span
|
||||
>
|
||||
<span class="unit-level">Lvl 1</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="unit-status">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
?disabled=${this.wallet.aetherShards < candidate.hiringCost}
|
||||
@click=${() => this._onHireClick(candidate)}
|
||||
>
|
||||
Hire (${candidate.hiringCost} 💎)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
<div class="detail-sidebar">
|
||||
<div class="detail-header">
|
||||
<h3>Recruitment Office</h3>
|
||||
<p style="color: var(--color-text-secondary);">
|
||||
New candidates arrive periodically. Hire them to expand your squad.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async _onHireClick(candidate) {
|
||||
if (this.wallet.aetherShards < candidate.hiringCost) {
|
||||
alert("Insufficient funds.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Deduct Funds
|
||||
if (gameStateManager.hubStash && gameStateManager.hubStash.currency) {
|
||||
gameStateManager.hubStash.currency.aetherShards -= candidate.hiringCost;
|
||||
this.wallet.aetherShards =
|
||||
gameStateManager.hubStash.currency.aetherShards;
|
||||
|
||||
// Calls RosterManager to move candidate to roster
|
||||
const success = await gameStateManager.rosterManager.hireCandidate(
|
||||
candidate.id
|
||||
);
|
||||
|
||||
if (success) {
|
||||
// Save everything
|
||||
await gameStateManager.persistence.saveRoster(
|
||||
gameStateManager.rosterManager.save()
|
||||
);
|
||||
await gameStateManager.persistence.saveHubStash(
|
||||
gameStateManager.hubStash
|
||||
);
|
||||
|
||||
// Update UI
|
||||
this._loadRoster();
|
||||
this.requestUpdate();
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("wallet-updated", {
|
||||
detail: { wallet: { ...this.wallet } },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
alert("Roster is full!");
|
||||
// Refund if failed (simple rollback)
|
||||
gameStateManager.hubStash.currency.aetherShards += candidate.hiringCost;
|
||||
this.wallet.aetherShards =
|
||||
gameStateManager.hubStash.currency.aetherShards;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_renderRosterView() {
|
||||
const filteredUnits = this._getFilteredUnits();
|
||||
const selectedUnit = this._getSelectedUnit();
|
||||
|
||||
return html`
|
||||
<div
|
||||
style="grid-column: 1 / -1; display: grid; grid-template-columns: 60% 40%; gap: var(--spacing-lg); height: 100%;"
|
||||
>
|
||||
<div
|
||||
class="roster-list-container"
|
||||
style="display: flex; flex-direction: column; gap: 10px; overflow: hidden;"
|
||||
>
|
||||
<div class="filter-bar">
|
||||
<button
|
||||
class="filter-button ${this.filter === "ALL" ? "active" : ""}"
|
||||
@click=${() => this._onFilterClick("ALL")}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
class="filter-button ${this.filter === "READY" ? "active" : ""}"
|
||||
@click=${() => this._onFilterClick("READY")}
|
||||
>
|
||||
Ready
|
||||
</button>
|
||||
<button
|
||||
class="filter-button ${this.filter === "INJURED" ? "active" : ""}"
|
||||
@click=${() => this._onFilterClick("INJURED")}
|
||||
>
|
||||
Injured
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="roster-list" style="flex: 1; overflow-y: auto;">
|
||||
${filteredUnits.length === 0
|
||||
? html`<div class="empty-state">
|
||||
No units found matching filter.
|
||||
</div>`
|
||||
: filteredUnits.map((unit) => this._renderUnitCard(unit))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detail Sidebar -->
|
||||
${selectedUnit
|
||||
? this._renderDetailContent(selectedUnit)
|
||||
: html`
|
||||
<div class="detail-sidebar">
|
||||
<div class="empty-state">Select a unit to view details</div>
|
||||
</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
_renderDetailContent(selectedUnit) {
|
||||
const healCost = this._calculateHealCost(selectedUnit);
|
||||
const canHeal =
|
||||
unit.currentHp < unit.maxHp && this.wallet.aetherShards >= healCost;
|
||||
const isInjured = unit.currentHp < unit.maxHp;
|
||||
const hpPercent = (unit.currentHp / unit.maxHp) * 100;
|
||||
selectedUnit.currentHp < selectedUnit.maxHp &&
|
||||
this.wallet.aetherShards >= healCost;
|
||||
|
||||
return html`
|
||||
<div class="detail-sidebar">
|
||||
<div class="detail-header">
|
||||
<div class="detail-preview">
|
||||
${unit.portrait
|
||||
${selectedUnit.portrait
|
||||
? html`<img
|
||||
src="${unit.portrait}"
|
||||
alt="${unit.name}"
|
||||
style="width: 100%; height: 100%; object-fit: cover;"
|
||||
@error=${(e) => {
|
||||
e.target.style.display = "none";
|
||||
}}
|
||||
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';"
|
||||
/>
|
||||
<span
|
||||
style="display: none; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 2em;"
|
||||
>
|
||||
${unit.classId
|
||||
? unit.classId.replace("CLASS_", "")[0]
|
||||
: "?"}
|
||||
</span>`
|
||||
: html`<span
|
||||
style="display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; font-size: 2em;"
|
||||
>
|
||||
${unit.classId ? unit.classId.replace("CLASS_", "")[0] : "?"}
|
||||
</span>`}
|
||||
src="${selectedUnit.portrait}"
|
||||
alt="${selectedUnit.name}"
|
||||
/>`
|
||||
: html`👤`}
|
||||
</div>
|
||||
<div>
|
||||
<h3 style="margin: 0; color: var(--color-accent-cyan);">
|
||||
${unit.name}
|
||||
</h3>
|
||||
<p
|
||||
style="margin: var(--spacing-xs) 0; color: var(--color-text-secondary);"
|
||||
>
|
||||
${unit.classId?.replace("CLASS_", "") || "Unknown"} • Level
|
||||
${unit.level}
|
||||
</p>
|
||||
<div class="unit-name">${selectedUnit.name}</div>
|
||||
<div class="unit-class">
|
||||
${selectedUnit.classId.replace("CLASS_", "")}
|
||||
<span class="unit-level">Lv ${selectedUnit.level}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-stats">
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Health</span>
|
||||
<span class="stat-value">${unit.currentHp} / ${unit.maxHp}</span>
|
||||
<span class="stat-value"
|
||||
>${selectedUnit.currentHp}/${selectedUnit.maxHp}</span
|
||||
>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div
|
||||
class="progress-bar-fill hp"
|
||||
style="width: ${hpPercent}%"
|
||||
></div>
|
||||
<div class="progress-bar-label">${Math.round(hpPercent)}%</div>
|
||||
<div class="unit-hp-bar">
|
||||
<div class="progress-bar-container">
|
||||
<div
|
||||
class="progress-bar-fill ${selectedUnit.currentHp <
|
||||
selectedUnit.maxHp
|
||||
? "injured"
|
||||
: "ready"}"
|
||||
style="width: ${(selectedUnit.currentHp / selectedUnit.maxHp) *
|
||||
100}%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span class="stat-label">Status</span>
|
||||
<span class="stat-value">${unit.status}</span>
|
||||
<span
|
||||
class="stat-value status-badge ${selectedUnit.status.toLowerCase()}"
|
||||
>${selectedUnit.status}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-actions">
|
||||
<button class="btn action-button" @click=${this._handleInspect}>
|
||||
<button class="btn" @click=${this._handleInspect}>
|
||||
Inspect / Equip
|
||||
</button>
|
||||
${isInjured
|
||||
${selectedUnit.currentHp < selectedUnit.maxHp
|
||||
? html`
|
||||
<button
|
||||
class="btn btn-primary action-button"
|
||||
class="btn btn-primary"
|
||||
?disabled=${!canHeal}
|
||||
@click=${this._handleHeal}
|
||||
>
|
||||
Treat Wounds (${healCost} 💎)
|
||||
Heal (${healCost} 💎)
|
||||
</button>
|
||||
${!canHeal && healCost > 0
|
||||
? html`
|
||||
<div class="heal-cost">
|
||||
Insufficient funds. Need ${healCost} Shards.
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
`
|
||||
: html`
|
||||
<button class="btn action-button" disabled>Full Health</button>
|
||||
`}
|
||||
: ""}
|
||||
<button
|
||||
class="btn btn-danger action-button"
|
||||
class="btn btn-danger"
|
||||
@click=${this._handleDismiss}
|
||||
style="margin-top: auto;"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
|
|
@ -956,81 +1096,58 @@ export class BarracksScreen extends LitElement {
|
|||
}
|
||||
|
||||
render() {
|
||||
const filteredUnits = this._getFilteredUnits();
|
||||
const rosterCount = this.units.length;
|
||||
const rosterLimit = gameStateManager.rosterManager.rosterLimit || 12;
|
||||
const isRecruit = this.viewMode === "RECRUIT";
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<div>
|
||||
<h2>The Squad Quarters</h2>
|
||||
<div class="roster-info">
|
||||
<span class="roster-count"
|
||||
>Roster: ${rosterCount}/${rosterLimit}</span
|
||||
>
|
||||
<div class="filter-bar">
|
||||
<button
|
||||
class="filter-button ${this.filter === "ALL" ? "active" : ""}"
|
||||
@click=${() => this._onFilterClick("ALL")}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
class="filter-button ${this.filter === "READY" ? "active" : ""}"
|
||||
@click=${() => this._onFilterClick("READY")}
|
||||
>
|
||||
Ready
|
||||
</button>
|
||||
<button
|
||||
class="filter-button ${this.filter === "INJURED"
|
||||
? "active"
|
||||
: ""}"
|
||||
@click=${() => this._onFilterClick("INJURED")}
|
||||
>
|
||||
Injured
|
||||
</button>
|
||||
</div>
|
||||
<div class="sort-bar">
|
||||
<span
|
||||
style="color: var(--color-text-secondary); font-size: var(--font-size-xs);"
|
||||
>
|
||||
Sort:
|
||||
</span>
|
||||
<button
|
||||
class="sort-button"
|
||||
@click=${() => this._onSortClick("LEVEL_DESC")}
|
||||
>
|
||||
Level
|
||||
</button>
|
||||
<button
|
||||
class="sort-button"
|
||||
@click=${() => this._onSortClick("NAME_ASC")}
|
||||
>
|
||||
Name
|
||||
</button>
|
||||
<button
|
||||
class="sort-button"
|
||||
@click=${() => this._onSortClick("HP_ASC")}
|
||||
>
|
||||
HP
|
||||
</button>
|
||||
</div>
|
||||
<div class="roster-info">
|
||||
<h2>Squad Quarters</h2>
|
||||
<div class="roster-count">
|
||||
${gameStateManager.rosterManager.roster.length} /
|
||||
${gameStateManager.rosterManager.rosterLimit} Units
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-close" @click=${this._handleClose}>✕</button>
|
||||
|
||||
<div
|
||||
class="wallet-display"
|
||||
style="display: flex; gap: 20px; align-items: center;"
|
||||
>
|
||||
<div
|
||||
class="wallet-item"
|
||||
style="color: var(--color-accent-gold); font-weight: bold;"
|
||||
>
|
||||
💎 ${this.wallet.aetherShards}
|
||||
</div>
|
||||
<button class="btn btn-close" @click=${this._handleClose}>✕</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="roster-list">
|
||||
${filteredUnits.length === 0
|
||||
? html`
|
||||
<div class="empty-state">
|
||||
<p>No units found matching the current filter.</p>
|
||||
</div>
|
||||
`
|
||||
: filteredUnits.map((unit) => this._renderUnitCard(unit))}
|
||||
<div
|
||||
style="grid-area: tabs; display: flex; gap: 10px; border-bottom: 1px solid var(--color-border-default); padding-bottom: 10px;"
|
||||
>
|
||||
<button
|
||||
class="filter-button ${!isRecruit ? "active" : ""}"
|
||||
@click=${() => {
|
||||
this.viewMode = "ROSTER";
|
||||
this.requestUpdate();
|
||||
}}
|
||||
>
|
||||
Active Roster
|
||||
</button>
|
||||
<button
|
||||
class="filter-button ${isRecruit ? "active" : ""}"
|
||||
@click=${() => {
|
||||
this.viewMode = "RECRUIT";
|
||||
this.requestUpdate();
|
||||
}}
|
||||
>
|
||||
Recruit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
${this._renderDetailSidebar()}
|
||||
<div style="grid-area: content; height: 100%; overflow: hidden;">
|
||||
${isRecruit ? this._renderRecruitView() : this._renderRosterView()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue