import { LitElement, html, css } from "lit"; import { theme, buttonStyles } from "../styles/theme.js"; /** * MissionDebrief.js * After Action Report - UI component for displaying mission results. * Shows XP, rewards, loot, reputation changes, and squad status. */ export class MissionDebrief extends LitElement { static get properties() { return { result: { type: Object }, }; } static get styles() { return [ theme, buttonStyles, css` :host { display: block; } dialog { margin: auto; padding: 0; border: none; background: transparent; max-width: 900px; max-height: 90vh; width: 90vw; } dialog::backdrop { background: rgba(0, 0, 0, 0.85); backdrop-filter: blur(4px); } .modal-content { background: var(--color-bg-tertiary); border: var(--border-width-thick) solid var(--color-border-default); box-shadow: var(--shadow-lg); width: 100%; max-height: 90vh; overflow-y: auto; display: grid; grid-template-rows: auto 1fr auto; grid-template-areas: "header" "content" "footer"; font-family: var(--font-family); color: var(--color-text-primary); } /* Header */ .header { grid-area: header; padding: var(--spacing-lg); text-align: center; border-bottom: var(--border-width-medium) solid var(--color-border-default); } .header.victory { color: var(--color-accent-gold); text-shadow: 0 0 10px rgba(255, 215, 0, 0.5); } .header.defeat { color: var(--color-accent-red); text-shadow: 0 0 10px rgba(255, 102, 102, 0.5); } .header h1 { margin: 0; font-size: var(--font-size-4xl); text-transform: uppercase; letter-spacing: 0.1em; } .mission-title { margin-top: var(--spacing-sm); font-size: var(--font-size-lg); color: var(--color-text-secondary); } /* Content */ .content { grid-area: content; padding: var(--spacing-lg); display: flex; flex-direction: column; gap: var(--spacing-lg); } /* Primary Stats Row */ .primary-stats { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-lg); } .stat-card { background: var(--color-bg-card); border: var(--border-width-medium) solid var(--color-border-default); padding: var(--spacing-md); display: flex; flex-direction: column; gap: var(--spacing-sm); } .stat-label { font-size: var(--font-size-sm); color: var(--color-text-secondary); text-transform: uppercase; } .stat-value { font-size: var(--font-size-2xl); color: var(--color-accent-cyan); font-weight: var(--font-weight-bold); } .xp-bar-container { width: 100%; height: 20px; background: var(--color-bg-primary); border: var(--border-width-thin) solid var(--color-border-default); position: relative; overflow: hidden; } .xp-bar-fill { height: 100%; background: linear-gradient( 90deg, var(--color-accent-gold) 0%, var(--color-accent-orange) 100% ); transition: width 1s ease-out; width: 0%; } /* Rewards Panel */ .rewards-panel { background: var(--color-bg-panel); border: var(--border-width-medium) solid var(--color-border-default); padding: var(--spacing-md); display: flex; flex-direction: column; gap: var(--spacing-md); } .rewards-panel h2 { margin: 0; font-size: var(--font-size-xl); color: var(--color-accent-gold); border-bottom: var(--border-width-thin) solid var(--color-border-default); padding-bottom: var(--spacing-sm); } .currency-display { display: flex; gap: var(--spacing-lg); align-items: center; } .currency-item { display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-lg); } .loot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); gap: var(--spacing-md); margin-top: var(--spacing-sm); } .item-card { background: var(--color-bg-card); border: var(--border-width-medium) solid var(--color-border-default); padding: var(--spacing-sm); display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 120px; cursor: pointer; transition: all var(--transition-normal); position: relative; } .item-card:hover { border-color: var(--color-accent-cyan); box-shadow: var(--shadow-glow-cyan); transform: translateY(-2px); } .item-card img { width: 48px; height: 48px; object-fit: contain; } .item-card .item-name { margin-top: var(--spacing-xs); font-size: var(--font-size-xs); text-align: center; color: var(--color-text-primary); } .reputation-display { margin-top: var(--spacing-sm); } .reputation-item { display: flex; justify-content: space-between; align-items: center; padding: var(--spacing-xs) 0; border-bottom: var(--border-width-thin) solid var(--color-border-dashed); } .reputation-item:last-child { border-bottom: none; } .reputation-name { color: var(--color-text-secondary); } .reputation-amount { color: var(--color-accent-green); font-weight: var(--font-weight-bold); } /* Roster Status */ .roster-status { background: var(--color-bg-panel); border: var(--border-width-medium) solid var(--color-border-default); padding: var(--spacing-md); } .roster-status h2 { margin: 0 0 var(--spacing-md) 0; font-size: var(--font-size-xl); color: var(--color-accent-cyan); border-bottom: var(--border-width-thin) solid var(--color-border-default); padding-bottom: var(--spacing-sm); } .roster-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--spacing-md); } .unit-status { background: var(--color-bg-card); border: var(--border-width-medium) solid var(--color-border-default); padding: var(--spacing-sm); display: flex; flex-direction: column; align-items: center; gap: var(--spacing-xs); position: relative; } .unit-status.dead { opacity: 0.5; filter: grayscale(100%); } .unit-status.injured { border-color: var(--color-accent-orange); } .unit-portrait { width: 60px; height: 60px; border: var(--border-width-thin) solid var(--color-border-default); background: var(--color-bg-primary); display: flex; align-items: center; justify-content: center; font-size: 32px; } .unit-name { font-size: var(--font-size-sm); font-weight: var(--font-weight-bold); } .unit-status-text { font-size: var(--font-size-xs); color: var(--color-text-secondary); } .level-up-badge { position: absolute; top: -8px; right: -8px; background: var(--color-accent-gold); color: var(--color-bg-primary); padding: 2px 6px; font-size: var(--font-size-xs); font-weight: var(--font-weight-bold); border-radius: var(--border-radius-sm); box-shadow: var(--shadow-glow-gold); } /* Footer */ .footer { grid-area: footer; padding: var(--spacing-lg); border-top: var(--border-width-medium) solid var(--color-border-default); display: flex; justify-content: center; } /* Typewriter Effect */ .typewriter { display: inline-block; overflow: hidden; white-space: nowrap; border-right: 2px solid var(--color-accent-cyan); animation: typing 2s steps(40, end), blink-caret 0.75s step-end infinite; } @keyframes typing { from { width: 0; } to { width: 100%; } } @keyframes blink-caret { from, to { border-color: transparent; } 50% { border-color: var(--color-accent-cyan); } } /* Mobile Responsive */ @media (max-width: 768px) { .modal-content { max-width: 100%; max-height: 100vh; border-radius: 0; } .primary-stats { grid-template-columns: 1fr; } .loot-grid { grid-template-columns: repeat(auto-fill, minmax(80px, 1fr)); } .roster-grid { grid-template-columns: 1fr; } } `, ]; } constructor() { super(); this.result = null; this._typewriterTexts = new Map(); } firstUpdated() { const dialog = this.shadowRoot?.querySelector("dialog"); if (dialog && this.result) { dialog.showModal(); // Prevent closing on backdrop click or ESC (user must click return button) dialog.addEventListener("cancel", (e) => { e.preventDefault(); }); dialog.addEventListener("click", (e) => { // Only close if clicking the backdrop, not the content if (e.target === dialog) { e.preventDefault(); } }); this._startAnimations(); } } updated(changedProperties) { if (changedProperties.has("result") && this.result) { const dialog = this.shadowRoot?.querySelector("dialog"); if (dialog && !dialog.open) { dialog.showModal(); } this._startAnimations(); } } /** * Starts animations for XP bar and typewriter effects * @private */ _startAnimations() { // Animate XP bar this.updateComplete.then(() => { const xpBar = this.shadowRoot?.querySelector(".xp-bar-fill"); if (xpBar && this.result) { // Calculate percentage (assuming max XP of 1000 for now) const maxXp = 1000; const percentage = Math.min((this.result.xpEarned / maxXp) * 100, 100); setTimeout(() => { xpBar.style.width = `${percentage}%`; }, 100); } }); } /** * Gets item display name * @param {Object} item - Item instance * @returns {string} */ _getItemName(item) { return item.name || item.defId || "Unknown Item"; } /** * Gets item icon * @param {Object} item - Item instance * @returns {string|null} */ _getItemIcon(item) { return item.icon || null; } /** * Handles return to hub button click * @private */ _handleReturn() { const dialog = this.shadowRoot?.querySelector("dialog"); if (dialog) { dialog.close(); } this.dispatchEvent( new CustomEvent("return-to-hub", { bubbles: true, composed: true, }) ); } render() { if (!this.result) { return html``; } const isVictory = this.result.outcome === "VICTORY"; const headerClass = isVictory ? "victory" : "defeat"; const headerText = isVictory ? "MISSION ACCOMPLISHED" : "MISSION FAILED"; return html` `; } } customElements.define("mission-debrief", MissionDebrief);