Browse Source

refactor

main
Stephanie Gredell 4 months ago
parent
commit
e4a8b31bc5
  1. 55
      src/data/cards.js
  2. 23
      src/engine/battle.js
  3. 35
      src/engine/core.js
  4. 164
      src/input/InputManager.js
  5. 8
      src/main.js
  6. 165
      src/ui/render.js
  7. 131
      style.css

55
src/data/cards.js

@ -130,9 +130,15 @@ export const CARDS = {
ctx.deal(ctx.enemy, ctx.scalarFromWeak(5)); ctx.deal(ctx.enemy, ctx.scalarFromWeak(5));
if (prevHp > 0 && ctx.enemy.hp <= 0) { if (prevHp > 0 && ctx.enemy.hp <= 0) {
ctx.log("Recursion activates and strikes again!"); ctx.log("Recursion activates and strikes again!");
ctx.enemy.hp = 1; ctx.enemy.hp = 1;
ctx.deal(ctx.enemy, ctx.scalarFromWeak(5)); ctx.deal(ctx.enemy, ctx.scalarFromWeak(5));
// Check for battle end after second attack
if (ctx.enemy.hp <= 0) {
ctx.enemy.hp = 0;
ctx.onWin();
return;
}
} }
} }
}, },
@ -156,8 +162,44 @@ export const CARDS = {
id: "code_review", name: "Code Review", cost: 1, type: "skill", text: "Look at top 3 cards. Put 1 in hand, rest on bottom of deck.", id: "code_review", name: "Code Review", cost: 1, type: "skill", text: "Look at top 3 cards. Put 1 in hand, rest on bottom of deck.",
art: "Monk_24.png", art: "Monk_24.png",
effect: (ctx) => { effect: (ctx) => {
ctx.draw(1); const topCards = ctx.peekTop(3);
ctx.log("Code review reveals useful insights. You draw a card."); if (topCards.length === 0) {
ctx.log("No cards left in deck to review.");
return;
}
// Store selection state for modal
ctx.root._codeReviewCards = topCards;
ctx.root._codeReviewCallback = (selectedIndex) => {
// Get the selected card
const selectedCard = topCards[selectedIndex];
// Remove the peeked cards from draw pile (they were only peeked)
topCards.forEach((card, i) => {
const drawIndex = ctx.root.player.draw.findIndex(id => id === card.id);
if (drawIndex >= 0) {
ctx.root.player.draw.splice(drawIndex, 1);
}
});
// Add selected card to hand
ctx.addToHand(selectedCard);
// Put remaining cards on bottom of deck
topCards.forEach((card, i) => {
if (i !== selectedIndex) {
ctx.putOnBottom(card.id);
}
});
ctx.log(`Code review complete. Added ${selectedCard.name} to hand.`);
ctx.render();
};
// Show selection modal
if (window.gameModules?.render?.renderCodeReviewSelection) {
window.gameModules.render.renderCodeReviewSelection(ctx.root, topCards);
}
} }
}, },
@ -292,17 +334,14 @@ export const CARDS = {
art: "Monk_34.png", art: "Monk_34.png",
effect: (ctx) => { effect: (ctx) => {
ctx.deal(ctx.enemy, ctx.scalarFromWeak(15)); ctx.deal(ctx.enemy, ctx.scalarFromWeak(15));
if (ctx.player.hand.length > 1) { // Don't remove this card itself if (ctx.player.hand.length > 0) { // Check if there are any cards left in hand
const otherCards = ctx.player.hand.filter(c => c.id !== "git_push_force"); const randomCard = ctx.player.hand[Math.floor(Math.random() * ctx.player.hand.length)];
if (otherCards.length > 0) {
const randomCard = otherCards[Math.floor(Math.random() * otherCards.length)];
const handIdx = ctx.player.hand.findIndex(c => c === randomCard); const handIdx = ctx.player.hand.findIndex(c => c === randomCard);
const [card] = ctx.player.hand.splice(handIdx, 1); const [card] = ctx.player.hand.splice(handIdx, 1);
ctx.player.draw.push(card.id); ctx.player.draw.push(card.id);
ctx.log(`${card.name} was force-pushed back to your deck!`); ctx.log(`${card.name} was force-pushed back to your deck!`);
} }
} }
}
}, },
stack_trace: { stack_trace: {

23
src/engine/battle.js

@ -1,7 +1,7 @@
import { ENEMIES } from "../data/enemies.js"; import { ENEMIES } from "../data/enemies.js";
import { RELICS } from "../data/relics.js"; import { RELICS } from "../data/relics.js";
import { CARDS } from "../data/cards.js"; import { CARDS } from "../data/cards.js";
import { draw, endTurnDiscard, clamp, cloneCard, shuffle } from "./core.js"; import { draw, endTurnDiscard, clamp, cloneCard, shuffle, peekTopCards, putCardOnBottomOfDeck, addCardToHand } from "./core.js";
export function createBattle(ctx, enemyId) { export function createBattle(ctx, enemyId) {
const enemyData = ENEMIES[enemyId]; const enemyData = ENEMIES[enemyId];
@ -74,6 +74,12 @@ export function playCard(ctx, handIndex) {
ctx.player.energy -= actualCost; ctx.player.energy -= actualCost;
ctx.lastCard = card.id; ctx.lastCard = card.id;
// Remove the played card from hand BEFORE running effect to prevent index shifting issues
let usedCard = null;
if (card.type !== "power") {
const [removed] = ctx.player.hand.splice(handIndex, 1);
usedCard = removed;
}
const prevDeal = ctx.deal; const prevDeal = ctx.deal;
ctx.deal = (target, amount) => { ctx.deal = (target, amount) => {
@ -108,13 +114,12 @@ export function playCard(ctx, handIndex) {
return; return;
} }
// Handle card disposal after effect (if it was removed from hand)
if (card.type !== "power") { if (usedCard) {
const [used] = ctx.player.hand.splice(handIndex, 1);
if (!card.exhaust) { if (!card.exhaust) {
ctx.player.discard.push(used.id); ctx.player.discard.push(usedCard.id);
} else { } else {
ctx.log(`${used.name} is exhausted and removed from the fight.`); ctx.log(`${usedCard.name} is exhausted and removed from the fight.`);
} }
} }
@ -167,6 +172,7 @@ export function enemyTurn(ctx) {
if (ctx.player.hp <= 0) { ctx.onLose(); return; } if (ctx.player.hp <= 0) { ctx.onLose(); return; }
if (ctx.enemy.hp <= 0) { ctx.enemy.hp = 0; ctx.onWin(); return; }
e.turn++; e.turn++;
try { try {
@ -235,6 +241,7 @@ export function makeBattleContext(root) {
showDamageNumber: root.showDamageNumber, showDamageNumber: root.showDamageNumber,
lastCard: null, lastCard: null,
flags: {}, flags: {},
root: root, // Provide access to root for complex card effects
// New mechanics for advanced cards // New mechanics for advanced cards
moveFromDiscardToHand: (cardId) => { moveFromDiscardToHand: (cardId) => {
const idx = root.player.discard.findIndex(id => id === cardId); const idx = root.player.discard.findIndex(id => id === cardId);
@ -249,6 +256,10 @@ export function makeBattleContext(root) {
} }
return false; return false;
}, },
// Code Review card mechanics
peekTop: (n) => peekTopCards(root.player, n),
putOnBottom: (cardId) => putCardOnBottomOfDeck(root.player, cardId),
addToHand: (card) => addCardToHand(root.player, card),
countCardType: (type) => { countCardType: (type) => {
const allCards = [...root.player.deck, ...root.player.hand.map(c => c.id), ...root.player.draw, ...root.player.discard]; const allCards = [...root.player.deck, ...root.player.hand.map(c => c.id), ...root.player.draw, ...root.player.discard];
return allCards.filter(id => CARDS[id]?.type === type).length; return allCards.filter(id => CARDS[id]?.type === type).length;

35
src/engine/core.js

@ -46,6 +46,41 @@ export function endTurnDiscard(player) {
player.energy = player.maxEnergy; player.energy = player.maxEnergy;
} }
// Peek at top N cards from draw pile without removing them
export function peekTopCards(player, n = 3) {
const cards = [];
const drawPile = [...player.draw]; // Copy to avoid modifying original
for (let i = 0; i < n && i < drawPile.length; i++) {
const cardId = drawPile[drawPile.length - 1 - i]; // Peek from top (end of array)
const originalCard = CARDS[cardId];
if (originalCard) {
const clonedCard = cloneCard(originalCard);
clonedCard._drawIndex = drawPile.length - 1 - i; // Store original position
cards.push(clonedCard);
}
}
return cards;
}
// Put a specific card on the bottom of the draw pile
export function putCardOnBottomOfDeck(player, cardId) {
// Remove from draw pile first (if it's there)
const drawIndex = player.draw.findIndex(id => id === cardId);
if (drawIndex >= 0) {
player.draw.splice(drawIndex, 1);
}
// Add to bottom of draw pile (beginning of array)
player.draw.unshift(cardId);
}
// Add a specific card object to hand
export function addCardToHand(player, card) {
player.hand.push(card);
}
export function cloneCard(c) { export function cloneCard(c) {
if (!c) { if (!c) {

164
src/input/InputManager.js

@ -46,6 +46,19 @@ export class InputManager {
this.handleEscapeKey(event); this.handleEscapeKey(event);
} }
// Handle number keys for code review selection
if (this.root._codeReviewCards && event.key >= '1' && event.key <= '3') {
const selectedIndex = parseInt(event.key, 10) - 1;
if (selectedIndex < this.root._codeReviewCards.length) {
event.preventDefault();
if (this.root._codeReviewCallback) {
this.root._codeReviewCallback(selectedIndex);
this.root._codeReviewCards = null;
this.root._codeReviewCallback = null;
}
}
}
// Add other global shortcuts here as needed // Add other global shortcuts here as needed
} }
@ -103,12 +116,30 @@ export class InputManager {
return; return;
} }
const buyRelicElement = target.closest('[data-buy-relic]');
if (buyRelicElement) {
this.handleShopRelicBuy(buyRelicElement, event);
return;
}
const leaveElement = target.closest('[data-leave]');
if (leaveElement) {
this.handleLeaveShop(leaveElement, event);
return;
}
const relicElement = target.closest('[data-relic]'); const relicElement = target.closest('[data-relic]');
if (relicElement) { if (relicElement) {
this.handleRelicSelection(relicElement, event); this.handleRelicSelection(relicElement, event);
return; return;
} }
const codeReviewElement = target.closest('[data-code-review-pick]');
if (codeReviewElement) {
this.handleCodeReviewPick(codeReviewElement, event);
return;
}
// Check for direct data attributes on target (fallback) // Check for direct data attributes on target (fallback)
if (target.dataset.node !== undefined) { if (target.dataset.node !== undefined) {
this.handleMapNodeClick(target, event); this.handleMapNodeClick(target, event);
@ -234,12 +265,116 @@ export class InputManager {
if (goldDisplay) { if (goldDisplay) {
goldDisplay.textContent = this.root.player.gold; goldDisplay.textContent = this.root.player.gold;
} }
// Update affordability of remaining items
this.updateShopAffordability();
// Save immediately to persist purchase
this.root.save();
} else { } else {
this.root.log("Not enough gold!"); this.root.log("Not enough gold!");
} }
} }
} }
/**
* Handle shop relic purchases
*/
handleShopRelicBuy(element, event) {
if (this.root.currentShopRelic) {
const relic = this.root.currentShopRelic;
if (this.root.player.gold >= 100) {
this.root.player.gold -= 100;
this.root.log(`Bought ${relic.name} for 100 gold.`);
// Attach the relic
import("../engine/battle.js").then(({ attachRelics }) => {
const currentRelicIds = this.root.relicStates.map(r => r.id);
const newRelicIds = [...currentRelicIds, relic.id];
attachRelics(this.root, newRelicIds);
});
element.disabled = true;
element.textContent = "SOLD";
// Update gold display
const goldDisplay = this.root.app.querySelector('.gold-amount');
if (goldDisplay) {
goldDisplay.textContent = this.root.player.gold;
}
// Update affordability of remaining items
this.updateShopAffordability();
// Save immediately to persist purchase
this.root.save();
} else {
this.root.log("Not enough gold!");
}
}
}
/**
* Update shop item affordability
*/
updateShopAffordability() {
// Update card affordability
this.root.app.querySelectorAll("[data-buy-card]").forEach(btn => {
if (!btn.disabled) {
const cardContainer = btn.closest('.shop-card-container');
const overlay = cardContainer.querySelector('.card-disabled-overlay');
if (this.root.player.gold < 50) {
btn.classList.remove('playable');
btn.classList.add('unplayable');
if (!overlay) {
const newOverlay = document.createElement('div');
newOverlay.className = 'card-disabled-overlay';
newOverlay.innerHTML = '<span>Need 50 gold</span>';
cardContainer.appendChild(newOverlay);
}
} else {
btn.classList.remove('unplayable');
btn.classList.add('playable');
if (overlay) {
overlay.remove();
}
}
}
});
// Update relic affordability
const relicBtn = this.root.app.querySelector("[data-buy-relic]");
if (relicBtn && !relicBtn.disabled) {
const relicContainer = relicBtn.closest('.shop-relic-container');
const overlay = relicContainer.querySelector('.relic-disabled-overlay');
if (this.root.player.gold < 100) {
relicBtn.classList.remove('affordable');
relicBtn.classList.add('unaffordable');
if (!overlay) {
const newOverlay = document.createElement('div');
newOverlay.className = 'relic-disabled-overlay';
newOverlay.innerHTML = '<span>Need 100 gold</span>';
relicContainer.appendChild(newOverlay);
}
} else {
relicBtn.classList.remove('unaffordable');
relicBtn.classList.add('affordable');
if (overlay) {
overlay.remove();
}
}
}
}
/**
* Handle leaving the shop
*/
handleLeaveShop(element, event) {
this.root.afterNode();
}
/** /**
* Handle relic selection * Handle relic selection
*/ */
@ -248,6 +383,26 @@ export class InputManager {
this.root.selectStartingRelic(relicId); this.root.selectStartingRelic(relicId);
} }
/**
* Handle code review card selection
*/
handleCodeReviewPick(element, event) {
const selectedIndex = parseInt(element.dataset.codeReviewPick, 10);
if (this.root._codeReviewCallback && this.root._codeReviewCards) {
try {
// Execute the callback with selected index
this.root._codeReviewCallback(selectedIndex);
// Clean up state
this.root._codeReviewCards = null;
this.root._codeReviewCallback = null;
} catch (error) {
console.error('Error handling code review selection:', error);
}
}
}
/** /**
* Handle action buttons (like show-messages, end) * Handle action buttons (like show-messages, end)
*/ */
@ -373,6 +528,15 @@ export class InputManager {
* Handle Escape key presses * Handle Escape key presses
*/ */
handleEscapeKey(event) { handleEscapeKey(event) {
// Handle code review modal cancellation
if (this.root._codeReviewCards) {
this.root._codeReviewCards = null;
this.root._codeReviewCallback = null;
// Return to battle without making a choice
this.root.render();
return;
}
// Close any open modals // Close any open modals
const modals = document.querySelectorAll('.messages-modal-overlay'); const modals = document.querySelectorAll('.messages-modal-overlay');
modals.forEach(modal => modal.remove()); modals.forEach(modal => modal.remove());

8
src/main.js

@ -2,9 +2,9 @@ import { CARDS, CARD_POOL } from "./data/cards.js";
import { START_RELIC_CHOICES } from "./data/relics.js"; import { START_RELIC_CHOICES } from "./data/relics.js";
import { ENEMIES } from "./data/enemies.js"; import { ENEMIES } from "./data/enemies.js";
import { MAPS } from "./data/maps.js"; import { MAPS } from "./data/maps.js";
import { makePlayer, initDeck, draw } from "./engine/core.js"; import { makePlayer, initDeck, draw, peekTopCards, putCardOnBottomOfDeck, addCardToHand } from "./engine/core.js";
import { createBattle, startPlayerTurn, playCard, endTurn, makeBattleContext, attachRelics } from "./engine/battle.js"; import { createBattle, startPlayerTurn, playCard, endTurn, makeBattleContext, attachRelics } from "./engine/battle.js";
import { renderBattle, renderMap, renderReward, renderRest, renderShop, renderWin, renderLose, renderEvent, renderRelicSelection, renderUpgrade, updateCardSelection, showDamageNumber } from "./ui/render.js"; import { renderBattle, renderMap, renderReward, renderRest, renderShop, renderWin, renderLose, renderEvent, renderRelicSelection, renderUpgrade, updateCardSelection, showDamageNumber, renderCodeReviewSelection } from "./ui/render.js";
import { InputManager } from "./input/InputManager.js"; import { InputManager } from "./input/InputManager.js";
import { CommandInvoker } from "./commands/CommandInvoker.js"; import { CommandInvoker } from "./commands/CommandInvoker.js";
@ -23,6 +23,8 @@ const root = {
currentEvent: null, // For event handling currentEvent: null, // For event handling
currentShopCards: null, // For shop handling currentShopCards: null, // For shop handling
currentShopRelic: null, // For shop relic handling currentShopRelic: null, // For shop relic handling
_codeReviewCards: null, // For code review card selection
_codeReviewCallback: null, // For code review completion
log(m) { this.logs.push(m); this.logs = this.logs.slice(-200); }, log(m) { this.logs.push(m); this.logs = this.logs.slice(-200); },
async render() { await renderBattle(this); }, async render() { await renderBattle(this); },
@ -556,7 +558,7 @@ root.inputManager.initGlobalListeners();
// Make modules available globally for InputManager // Make modules available globally for InputManager
window.gameModules = { window.gameModules = {
cards: { CARDS }, cards: { CARDS },
render: { renderMap, renderUpgrade, updateCardSelection } render: { renderMap, renderUpgrade, updateCardSelection, renderCodeReviewSelection }
}; };
initializeGame(); initializeGame();

165
src/ui/render.js

@ -906,6 +906,10 @@ export function renderShop(root) {
const availableRelics = START_RELIC_CHOICES.filter(id => !ownedRelicIds.includes(id)); const availableRelics = START_RELIC_CHOICES.filter(id => !ownedRelicIds.includes(id));
const shopRelic = availableRelics.length > 0 ? RELICS[availableRelics[0]] : null; const shopRelic = availableRelics.length > 0 ? RELICS[availableRelics[0]] : null;
// Store shop cards for InputManager access
root.currentShopCards = shopCards;
root.currentShopRelic = shopRelic;
root.app.innerHTML = ` root.app.innerHTML = `
<div class="shop-screen"> <div class="shop-screen">
<div class="shop-header"> <div class="shop-header">
@ -996,62 +1000,11 @@ export function renderShop(root) {
if (!root.player.gold) root.player.gold = 100; if (!root.player.gold) root.player.gold = 100;
root.app.querySelectorAll("[data-buy-card]").forEach(btn => { // Note: Card purchase events are now handled by InputManager
btn.addEventListener("click", () => {
const idx = parseInt(btn.dataset.buyCard, 10);
const card = shopCards[idx];
if (root.player.gold >= 50) {
root.player.gold -= 50;
root.player.deck.push(card.id);
root.log(`Bought ${card.name} for 50 gold.`);
btn.disabled = true;
btn.textContent = "SOLD";
// Update gold display
const goldDisplay = root.app.querySelector('.gold-amount');
if (goldDisplay) {
goldDisplay.textContent = root.player.gold;
}
// Update affordability of remaining items
updateShopAffordability(root);
} else {
root.log("Not enough gold!");
}
});
});
if (shopRelic) {
root.app.querySelector("[data-buy-relic]").addEventListener("click", () => {
if (root.player.gold >= 100) {
root.player.gold -= 100;
root.log(`Bought ${shopRelic.name} for 100 gold.`);
import("../engine/battle.js").then(({ attachRelics }) => { // Note: Shop purchase events are now handled by InputManager
// Note: Leave shop event is handled by InputManager
const currentRelicIds = root.relicStates.map(r => r.id);
const newRelicIds = [...currentRelicIds, shopRelic.id];
attachRelics(root, newRelicIds);
});
root.app.querySelector("[data-buy-relic]").disabled = true;
root.app.querySelector("[data-buy-relic]").textContent = "SOLD";
// Update gold display
const goldDisplay = root.app.querySelector('.gold-amount');
if (goldDisplay) {
goldDisplay.textContent = root.player.gold;
}
// Update affordability of remaining items
updateShopAffordability(root);
} else {
root.log("Not enough gold!");
}
});
}
root.app.querySelector("[data-leave]").addEventListener("click", () => root.afterNode());
}); });
}); });
} }
@ -1071,56 +1024,7 @@ export function updateCardSelection(root) {
} }
} }
function updateShopAffordability(root) { // updateShopAffordability function moved to InputManager
// Update card affordability
root.app.querySelectorAll("[data-buy-card]").forEach(btn => {
if (!btn.disabled) {
const cardContainer = btn.closest('.shop-card-container');
const overlay = cardContainer.querySelector('.card-disabled-overlay');
if (root.player.gold < 50) {
btn.classList.remove('playable');
btn.classList.add('unplayable');
if (!overlay) {
const newOverlay = document.createElement('div');
newOverlay.className = 'card-disabled-overlay';
newOverlay.innerHTML = '<span>Need 50 gold</span>';
cardContainer.appendChild(newOverlay);
}
} else {
btn.classList.remove('unplayable');
btn.classList.add('playable');
if (overlay) {
overlay.remove();
}
}
}
});
// Update relic affordability
const relicBtn = root.app.querySelector("[data-buy-relic]");
if (relicBtn && !relicBtn.disabled) {
const relicContainer = relicBtn.closest('.shop-relic-container');
const overlay = relicContainer.querySelector('.relic-disabled-overlay');
if (root.player.gold < 100) {
relicBtn.classList.remove('affordable');
relicBtn.classList.add('unaffordable');
if (!overlay) {
const newOverlay = document.createElement('div');
newOverlay.className = 'relic-disabled-overlay';
newOverlay.innerHTML = '<span>Need 100 gold</span>';
relicContainer.appendChild(newOverlay);
}
} else {
relicBtn.classList.remove('unaffordable');
relicBtn.classList.add('affordable');
if (overlay) {
overlay.remove();
}
}
}
}
function shuffle(array) { function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) { for (let i = array.length - 1; i > 0; i--) {
@ -1553,6 +1457,59 @@ export async function renderWin(root) {
root.app.querySelector("[data-replay]").addEventListener("click", () => root.reset()); root.app.querySelector("[data-replay]").addEventListener("click", () => root.reset());
} }
export async function renderCodeReviewSelection(root, cards) {
const { CARDS } = await import("../data/cards.js");
if (!cards || cards.length === 0) {
root.log("No cards available for code review.");
return;
}
root.app.innerHTML = `
<div class="code-review-modal-overlay">
<div class="code-review-modal">
<div class="code-review-header">
<h2>🔍 Code Review</h2>
<p>Choose 1 card to add to your hand. The rest will go to the bottom of your deck.</p>
</div>
<div class="code-review-cards-container">
${cards.map((card, index) => {
const cardType = card.type === 'attack' ? 'attack' : card.type === 'skill' ? 'skill' : 'power';
return `
<div class="code-review-card" data-code-review-pick="${index}">
<div class="battle-card ${cardType} playable">
<div class="card-glow"></div>
<div class="card-frame">
<div class="card-header-row">
<div class="card-title">${card.name}</div>
<div class="card-cost-orb">${card.cost}</div>
</div>
<div class="card-artwork">
<div class="card-art-icon">${getCardArt(card.id, CARDS)}</div>
<div class="card-type-badge ${cardType}">${card.type}</div>
</div>
<div class="card-description-box">
<div class="card-text">${card.text}</div>
</div>
</div>
</div>
<div class="code-review-card-label">Click to choose</div>
</div>
`;
}).join('')}
</div>
<div class="code-review-footer">
<p>💡 Press ESC to cancel</p>
</div>
</div>
</div>
`;
}
export async function renderLose(root) { export async function renderLose(root) {
const { RELICS } = await import("../data/relics.js"); const { RELICS } = await import("../data/relics.js");
const finalStats = { const finalStats = {

131
style.css

@ -2116,6 +2116,7 @@ h3 {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
pointer-events: none; /* Allow clicks to pass through to elements behind */
color: #dc3545; color: #dc3545;
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
@ -4210,6 +4211,7 @@ h3 {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
border-radius: 16px; border-radius: 16px;
pointer-events: none; /* Allow clicks to pass through to elements behind */
color: #ff6b6b; color: #ff6b6b;
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
@ -5825,3 +5827,132 @@ h3 {
padding: 15px; padding: 15px;
} }
} }
/* Code Review Modal Styles */
.code-review-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(5px);
}
.code-review-modal {
background: linear-gradient(145deg, #2a2a2a, #1a1a1a);
border: 2px solid #4a4a4a;
border-radius: 15px;
padding: 30px;
max-width: 900px;
width: 90%;
max-height: 80vh;
overflow-y: auto;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.code-review-header {
text-align: center;
margin-bottom: 30px;
}
.code-review-header h2 {
color: #fff;
font-size: 1.8em;
margin: 0 0 10px 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
}
.code-review-header p {
color: #ccc;
font-size: 1.1em;
margin: 0;
}
.code-review-cards-container {
display: flex;
gap: 20px;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 20px;
}
.code-review-card {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
padding: 10px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.05);
border: 2px solid transparent;
}
.code-review-card:hover {
transform: translateY(-5px) scale(1.02);
box-shadow: 0 8px 25px rgba(0, 150, 255, 0.3);
border-color: #4a9eff;
background: rgba(74, 158, 255, 0.1);
}
.code-review-card .battle-card {
transform: scale(0.9);
margin-bottom: 10px;
}
.code-review-card-label {
color: #ccc;
font-size: 0.9em;
text-align: center;
padding: 5px 10px;
background: rgba(0, 0, 0, 0.3);
border-radius: 15px;
border: 1px solid #666;
transition: all 0.2s ease;
}
.code-review-card:hover .code-review-card-label {
background: rgba(74, 158, 255, 0.2);
border-color: #4a9eff;
color: #fff;
}
.code-review-footer {
text-align: center;
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #444;
}
.code-review-footer p {
color: #888;
font-size: 0.9em;
margin: 0;
}
/* Responsive adjustments for code review modal */
@media (max-width: 768px) {
.code-review-modal {
padding: 20px;
width: 95%;
max-height: 90vh;
}
.code-review-cards-container {
flex-direction: column;
align-items: center;
}
.code-review-card .battle-card {
transform: scale(0.8);
}
.code-review-header h2 {
font-size: 1.5em;
}
}

Loading…
Cancel
Save