valtech-components 4.0.586 → 4.0.590

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -59,7 +59,7 @@ import fixWebmDuration from 'fix-webm-duration';
59
59
  * Current version of valtech-components.
60
60
  * This is automatically updated during the publish process.
61
61
  */
62
- const VERSION = '4.0.586';
62
+ const VERSION = '4.0.590';
63
63
 
64
64
  // Control de estado de refresco (singleton a nivel de módulo)
65
65
  let isRefreshing = false;
@@ -63672,6 +63672,9 @@ class ChessimalsAdventureComponent {
63672
63672
  t(key) {
63673
63673
  return this.i18n.t(key, 'MiniGames');
63674
63674
  }
63675
+ avatarSrc(profileId) {
63676
+ return `assets/games/chessimals/challenge-avatars/${profileId}.png`;
63677
+ }
63675
63678
  nodeIcon(type) {
63676
63679
  switch (type) {
63677
63680
  case 'battle':
@@ -63776,7 +63779,16 @@ class ChessimalsAdventureComponent {
63776
63779
  (click)="onNodeTap(nodeView)"
63777
63780
  type="button"
63778
63781
  >
63779
- <span class="adventure-node__icon">{{ nodeIcon(nodeView.node.type) }}</span>
63782
+ @if (nodeView.node.profileId) {
63783
+ <img
63784
+ class="adventure-node__avatar"
63785
+ [src]="avatarSrc(nodeView.node.profileId)"
63786
+ [alt]="t(nodeView.node.titleKey)"
63787
+ loading="lazy"
63788
+ />
63789
+ } @else {
63790
+ <span class="adventure-node__icon">{{ nodeIcon(nodeView.node.type) }}</span>
63791
+ }
63780
63792
  <span class="adventure-node__label">{{ t(nodeView.node.titleKey) }}</span>
63781
63793
  <span class="adventure-node__stars">
63782
63794
  @for (i of [0, 1, 2]; track i) {
@@ -63800,7 +63812,7 @@ class ChessimalsAdventureComponent {
63800
63812
  }
63801
63813
  </div>
63802
63814
  </div>
63803
- `, isInline: true, styles: [".adventure-hub{padding:0 0 40px}.adventure-hub__header{padding:20px 16px 24px;text-align:center}.adventure-hub__eyebrow{font-size:.75rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ion-color-medium);margin:0 0 6px}.adventure-hub__title{font-size:1.75rem;font-weight:800;margin:0 0 6px;color:var(--ion-text-color)}.adventure-hub__subtitle{font-size:.9rem;color:var(--ion-color-medium);margin:0 0 12px}.adventure-hub__stars{display:inline-flex;align-items:center;gap:4px;background:var(--ion-card-background, var(--ion-background-color));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:20px;padding:4px 14px;font-weight:700;font-size:.95rem;color:var(--ion-color-warning)}.adventure-hub__stars ion-icon{color:var(--ion-color-warning);font-size:1rem}.adventure-hub__acts{display:flex;flex-direction:column;gap:16px;padding:0 12px}.adventure-act{border-radius:16px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color));overflow:hidden}.adventure-act--locked{opacity:.5}.adventure-act--done{border-color:var(--ion-color-success)}.adventure-act__head{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.adventure-act__emoji{font-size:1.75rem;line-height:1;flex-shrink:0}.adventure-act__meta{flex:1;min-width:0}.adventure-act__name{font-size:1rem;font-weight:700;margin:0 0 2px;color:var(--ion-text-color)}.adventure-act__desc{font-size:.8rem;color:var(--ion-color-medium);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.adventure-act__lock{color:var(--ion-color-medium);font-size:1.25rem;flex-shrink:0}.adventure-act__progress{font-size:.8rem;font-weight:700;color:var(--ion-color-medium);flex-shrink:0}.adventure-act__nodes{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;padding:12px}.adventure-node{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 8px;border-radius:12px;border:1.5px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-background-color, #f8f8f8);cursor:pointer;text-align:center;transition:transform .12s ease,border-color .12s ease}.adventure-node:active{transform:scale(.97)}.adventure-node--available{border-color:var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 6%,transparent)}.adventure-node--completed{border-color:var(--ion-color-success);background:color-mix(in srgb,var(--ion-color-success) 6%,transparent)}.adventure-node--locked{opacity:.4;cursor:default}.adventure-node__icon{font-size:1.5rem;line-height:1}.adventure-node__label{font-size:.75rem;font-weight:600;color:var(--ion-text-color);line-height:1.2}.adventure-node__stars{display:flex;gap:1px}.adventure-node__star{font-size:.7rem;color:var(--ion-color-medium)}.adventure-node__star--filled{color:var(--ion-color-warning)}.adventure-node__badge{position:absolute;top:-4px;right:-4px;font-size:.6rem;padding:2px 5px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }] }); }
63815
+ `, isInline: true, styles: [".adventure-hub{padding:0 0 40px}.adventure-hub__header{padding:20px 16px 24px;text-align:center}.adventure-hub__eyebrow{font-size:.75rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ion-color-medium);margin:0 0 6px}.adventure-hub__title{font-size:1.75rem;font-weight:800;margin:0 0 6px;color:var(--ion-text-color)}.adventure-hub__subtitle{font-size:.9rem;color:var(--ion-color-medium);margin:0 0 12px}.adventure-hub__stars{display:inline-flex;align-items:center;gap:4px;background:var(--ion-card-background, var(--ion-background-color));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:20px;padding:4px 14px;font-weight:700;font-size:.95rem;color:var(--ion-color-warning)}.adventure-hub__stars ion-icon{color:var(--ion-color-warning);font-size:1rem}.adventure-hub__acts{display:flex;flex-direction:column;gap:16px;padding:0 12px}.adventure-act{border-radius:16px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color));overflow:hidden}.adventure-act--locked{opacity:.5}.adventure-act--done{border-color:var(--ion-color-success)}.adventure-act__head{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.adventure-act__emoji{font-size:1.75rem;line-height:1;flex-shrink:0}.adventure-act__meta{flex:1;min-width:0}.adventure-act__name{font-size:1rem;font-weight:700;margin:0 0 2px;color:var(--ion-text-color)}.adventure-act__desc{font-size:.8rem;color:var(--ion-color-medium);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.adventure-act__lock{color:var(--ion-color-medium);font-size:1.25rem;flex-shrink:0}.adventure-act__progress{font-size:.8rem;font-weight:700;color:var(--ion-color-medium);flex-shrink:0}.adventure-act__nodes{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;padding:12px}.adventure-node{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 8px;border-radius:12px;border:1.5px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-background-color, #f8f8f8);cursor:pointer;text-align:center;transition:transform .12s ease,border-color .12s ease}.adventure-node:active{transform:scale(.97)}.adventure-node--available{border-color:var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 6%,transparent)}.adventure-node--completed{border-color:var(--ion-color-success);background:color-mix(in srgb,var(--ion-color-success) 6%,transparent)}.adventure-node--locked{opacity:.4;cursor:default}.adventure-node__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;border:2px solid var(--ion-border-color, rgba(0, 0, 0, .1))}.adventure-node--available .adventure-node__avatar{border-color:var(--ion-color-primary)}.adventure-node--completed .adventure-node__avatar{border-color:var(--ion-color-success)}.adventure-node__icon{font-size:1.5rem;line-height:1}.adventure-node__label{font-size:.75rem;font-weight:600;color:var(--ion-text-color);line-height:1.2}.adventure-node__stars{display:flex;gap:1px}.adventure-node__star{font-size:.7rem;color:var(--ion-color-medium)}.adventure-node__star--filled{color:var(--ion-color-warning)}.adventure-node__badge{position:absolute;top:-4px;right:-4px;font-size:.6rem;padding:2px 5px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: IonBadge, selector: "ion-badge", inputs: ["color", "mode"] }] }); }
63804
63816
  }
63805
63817
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChessimalsAdventureComponent, decorators: [{
63806
63818
  type: Component,
@@ -63852,7 +63864,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
63852
63864
  (click)="onNodeTap(nodeView)"
63853
63865
  type="button"
63854
63866
  >
63855
- <span class="adventure-node__icon">{{ nodeIcon(nodeView.node.type) }}</span>
63867
+ @if (nodeView.node.profileId) {
63868
+ <img
63869
+ class="adventure-node__avatar"
63870
+ [src]="avatarSrc(nodeView.node.profileId)"
63871
+ [alt]="t(nodeView.node.titleKey)"
63872
+ loading="lazy"
63873
+ />
63874
+ } @else {
63875
+ <span class="adventure-node__icon">{{ nodeIcon(nodeView.node.type) }}</span>
63876
+ }
63856
63877
  <span class="adventure-node__label">{{ t(nodeView.node.titleKey) }}</span>
63857
63878
  <span class="adventure-node__stars">
63858
63879
  @for (i of [0, 1, 2]; track i) {
@@ -63876,7 +63897,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
63876
63897
  }
63877
63898
  </div>
63878
63899
  </div>
63879
- `, styles: [".adventure-hub{padding:0 0 40px}.adventure-hub__header{padding:20px 16px 24px;text-align:center}.adventure-hub__eyebrow{font-size:.75rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ion-color-medium);margin:0 0 6px}.adventure-hub__title{font-size:1.75rem;font-weight:800;margin:0 0 6px;color:var(--ion-text-color)}.adventure-hub__subtitle{font-size:.9rem;color:var(--ion-color-medium);margin:0 0 12px}.adventure-hub__stars{display:inline-flex;align-items:center;gap:4px;background:var(--ion-card-background, var(--ion-background-color));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:20px;padding:4px 14px;font-weight:700;font-size:.95rem;color:var(--ion-color-warning)}.adventure-hub__stars ion-icon{color:var(--ion-color-warning);font-size:1rem}.adventure-hub__acts{display:flex;flex-direction:column;gap:16px;padding:0 12px}.adventure-act{border-radius:16px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color));overflow:hidden}.adventure-act--locked{opacity:.5}.adventure-act--done{border-color:var(--ion-color-success)}.adventure-act__head{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.adventure-act__emoji{font-size:1.75rem;line-height:1;flex-shrink:0}.adventure-act__meta{flex:1;min-width:0}.adventure-act__name{font-size:1rem;font-weight:700;margin:0 0 2px;color:var(--ion-text-color)}.adventure-act__desc{font-size:.8rem;color:var(--ion-color-medium);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.adventure-act__lock{color:var(--ion-color-medium);font-size:1.25rem;flex-shrink:0}.adventure-act__progress{font-size:.8rem;font-weight:700;color:var(--ion-color-medium);flex-shrink:0}.adventure-act__nodes{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;padding:12px}.adventure-node{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 8px;border-radius:12px;border:1.5px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-background-color, #f8f8f8);cursor:pointer;text-align:center;transition:transform .12s ease,border-color .12s ease}.adventure-node:active{transform:scale(.97)}.adventure-node--available{border-color:var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 6%,transparent)}.adventure-node--completed{border-color:var(--ion-color-success);background:color-mix(in srgb,var(--ion-color-success) 6%,transparent)}.adventure-node--locked{opacity:.4;cursor:default}.adventure-node__icon{font-size:1.5rem;line-height:1}.adventure-node__label{font-size:.75rem;font-weight:600;color:var(--ion-text-color);line-height:1.2}.adventure-node__stars{display:flex;gap:1px}.adventure-node__star{font-size:.7rem;color:var(--ion-color-medium)}.adventure-node__star--filled{color:var(--ion-color-warning)}.adventure-node__badge{position:absolute;top:-4px;right:-4px;font-size:.6rem;padding:2px 5px}\n"] }]
63900
+ `, styles: [".adventure-hub{padding:0 0 40px}.adventure-hub__header{padding:20px 16px 24px;text-align:center}.adventure-hub__eyebrow{font-size:.75rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--ion-color-medium);margin:0 0 6px}.adventure-hub__title{font-size:1.75rem;font-weight:800;margin:0 0 6px;color:var(--ion-text-color)}.adventure-hub__subtitle{font-size:.9rem;color:var(--ion-color-medium);margin:0 0 12px}.adventure-hub__stars{display:inline-flex;align-items:center;gap:4px;background:var(--ion-card-background, var(--ion-background-color));border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));border-radius:20px;padding:4px 14px;font-weight:700;font-size:.95rem;color:var(--ion-color-warning)}.adventure-hub__stars ion-icon{color:var(--ion-color-warning);font-size:1rem}.adventure-hub__acts{display:flex;flex-direction:column;gap:16px;padding:0 12px}.adventure-act{border-radius:16px;border:1px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-card-background, var(--ion-background-color));overflow:hidden}.adventure-act--locked{opacity:.5}.adventure-act--done{border-color:var(--ion-color-success)}.adventure-act__head{display:flex;align-items:center;gap:10px;padding:14px 16px;border-bottom:1px solid var(--ion-border-color, rgba(0, 0, 0, .08))}.adventure-act__emoji{font-size:1.75rem;line-height:1;flex-shrink:0}.adventure-act__meta{flex:1;min-width:0}.adventure-act__name{font-size:1rem;font-weight:700;margin:0 0 2px;color:var(--ion-text-color)}.adventure-act__desc{font-size:.8rem;color:var(--ion-color-medium);margin:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.adventure-act__lock{color:var(--ion-color-medium);font-size:1.25rem;flex-shrink:0}.adventure-act__progress{font-size:.8rem;font-weight:700;color:var(--ion-color-medium);flex-shrink:0}.adventure-act__nodes{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:10px;padding:12px}.adventure-node{position:relative;display:flex;flex-direction:column;align-items:center;gap:4px;padding:12px 8px;border-radius:12px;border:1.5px solid var(--ion-border-color, rgba(0, 0, 0, .1));background:var(--ion-background-color, #f8f8f8);cursor:pointer;text-align:center;transition:transform .12s ease,border-color .12s ease}.adventure-node:active{transform:scale(.97)}.adventure-node--available{border-color:var(--ion-color-primary);background:color-mix(in srgb,var(--ion-color-primary) 6%,transparent)}.adventure-node--completed{border-color:var(--ion-color-success);background:color-mix(in srgb,var(--ion-color-success) 6%,transparent)}.adventure-node--locked{opacity:.4;cursor:default}.adventure-node__avatar{width:48px;height:48px;border-radius:50%;object-fit:cover;border:2px solid var(--ion-border-color, rgba(0, 0, 0, .1))}.adventure-node--available .adventure-node__avatar{border-color:var(--ion-color-primary)}.adventure-node--completed .adventure-node__avatar{border-color:var(--ion-color-success)}.adventure-node__icon{font-size:1.5rem;line-height:1}.adventure-node__label{font-size:.75rem;font-weight:600;color:var(--ion-text-color);line-height:1.2}.adventure-node__stars{display:flex;gap:1px}.adventure-node__star{font-size:.7rem;color:var(--ion-color-medium)}.adventure-node__star--filled{color:var(--ion-color-warning)}.adventure-node__badge{position:absolute;top:-4px;right:-4px;font-size:.6rem;padding:2px 5px}\n"] }]
63880
63901
  }], ctorParameters: () => [] });
63881
63902
 
63882
63903
  const ANIMALS$1 = ['eagle', 'bull', 'kangaroo', 'lion'];
@@ -68457,6 +68478,12 @@ class ChessimalsGameComponent {
68457
68478
  }
68458
68479
  backToHome() {
68459
68480
  this.cancelActiveGameplayFeedback();
68481
+ const adventureNode = this.adventureNodeId();
68482
+ if (adventureNode) {
68483
+ this.adventureNodeId.set('');
68484
+ void this.router.navigateByUrl('/mini-games/chessimals/adventure').catch(() => undefined);
68485
+ return;
68486
+ }
68460
68487
  this.screen.set('home');
68461
68488
  this.audio.flip();
68462
68489
  }
@@ -73992,7 +74019,14 @@ function canPlayCard(game, seat, card) {
73992
74019
  if (card.type === 'offering')
73993
74020
  return !player.offeringPlayedThisTurn;
73994
74021
  if (card.type === 'character') {
73995
- return readyOfferings(player) >= (card.cost ?? 0) && player.battleZone.length < CHRISTIANITY_BATTLE_SLOTS;
74022
+ return (readyOfferings(player) >= (card.cost ?? 0) &&
74023
+ player.battleZone.length < CHRISTIANITY_BATTLE_SLOTS);
74024
+ }
74025
+ if (card.type === 'action' ||
74026
+ card.type === 'wisdom' ||
74027
+ card.type === 'event' ||
74028
+ card.type === 'tool') {
74029
+ return readyOfferings(player) >= (card.cost ?? 0);
73996
74030
  }
73997
74031
  if (card.type === 'place')
73998
74032
  return true;
@@ -74028,7 +74062,7 @@ function playCardFromHand(game, seat, card, options = {}) {
74028
74062
  player.offeringPlayedThisTurn = true;
74029
74063
  }
74030
74064
  else if (card.type === 'character') {
74031
- const occupiedSlots = new Set(player.battleZone.map((character, index) => Number.isInteger(character.slot) ? character.slot ?? index : index));
74065
+ const occupiedSlots = new Set(player.battleZone.map((character, index) => Number.isInteger(character.slot) ? (character.slot ?? index) : index));
74032
74066
  const targetSlot = options.slotIndex ?? [0, 1, 2, 3].find(index => !occupiedSlots.has(index));
74033
74067
  if (targetSlot === undefined || occupiedSlots.has(targetSlot)) {
74034
74068
  return { game, played: false, playedOfferingValue: 0, spentOfferings: 0, log: [] };
@@ -74050,6 +74084,18 @@ function playCardFromHand(game, seat, card, options = {}) {
74050
74084
  });
74051
74085
  player.battleZone.sort((left, right) => (left.slot ?? 0) - (right.slot ?? 0));
74052
74086
  }
74087
+ else if (card.type === 'action' ||
74088
+ card.type === 'wisdom' ||
74089
+ card.type === 'event' ||
74090
+ card.type === 'tool') {
74091
+ spentOfferings = card.cost ?? 0;
74092
+ if (spentOfferings > 0) {
74093
+ spendOfferings(player, spentOfferings);
74094
+ }
74095
+ next.historyZone.push(card.id);
74096
+ next.players[seat] = player;
74097
+ resolveCardEffect(next, card, seat);
74098
+ }
74053
74099
  else if (card.type === 'place') {
74054
74100
  const previousPlace = next.placeZone[0];
74055
74101
  if (previousPlace) {
@@ -74086,9 +74132,7 @@ function resolveOfferingAbility(game, seat, offeringInstanceId, messages) {
74086
74132
  }
74087
74133
  const reserveGain = offeringAbilityReserveGain(liveOffering.abilityText, liveOffering.value);
74088
74134
  player.temporaryOfferingReserve = (player.temporaryOfferingReserve ?? 0) + reserveGain;
74089
- player.offerings = player.offerings.map(card => (card.instanceId === offeringInstanceId
74090
- ? { ...card, abilityUsedThisTurn: true }
74091
- : card));
74135
+ player.offerings = player.offerings.map(card => card.instanceId === offeringInstanceId ? { ...card, abilityUsedThisTurn: true } : card);
74092
74136
  player.leaderHp = Math.max(0, player.leaderHp - 1);
74093
74137
  next.revision++;
74094
74138
  const log = [`${liveOffering.name}: ${liveOffering.abilityName}.`];
@@ -74103,6 +74147,12 @@ function playCardLog(card, displayName) {
74103
74147
  return `${displayName} queda lista como ofrenda.`;
74104
74148
  if (card.type === 'place')
74105
74149
  return `${displayName} queda como lugar activo.`;
74150
+ if (card.type === 'action' || card.type === 'event')
74151
+ return `${displayName} fue jugada.`;
74152
+ if (card.type === 'wisdom')
74153
+ return `${displayName} fue revelada.`;
74154
+ if (card.type === 'tool')
74155
+ return `${displayName} fue equipada.`;
74106
74156
  return `${displayName} entra en batalla.`;
74107
74157
  }
74108
74158
  function resolveAttackLeader(game, attackerSeat, attackerInstanceId, messages) {
@@ -74117,7 +74167,9 @@ function resolveAttackLeader(game, attackerSeat, attackerInstanceId, messages) {
74117
74167
  const damage = attacker.attack;
74118
74168
  next.players[defenderSeat].leaderHp = Math.max(0, next.players[defenderSeat].leaderHp - damage);
74119
74169
  attacker.canAttack = false;
74120
- const log = [`${attacker.name} ${attackerSeat === 'P1' ? messages.attacksOpponentLeader : messages.attacksYourLeader}`];
74170
+ const log = [
74171
+ `${attacker.name} ${attackerSeat === 'P1' ? messages.attacksOpponentLeader : messages.attacksYourLeader}`,
74172
+ ];
74121
74173
  evaluateVictory(next, log, messages);
74122
74174
  next.revision++;
74123
74175
  next.log = [...log, ...next.log].slice(0, 4);
@@ -74142,7 +74194,13 @@ function resolveAttackCharacter(game, attackerSeat, attackerInstanceId, targetIn
74142
74194
  evaluateVictory(next, log, messages);
74143
74195
  next.revision++;
74144
74196
  next.log = [...log, ...next.log].slice(0, 4);
74145
- return { game: next, attacked: true, attackerDamage: target.attack, targetDamage: attacker.attack, log };
74197
+ return {
74198
+ game: next,
74199
+ attacked: true,
74200
+ attackerDamage: target.attack,
74201
+ targetDamage: attacker.attack,
74202
+ log,
74203
+ };
74146
74204
  }
74147
74205
  function advanceTurn(game, messages) {
74148
74206
  game.players[game.turnOwner].temporaryOfferingReserve = 0;
@@ -74150,7 +74208,11 @@ function advanceTurn(game, messages) {
74150
74208
  game.turnCount++;
74151
74209
  const current = game.players[game.turnOwner];
74152
74210
  current.offeringPlayedThisTurn = false;
74153
- current.offerings = current.offerings.map(offering => ({ ...offering, ready: true, abilityUsedThisTurn: false }));
74211
+ current.offerings = current.offerings.map(offering => ({
74212
+ ...offering,
74213
+ ready: true,
74214
+ abilityUsedThisTurn: false,
74215
+ }));
74154
74216
  current.battleZone = current.battleZone.map(card => ({ ...card, canAttack: true }));
74155
74217
  const drawnCard = game.sharedDeck.shift();
74156
74218
  const log = [];
@@ -74172,16 +74234,16 @@ function advanceTurn(game, messages) {
74172
74234
  ];
74173
74235
  }
74174
74236
  function evaluateVictory(game, log, messages) {
74175
- if (game.players.P1.leaderHp > 0
74176
- && game.players.P2.leaderHp > 0
74177
- && game.sharedDeck.length > 0)
74237
+ if (game.players.P1.leaderHp > 0 && game.players.P2.leaderHp > 0 && game.sharedDeck.length > 0)
74178
74238
  return;
74179
74239
  game.status = 'finished';
74180
74240
  const winner = game.players.P1.leaderHp <= 0
74181
74241
  ? 'P2'
74182
74242
  : game.players.P2.leaderHp <= 0
74183
74243
  ? 'P1'
74184
- : game.players.P1.leaderHp >= game.players.P2.leaderHp ? 'P1' : 'P2';
74244
+ : game.players.P1.leaderHp >= game.players.P2.leaderHp
74245
+ ? 'P1'
74246
+ : 'P2';
74185
74247
  game.matchScore[winner] += 1;
74186
74248
  log.push(winner === 'P1' ? messages.youWin : messages.opponentWins);
74187
74249
  }
@@ -74215,6 +74277,144 @@ function offeringAbilityReserveGain(abilityText, fallbackValue) {
74215
74277
  function opponentSeat(seat) {
74216
74278
  return seat === 'P1' ? 'P2' : 'P1';
74217
74279
  }
74280
+ // --- Effect resolution (H7) ---
74281
+ function resolveCardEffect(game, card, seat) {
74282
+ const text = card.abilities?.[0]?.text ?? '';
74283
+ switch (card.strategy) {
74284
+ case 'directdamage':
74285
+ effectDirectDamage(game, card, text, seat);
74286
+ break;
74287
+ case 'restore':
74288
+ effectRestore(game, card, text, seat);
74289
+ break;
74290
+ case 'draw':
74291
+ effectDraw(game, card, text, seat);
74292
+ break;
74293
+ case 'discard':
74294
+ effectDiscard(game, card, text, seat);
74295
+ break;
74296
+ }
74297
+ }
74298
+ function effectDirectDamage(game, card, text, seat) {
74299
+ const opponent = opponentSeat(seat);
74300
+ const lower = text.toLowerCase();
74301
+ let dmg;
74302
+ if (lower.includes('tantos')) {
74303
+ dmg = game.players[seat].battleZone.length;
74304
+ }
74305
+ else {
74306
+ dmg = parseSpanishNumber(text);
74307
+ }
74308
+ if (dmg <= 0)
74309
+ return;
74310
+ game.players[opponent].leaderHp = Math.max(0, game.players[opponent].leaderHp - dmg);
74311
+ }
74312
+ function effectRestore(game, card, text, seat) {
74313
+ const lower = text.toLowerCase();
74314
+ const n = Math.max(1, parseSpanishNumber(text) || 1);
74315
+ if (lower.includes('los líderes') || lower.includes('los lideres')) {
74316
+ for (const s of ['P1', 'P2']) {
74317
+ const p = game.players[s];
74318
+ p.leaderHp = Math.min(p.leaderMaxHp ?? 30, p.leaderHp + n);
74319
+ }
74320
+ return;
74321
+ }
74322
+ if (lower.includes('tus personajes') || lower.includes('personajes en juego')) {
74323
+ for (const ch of game.players[seat].battleZone) {
74324
+ ch.currentHp = Math.min(ch.maxHp, ch.currentHp + n);
74325
+ }
74326
+ return;
74327
+ }
74328
+ if (lower.includes('del líder') ||
74329
+ lower.includes('al líder') ||
74330
+ lower.includes('del lider') ||
74331
+ lower.includes('al lider')) {
74332
+ const p = game.players[seat];
74333
+ p.leaderHp = Math.min(p.leaderMaxHp ?? 30, p.leaderHp + n);
74334
+ }
74335
+ }
74336
+ function effectDraw(game, card, text, seat) {
74337
+ const lower = text.toLowerCase();
74338
+ if (lower.includes('los jugadores')) {
74339
+ const n = Math.max(1, parseSpanishNumber(text) || 1);
74340
+ for (let i = 0; i < n; i++) {
74341
+ drawCardFor(game, 'P1');
74342
+ drawCardFor(game, 'P2');
74343
+ }
74344
+ return;
74345
+ }
74346
+ if (lower.includes('por cada personaje oponente')) {
74347
+ const n = game.players[opponentSeat(seat)].battleZone.length;
74348
+ for (let i = 0; i < n; i++)
74349
+ drawCardFor(game, seat);
74350
+ return;
74351
+ }
74352
+ const n = Math.max(1, parseSpanishNumber(text) || 1);
74353
+ for (let i = 0; i < n; i++)
74354
+ drawCardFor(game, seat);
74355
+ }
74356
+ function effectDiscard(game, card, text, seat) {
74357
+ const lower = text.toLowerCase();
74358
+ const opponent = opponentSeat(seat);
74359
+ if (lower.includes('ambos jugadores descartan su mano') ||
74360
+ lower.includes('los jugadores descartan su mano')) {
74361
+ for (const s of ['P1', 'P2']) {
74362
+ const p = game.players[s];
74363
+ game.historyZone.push(...(p.hand ?? []));
74364
+ p.hand = [];
74365
+ }
74366
+ return;
74367
+ }
74368
+ if ((lower.includes('oponente') && lower.includes('azar')) ||
74369
+ lower.includes('tu oponente descarta')) {
74370
+ const n = Math.max(1, parseSpanishNumber(text) || 1);
74371
+ const op = game.players[opponent];
74372
+ const hand = op.hand ?? [];
74373
+ let seed = game.revision * 31 + game.turnCount * 7;
74374
+ for (let i = 0; i < n && hand.length > 0; i++) {
74375
+ seed = (seed * 1664525 + 1013904223) & 0xffffffff;
74376
+ const idx = Math.abs(seed) % hand.length;
74377
+ game.historyZone.push(hand[idx]);
74378
+ hand.splice(idx, 1);
74379
+ }
74380
+ op.hand = hand;
74381
+ }
74382
+ }
74383
+ function drawCardFor(game, seat) {
74384
+ const card = game.sharedDeck.shift();
74385
+ if (!card)
74386
+ return;
74387
+ const p = game.players[seat];
74388
+ if (p.hand)
74389
+ p.hand.push(card);
74390
+ else
74391
+ p.handCount = (p.handCount ?? 0) + 1;
74392
+ }
74393
+ function parseSpanishNumber(text) {
74394
+ const words = {
74395
+ un: 1,
74396
+ una: 1,
74397
+ uno: 1,
74398
+ dos: 2,
74399
+ tres: 3,
74400
+ cuatro: 4,
74401
+ cinco: 5,
74402
+ seis: 6,
74403
+ siete: 7,
74404
+ ocho: 8,
74405
+ nueve: 9,
74406
+ diez: 10,
74407
+ };
74408
+ const digitMatch = text.match(/\b(\d+)\b/);
74409
+ if (digitMatch)
74410
+ return parseInt(digitMatch[1], 10);
74411
+ const lower = text.toLowerCase();
74412
+ for (const [word, n] of Object.entries(words)) {
74413
+ if (new RegExp(`\\b${word}\\b`).test(lower))
74414
+ return n;
74415
+ }
74416
+ return 0;
74417
+ }
74218
74418
 
74219
74419
  class ChristianityCardComponent {
74220
74420
  constructor() {
@@ -74573,10 +74773,58 @@ const STARTER_CARDS = {
74573
74773
  author: 'MVP',
74574
74774
  rarity: 'normal',
74575
74775
  },
74576
- p_guard: { id: 'p_guard', name: 'Guard', type: 'character', cost: 2, attack: 1, life: 3, edition: 'GEN', collectorNumber: '003/020', year: '2026', author: 'MVP', rarity: 'normal' },
74577
- p_warrior: { id: 'p_warrior', name: 'Warrior', type: 'character', cost: 2, attack: 2, life: 2, edition: 'GEN', collectorNumber: '004/020', year: '2026', author: 'MVP', rarity: 'normal' },
74578
- p_elder: { id: 'p_elder', name: 'Elder', type: 'character', cost: 3, attack: 2, life: 4, edition: 'GEN', collectorNumber: '005/020', year: '2026', author: 'MVP', rarity: 'uncommon' },
74579
- p_champion: { id: 'p_champion', name: 'Champion', type: 'character', cost: 4, attack: 4, life: 4, edition: 'GEN', collectorNumber: '006/020', year: '2026', author: 'MVP', rarity: 'rare' },
74776
+ p_guard: {
74777
+ id: 'p_guard',
74778
+ name: 'Guard',
74779
+ type: 'character',
74780
+ cost: 2,
74781
+ attack: 1,
74782
+ life: 3,
74783
+ edition: 'GEN',
74784
+ collectorNumber: '003/020',
74785
+ year: '2026',
74786
+ author: 'MVP',
74787
+ rarity: 'normal',
74788
+ },
74789
+ p_warrior: {
74790
+ id: 'p_warrior',
74791
+ name: 'Warrior',
74792
+ type: 'character',
74793
+ cost: 2,
74794
+ attack: 2,
74795
+ life: 2,
74796
+ edition: 'GEN',
74797
+ collectorNumber: '004/020',
74798
+ year: '2026',
74799
+ author: 'MVP',
74800
+ rarity: 'normal',
74801
+ },
74802
+ p_elder: {
74803
+ id: 'p_elder',
74804
+ name: 'Elder',
74805
+ type: 'character',
74806
+ cost: 3,
74807
+ attack: 2,
74808
+ life: 4,
74809
+ edition: 'GEN',
74810
+ collectorNumber: '005/020',
74811
+ year: '2026',
74812
+ author: 'MVP',
74813
+ rarity: 'uncommon',
74814
+ },
74815
+ p_champion: {
74816
+ id: 'p_champion',
74817
+ name: 'Champion',
74818
+ type: 'character',
74819
+ cost: 4,
74820
+ attack: 4,
74821
+ life: 4,
74822
+ edition: 'GEN',
74823
+ collectorNumber: '006/020',
74824
+ year: '2026',
74825
+ author: 'MVP',
74826
+ rarity: 'rare',
74827
+ },
74580
74828
  };
74581
74829
  const STARTING_SHARED_DECK = [
74582
74830
  'o_simple',
@@ -74620,7 +74868,14 @@ const STARTING_GAME = {
74620
74868
  leaderHp: 15,
74621
74869
  leaderMaxHp: 15,
74622
74870
  offerings: [
74623
- { instanceId: 'p1_offering_001', cardId: 'o_simple', name: 'Ofrenda simple', owner: 'P1', ready: true, value: 1 },
74871
+ {
74872
+ instanceId: 'p1_offering_001',
74873
+ cardId: 'o_simple',
74874
+ name: 'Ofrenda simple',
74875
+ owner: 'P1',
74876
+ ready: true,
74877
+ value: 1,
74878
+ },
74624
74879
  {
74625
74880
  instanceId: 'p1_offering_002',
74626
74881
  cardId: 'o_forbidden',
@@ -74645,7 +74900,16 @@ const STARTING_GAME = {
74645
74900
  leaderName: 'Noah',
74646
74901
  leaderHp: 15,
74647
74902
  leaderMaxHp: 15,
74648
- offerings: [{ instanceId: 'p2_offering_001', cardId: 'o_simple', name: 'Ofrenda simple', owner: 'P2', ready: true, value: 1 }],
74903
+ offerings: [
74904
+ {
74905
+ instanceId: 'p2_offering_001',
74906
+ cardId: 'o_simple',
74907
+ name: 'Ofrenda simple',
74908
+ owner: 'P2',
74909
+ ready: true,
74910
+ value: 1,
74911
+ },
74912
+ ],
74649
74913
  offeringPlayedThisTurn: false,
74650
74914
  handCount: 4,
74651
74915
  battleZone: [
@@ -74680,7 +74944,9 @@ class ChristianityGameComponent {
74680
74944
  const nextIndex = (currentIndex + direction + items.length) % items.length;
74681
74945
  const item = items[nextIndex];
74682
74946
  if (item.kind === 'history') {
74683
- return this.definitionToRenderCard(this.cardDefinition(item.cardId), { statusText: this.t('christianityHistory') });
74947
+ return this.definitionToRenderCard(this.cardDefinition(item.cardId), {
74948
+ statusText: this.t('christianityHistory'),
74949
+ });
74684
74950
  }
74685
74951
  if (item.kind === 'hand') {
74686
74952
  return this.definitionToRenderCard(this.cardDefinition(item.cardId));
@@ -74787,7 +75053,9 @@ class ChristianityGameComponent {
74787
75053
  if (this.tutorialPhase() === 'coin') {
74788
75054
  return setup.coinWinner === 'P1' ? this.t('christianityCoinYouWon') : this.t('christianityCoinRivalWon');
74789
75055
  }
74790
- return setup.leaderPicker === 'P1' ? this.t('christianityChooseLeaderTitle') : this.t('christianityRivalChoosesLeaderTitle');
75056
+ return setup.leaderPicker === 'P1'
75057
+ ? this.t('christianityChooseLeaderTitle')
75058
+ : this.t('christianityRivalChoosesLeaderTitle');
74791
75059
  });
74792
75060
  this.setupBody = computed(() => {
74793
75061
  const setup = this.tutorialSetup();
@@ -74814,7 +75082,11 @@ class ChristianityGameComponent {
74814
75082
  this.hasOpponentGuards = computed(() => this.opponent().battleZone.length > 0);
74815
75083
  this.opponentHandCount = computed(() => this.opponent().handCount ?? this.opponent().hand?.length ?? 0);
74816
75084
  this.galleryCards = computed(() => {
74817
- const query = this.gallerySearch().trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
75085
+ const query = this.gallerySearch()
75086
+ .trim()
75087
+ .toLowerCase()
75088
+ .normalize('NFD')
75089
+ .replace(/[\u0300-\u036f]/g, '');
74818
75090
  const type = this.galleryType();
74819
75091
  return this.allCatalogCards()
74820
75092
  .filter(card => this.isClassicGalleryCard(card))
@@ -74849,12 +75121,12 @@ class ChristianityGameComponent {
74849
75121
  { value: 'place', label: this.t('christianityPlaceCard') },
74850
75122
  { value: 'event', label: this.t('christianityEventCard') },
74851
75123
  ]);
74852
- this.hasModalOverlay = computed(() => !!this.selectedPreview()
74853
- || !!this.selectedInfo()
74854
- || this.showHistory()
74855
- || this.showPlace()
74856
- || this.showGallery()
74857
- || !!this.offeringsSeat());
75124
+ this.hasModalOverlay = computed(() => !!this.selectedPreview() ||
75125
+ !!this.selectedInfo() ||
75126
+ this.showHistory() ||
75127
+ this.showPlace() ||
75128
+ this.showGallery() ||
75129
+ !!this.offeringsSeat());
74858
75130
  this.previewCarouselItems = computed(() => this.currentPreviewCarouselItems());
74859
75131
  this.canNavigatePreview = computed(() => this.previewCarouselItems().length > 1);
74860
75132
  this.previewPositionLabel = computed(() => {
@@ -74880,25 +75152,28 @@ class ChristianityGameComponent {
74880
75152
  canUse: this.canUsePreviewAbility(index),
74881
75153
  }))
74882
75154
  : text.abilityName || text.abilityText
74883
- ? [{
75155
+ ? [
75156
+ {
74884
75157
  index: 0,
74885
75158
  title: text.abilityName ?? this.t('christianityAbility'),
74886
75159
  body: text.abilityText ?? '',
74887
75160
  canUse: this.canUsePreviewAbility(0),
74888
- }]
74889
- : [{
75161
+ },
75162
+ ]
75163
+ : [
75164
+ {
74890
75165
  index: 0,
74891
75166
  title: this.t('christianityNoAbilityTitle'),
74892
75167
  body: this.t('christianityNoAbilityBody'),
74893
75168
  canUse: false,
74894
- }];
75169
+ },
75170
+ ];
74895
75171
  return abilities;
74896
75172
  });
74897
75173
  this.showPreviewAbilityTabs = computed(() => {
74898
75174
  const abilities = this.previewAbilities();
74899
- return abilities.length > 1
74900
- || !this.isPreviewAbilityPlaceholder(abilities[0])
74901
- && (abilities[0]?.title ?? '') !== this.t('christianityAbility');
75175
+ return (abilities.length > 1 ||
75176
+ (!this.isPreviewAbilityPlaceholder(abilities[0]) && (abilities[0]?.title ?? '') !== this.t('christianityAbility')));
74902
75177
  });
74903
75178
  this.showPreviewAbilities = computed(() => {
74904
75179
  const preview = this.selectedPreview();
@@ -75071,7 +75346,9 @@ class ChristianityGameComponent {
75071
75346
  return this.t('christianityWaitTurn');
75072
75347
  }
75073
75348
  if (attacker) {
75074
- return this.hasOpponentGuards() ? this.t('christianityChooseCharacterTarget') : this.t('christianityChooseTarget');
75349
+ return this.hasOpponentGuards()
75350
+ ? this.t('christianityChooseCharacterTarget')
75351
+ : this.t('christianityChooseTarget');
75075
75352
  }
75076
75353
  if (handCard) {
75077
75354
  if (!this.canPlay(handCard)) {
@@ -75080,12 +75357,18 @@ class ChristianityGameComponent {
75080
75357
  if (handCard.type === 'place') {
75081
75358
  return this.t('christianityChoosePlaceSlot');
75082
75359
  }
75360
+ if (handCard.type === 'action' ||
75361
+ handCard.type === 'wisdom' ||
75362
+ handCard.type === 'event' ||
75363
+ handCard.type === 'tool') {
75364
+ return this.t('christianityPlayCard');
75365
+ }
75083
75366
  return handCard.type === 'offering' ? this.t('christianityPlaceOffering') : this.t('christianityChooseSlot');
75084
75367
  }
75085
75368
  return this.t('christianityChooseCard');
75086
75369
  });
75087
75370
  this.i18n.registerDefaults('MiniGames', MINI_GAMES_I18N);
75088
- effect((onCleanup) => {
75371
+ effect(onCleanup => {
75089
75372
  if (!this.hasModalOverlay())
75090
75373
  return;
75091
75374
  const previousOverflow = this.document.body.style.overflow;
@@ -75141,7 +75424,7 @@ class ChristianityGameComponent {
75141
75424
  try {
75142
75425
  const response = await fetch(GENESIS_CATALOG_URL);
75143
75426
  if (response.ok) {
75144
- const catalog = await response.json();
75427
+ const catalog = (await response.json());
75145
75428
  const cards = (catalog.cards ?? []).map(card => this.normalizeCatalogCard(card, catalog));
75146
75429
  if (cards.length) {
75147
75430
  this.cardCatalog.set(Object.fromEntries(cards.map(card => [card.id, card])));
@@ -75171,19 +75454,19 @@ class ChristianityGameComponent {
75171
75454
  const editionIcon = card.editionIcon ?? catalog.edition?.icon ?? GENESIS_ICON_URL;
75172
75455
  const verseText = card.verseText ?? card.bible?.text;
75173
75456
  const scripture = card.scripture ?? this.formatScripture(card.bible);
75174
- const offeringValue = card.type === 'offering' ? card.offeringValue ?? card.life ?? card.cost ?? 1 : card.offeringValue;
75457
+ const offeringValue = card.type === 'offering' ? (card.offeringValue ?? card.life ?? card.cost ?? 1) : card.offeringValue;
75175
75458
  const life = card.type === 'leader'
75176
- ? card.leaderLife ?? card.life ?? 10
75459
+ ? (card.leaderLife ?? card.life ?? 10)
75177
75460
  : card.type === 'offering'
75178
75461
  ? offeringValue
75179
- : card.life ?? 0;
75462
+ : (card.life ?? 0);
75180
75463
  return {
75181
75464
  ...card,
75182
- cost: card.type === 'offering' ? 0 : card.cost ?? 0,
75465
+ cost: card.type === 'offering' ? 0 : (card.cost ?? 0),
75183
75466
  offeringValue,
75184
75467
  life,
75185
- leaderLife: card.type === 'leader' ? card.leaderLife ?? life : card.leaderLife,
75186
- attack: card.type === 'character' ? card.attack ?? 0 : card.attack,
75468
+ leaderLife: card.type === 'leader' ? (card.leaderLife ?? life) : card.leaderLife,
75469
+ attack: card.type === 'character' ? (card.attack ?? 0) : card.attack,
75187
75470
  abilityName: card.abilityName ?? card.abilities?.[0]?.name,
75188
75471
  abilityText: card.abilityText ?? card.abilities?.[0]?.text,
75189
75472
  verseText,
@@ -75205,21 +75488,27 @@ class ChristianityGameComponent {
75205
75488
  createTutorialGame(cards, options = {}) {
75206
75489
  const leaders = this.tutorialLeaderCards(cards);
75207
75490
  const playerLeader = this.cardById(options.playerLeaderId) ?? this.findCard(leaders, 'Adán') ?? leaders[0];
75208
- const opponentLeader = this.cardById(options.opponentLeaderId) ?? this.findCard(leaders, 'Noé') ?? leaders.find(card => card.id !== playerLeader?.id) ?? leaders[0];
75209
- const offerings = cards.filter(card => card.type === 'offering' && this.isClassicGalleryCard(card)).slice(0, TUTORIAL_OFFERING_LIMIT);
75491
+ const opponentLeader = this.cardById(options.opponentLeaderId) ??
75492
+ this.findCard(leaders, 'Noé') ??
75493
+ leaders.find(card => card.id !== playerLeader?.id) ??
75494
+ leaders[0];
75495
+ const offerings = cards
75496
+ .filter(card => card.type === 'offering' && this.isClassicGalleryCard(card))
75497
+ .slice(0, TUTORIAL_OFFERING_LIMIT);
75210
75498
  const places = cards.filter(card => card.type === 'place');
75211
75499
  const characters = cards
75212
- .filter(card => card.type === 'character' && this.isClassicGalleryCard(card) && (card.cost ?? 0) <= 4 && (card.attack ?? 0) >= 0 && (card.life ?? 0) > 0)
75500
+ .filter(card => card.type === 'character' &&
75501
+ this.isClassicGalleryCard(card) &&
75502
+ (card.cost ?? 0) <= 4 &&
75503
+ (card.attack ?? 0) >= 0 &&
75504
+ (card.life ?? 0) > 0)
75213
75505
  .slice(0, TUTORIAL_CHARACTER_LIMIT);
75214
- const tutorialDeck = [
75215
- ...offerings,
75216
- ...characters,
75217
- ...offerings,
75218
- ...characters.slice(0, 6),
75219
- ].slice(0, 28);
75506
+ const tutorialDeck = [...offerings, ...characters, ...offerings, ...characters.slice(0, 6)].slice(0, 28);
75220
75507
  const shuffledDeck = this.prepareTutorialOpeningDeck(this.shuffleCards(tutorialDeck, options.seed ?? TUTORIAL_SEED));
75221
75508
  const playerHand = shuffledDeck.slice(0, TUTORIAL_OPENING_HAND_SIZE).map(card => card.id);
75222
- const opponentHand = shuffledDeck.slice(TUTORIAL_OPENING_HAND_SIZE, TUTORIAL_OPENING_HAND_SIZE * 2).map(card => card.id);
75509
+ const opponentHand = shuffledDeck
75510
+ .slice(TUTORIAL_OPENING_HAND_SIZE, TUTORIAL_OPENING_HAND_SIZE * 2)
75511
+ .map(card => card.id);
75223
75512
  const sharedDeck = shuffledDeck.slice(TUTORIAL_OPENING_HAND_SIZE * 2).map(card => card.id);
75224
75513
  return {
75225
75514
  ...structuredClone(STARTING_GAME),
@@ -75289,7 +75578,7 @@ class ChristianityGameComponent {
75289
75578
  return cards.find(card => this.translatedCardText(card).name.toLowerCase().includes(namePart.toLowerCase()));
75290
75579
  }
75291
75580
  cardById(cardId) {
75292
- return cardId ? this.cardCatalog()[cardId] ?? STARTER_CARDS[cardId] : undefined;
75581
+ return cardId ? (this.cardCatalog()[cardId] ?? STARTER_CARDS[cardId]) : undefined;
75293
75582
  }
75294
75583
  tutorialLeaderCards(cards) {
75295
75584
  const leaders = cards
@@ -75391,7 +75680,7 @@ class ChristianityGameComponent {
75391
75680
  type: card.type,
75392
75681
  typeLabel: this.cardTypeLabel(card),
75393
75682
  cost: card.cost,
75394
- offeringValue: card.type === 'offering' ? card.offeringValue ?? card.life : undefined,
75683
+ offeringValue: card.type === 'offering' ? (card.offeringValue ?? card.life) : undefined,
75395
75684
  attack: card.type === 'character' ? card.attack : undefined,
75396
75685
  life: card.life,
75397
75686
  currentHp: card.life,
@@ -75529,8 +75818,17 @@ class ChristianityGameComponent {
75529
75818
  return this.cardTypeIconUrlForType('character');
75530
75819
  }
75531
75820
  cardTypeIconUrlForType(type) {
75532
- const normalized = type.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
75533
- const icon = normalized === 'lider' ? 'leader' : normalized === 'personaje' ? 'character' : normalized === 'ofrenda' ? 'offering' : normalized;
75821
+ const normalized = type
75822
+ .toLowerCase()
75823
+ .normalize('NFD')
75824
+ .replace(/[\u0300-\u036f]/g, '');
75825
+ const icon = normalized === 'lider'
75826
+ ? 'leader'
75827
+ : normalized === 'personaje'
75828
+ ? 'character'
75829
+ : normalized === 'ofrenda'
75830
+ ? 'offering'
75831
+ : normalized;
75534
75832
  return `assets/games/christianity/icons/${icon}.png`;
75535
75833
  }
75536
75834
  offeringPips(value) {
@@ -75543,7 +75841,7 @@ class ChristianityGameComponent {
75543
75841
  const cards = this.game().players[seat].battleZone;
75544
75842
  const positionedCards = new Map();
75545
75843
  cards.forEach((card, index) => {
75546
- const slot = Number.isInteger(card.slot) ? card.slot ?? index : index;
75844
+ const slot = Number.isInteger(card.slot) ? (card.slot ?? index) : index;
75547
75845
  if (slot >= 0 && slot < 4 && !positionedCards.has(slot)) {
75548
75846
  positionedCards.set(slot, card);
75549
75847
  }
@@ -75557,13 +75855,20 @@ class ChristianityGameComponent {
75557
75855
  return `christianity-battle-slot-${index}`;
75558
75856
  }
75559
75857
  dropTargetIds() {
75560
- return ['christianity-offerings-drop', 'christianity-place-drop', ...this.battleSlots('P1').map((_, index) => this.battleSlotDropId(index))];
75858
+ return [
75859
+ 'christianity-offerings-drop',
75860
+ 'christianity-place-drop',
75861
+ ...this.battleSlots('P1').map((_, index) => this.battleSlotDropId(index)),
75862
+ ];
75561
75863
  }
75562
75864
  ownAttackerDropSourceIds() {
75563
75865
  return ['christianity-player-battle-source'];
75564
75866
  }
75565
75867
  attackTargetDropIds() {
75566
- return ['christianity-opponent-leader-drop', ...this.opponent().battleZone.map(card => this.opponentTargetDropId(card))];
75868
+ return [
75869
+ 'christianity-opponent-leader-drop',
75870
+ ...this.opponent().battleZone.map(card => this.opponentTargetDropId(card)),
75871
+ ];
75567
75872
  }
75568
75873
  opponentTargetDropId(card) {
75569
75874
  return `christianity-opponent-target-${card.instanceId}`;
@@ -75699,7 +76004,11 @@ class ChristianityGameComponent {
75699
76004
  return `christianity-use-offering-${offering.owner}-${this.testIdPart(offering.instanceId)}`;
75700
76005
  }
75701
76006
  testIdPart(value) {
75702
- return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 48) || 'item';
76007
+ return (value
76008
+ .toLowerCase()
76009
+ .replace(/[^a-z0-9]+/g, '-')
76010
+ .replace(/^-|-$/g, '')
76011
+ .slice(0, 48) || 'item');
75703
76012
  }
75704
76013
  canPlaySelected() {
75705
76014
  const selected = this.selectedHandCard();
@@ -75815,10 +76124,10 @@ class ChristianityGameComponent {
75815
76124
  this.attackLeader();
75816
76125
  }
75817
76126
  isValidAttackerInstance(instanceId) {
75818
- return this.game().status === 'in_progress'
75819
- && this.isOwnTurn()
75820
- && !this.combatAnimation()
75821
- && this.player().battleZone.some(card => card.instanceId === instanceId && card.canAttack);
76127
+ return (this.game().status === 'in_progress' &&
76128
+ this.isOwnTurn() &&
76129
+ !this.combatAnimation() &&
76130
+ this.player().battleZone.some(card => card.instanceId === instanceId && card.canAttack));
75822
76131
  }
75823
76132
  selectHandCard(cardId) {
75824
76133
  const wasSelected = this.selectedHandCard() === cardId;
@@ -75831,9 +76140,7 @@ class ChristianityGameComponent {
75831
76140
  if (!cardId)
75832
76141
  return;
75833
76142
  const card = this.cardDefinition(cardId);
75834
- const resolvedSlotIndex = card.type === 'character' && slotIndex === undefined
75835
- ? this.firstAvailableBattleSlotIndex()
75836
- : slotIndex;
76143
+ const resolvedSlotIndex = card.type === 'character' && slotIndex === undefined ? this.firstAvailableBattleSlotIndex() : slotIndex;
75837
76144
  this.playCardFromHand(cardId, resolvedSlotIndex);
75838
76145
  }
75839
76146
  firstAvailableBattleSlotIndex() {
@@ -75914,7 +76221,12 @@ class ChristianityGameComponent {
75914
76221
  });
75915
76222
  return;
75916
76223
  }
75917
- this.startCombatAnimation({ attackerInstanceId: attacker.instanceId, attackerSeat: 'P1', targetSeat: 'P2', target: 'leader' });
76224
+ this.startCombatAnimation({
76225
+ attackerInstanceId: attacker.instanceId,
76226
+ attackerSeat: 'P1',
76227
+ targetSeat: 'P2',
76228
+ target: 'leader',
76229
+ });
75918
76230
  this.selectedAttacker.set(null);
75919
76231
  this.combatResolutionTimer = window.setTimeout(() => {
75920
76232
  let damage = 0;
@@ -76009,7 +76321,7 @@ class ChristianityGameComponent {
76009
76321
  this.resetTransientTableState();
76010
76322
  const setup = this.createTutorialSetup();
76011
76323
  this.tutorialSetup.set(setup);
76012
- this.selectedTutorialLeaderId.set(setup.leaderPicker === 'P1' ? setup.availableLeaders[0] ?? null : null);
76324
+ this.selectedTutorialLeaderId.set(setup.leaderPicker === 'P1' ? (setup.availableLeaders[0] ?? null) : null);
76013
76325
  this.leaderChoiceSeconds.set(TUTORIAL_LEADER_CHOICE_SECONDS);
76014
76326
  this.tutorialPhase.set('coin');
76015
76327
  this.screen.set('table');
@@ -76071,7 +76383,7 @@ class ChristianityGameComponent {
76071
76383
  return;
76072
76384
  this.selectTutorialLeader(leaderId);
76073
76385
  this.inspectHandCard(leaderId);
76074
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'leader-choice' } : preview);
76386
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'leader-choice' } : preview));
76075
76387
  }
76076
76388
  toggleOptions() {
76077
76389
  this.showOptions.update(value => !value);
@@ -76428,7 +76740,7 @@ class ChristianityGameComponent {
76428
76740
  }
76429
76741
  }
76430
76742
  combatAnimationAttacker(animation) {
76431
- return this.game().players[animation.attackerSeat].battleZone.find(card => card.instanceId === animation.attackerInstanceId) ?? null;
76743
+ return (this.game().players[animation.attackerSeat].battleZone.find(card => card.instanceId === animation.attackerInstanceId) ?? null);
76432
76744
  }
76433
76745
  combatAnimationTargetLabel(animation) {
76434
76746
  if (animation.target === 'leader') {
@@ -76467,7 +76779,7 @@ class ChristianityGameComponent {
76467
76779
  const dx = to.x - from.x;
76468
76780
  const dy = to.y - from.y;
76469
76781
  const length = Math.max(12, Math.hypot(dx, dy));
76470
- const angle = Math.atan2(dy, dx) * 180 / Math.PI;
76782
+ const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
76471
76783
  return {
76472
76784
  fromX: `${from.x}%`,
76473
76785
  fromY: `${from.y}%`,
@@ -76557,7 +76869,9 @@ class ChristianityGameComponent {
76557
76869
  const offeringId = hand.find(cardId => this.cardDefinition(cardId).type === 'offering' && canPlayCard(game, 'P2', this.cardDefinition(cardId)));
76558
76870
  if (offeringId) {
76559
76871
  const card = this.cardDefinition(offeringId);
76560
- const result = playCardFromHand(game, 'P2', card, { displayName: this.translatedCardText(card).name });
76872
+ const result = playCardFromHand(game, 'P2', card, {
76873
+ displayName: this.translatedCardText(card).name,
76874
+ });
76561
76875
  if (result.played) {
76562
76876
  Object.assign(game, result.game);
76563
76877
  log.push(...result.log);
@@ -76628,7 +76942,9 @@ class ChristianityGameComponent {
76628
76942
  title: leader ? this.translatedCardText(leader).name : player.leaderName,
76629
76943
  eyebrow: this.t('christianityLeaderCard'),
76630
76944
  meta: `${player.leaderHp}/${player.leaderMaxHp}`,
76631
- body: seat === 'P2' && this.hasOpponentGuards() ? this.t('christianityLeaderProtected') : this.t('christianityLeaderBody'),
76945
+ body: seat === 'P2' && this.hasOpponentGuards()
76946
+ ? this.t('christianityLeaderProtected')
76947
+ : this.t('christianityLeaderBody'),
76632
76948
  cardId: player.leaderCardId,
76633
76949
  source: 'board',
76634
76950
  });
@@ -76703,7 +77019,7 @@ class ChristianityGameComponent {
76703
77019
  const preview = this.selectedPreview();
76704
77020
  if (!preview?.seat || !preview.offeringInstanceId)
76705
77021
  return null;
76706
- return this.game().players[preview.seat].offerings.find(card => card.instanceId === preview.offeringInstanceId) ?? null;
77022
+ return (this.game().players[preview.seat].offerings.find(card => card.instanceId === preview.offeringInstanceId) ?? null);
76707
77023
  }
76708
77024
  canUsePreviewAbility(index) {
76709
77025
  const offering = this.selectedPreviewOffering();
@@ -76724,7 +77040,9 @@ class ChristianityGameComponent {
76724
77040
  this.t('christianityDeckRuleShared'),
76725
77041
  this.t('christianityDeckRuleDraw'),
76726
77042
  this.t('christianityDeckRuleEmpty'),
76727
- nextCard ? `${this.t('christianityDeckNextCard')}: ${this.cardDisplayName(nextCard)}` : this.t('christianityDeckNoNextCard'),
77043
+ nextCard
77044
+ ? `${this.t('christianityDeckNextCard')}: ${this.cardDisplayName(nextCard)}`
77045
+ : this.t('christianityDeckNoNextCard'),
76728
77046
  ],
76729
77047
  });
76730
77048
  }
@@ -76739,15 +77057,12 @@ class ChristianityGameComponent {
76739
77057
  eyebrow: this.t('christianitySharedZone'),
76740
77058
  meta: this.t('christianityEmpty'),
76741
77059
  body: this.t('christianityPlaceRuleBody'),
76742
- facts: [
76743
- this.t('christianityPlaceSlotPlayable'),
76744
- this.t('christianityPlaceOnlyOne'),
76745
- ],
77060
+ facts: [this.t('christianityPlaceSlotPlayable'), this.t('christianityPlaceOnlyOne')],
76746
77061
  });
76747
77062
  }
76748
77063
  inspectPlaceCard(cardId) {
76749
77064
  this.inspectHandCard(cardId);
76750
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'place' } : preview);
77065
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'place' } : preview));
76751
77066
  }
76752
77067
  inspectHowToPlay() {
76753
77068
  this.selectedPreview.set(null);
@@ -76784,11 +77099,11 @@ class ChristianityGameComponent {
76784
77099
  }
76785
77100
  inspectGalleryCard(cardId) {
76786
77101
  this.inspectHandCard(cardId);
76787
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'gallery' } : preview);
77102
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'gallery' } : preview));
76788
77103
  }
76789
77104
  inspectHistoryCard(cardId) {
76790
77105
  this.inspectHandCard(cardId);
76791
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'history' } : preview);
77106
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'history' } : preview));
76792
77107
  }
76793
77108
  inspectCharacter(card) {
76794
77109
  this.selectedInfo.set(null);
@@ -76892,418 +77207,276 @@ class ChristianityGameComponent {
76892
77207
  </section>
76893
77208
  </section>
76894
77209
  } @else {
76895
- <section class="christianity-match-strip" data-testid="christianity-match-strip" [attr.aria-label]="t('christianityMatchScore')">
76896
- <article class="christianity-match-player">
76897
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
76898
- <div>
76899
- <strong>{{ player().displayName }}</strong>
76900
- <small>{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small>
76901
- </div>
76902
- </article>
76903
- <div class="christianity-scoreboard">
76904
- <span>{{ t('christianityMatch') }}</span>
76905
- <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
76906
- </div>
76907
- <article class="christianity-match-player christianity-match-player--opponent">
76908
- <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
76909
- <div>
76910
- <strong>{{ opponent().displayName }}</strong>
76911
- <small>{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{ opponent().leaderMaxHp }}</small>
77210
+ <section
77211
+ class="christianity-match-strip"
77212
+ data-testid="christianity-match-strip"
77213
+ [attr.aria-label]="t('christianityMatchScore')"
77214
+ >
77215
+ <article class="christianity-match-player">
77216
+ <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
77217
+ <div>
77218
+ <strong>{{ player().displayName }}</strong>
77219
+ <small
77220
+ >{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small
77221
+ >
77222
+ </div>
77223
+ </article>
77224
+ <div class="christianity-scoreboard">
77225
+ <span>{{ t('christianityMatch') }}</span>
77226
+ <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
76912
77227
  </div>
76913
- </article>
76914
- </section>
77228
+ <article class="christianity-match-player christianity-match-player--opponent">
77229
+ <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
77230
+ <div>
77231
+ <strong>{{ opponent().displayName }}</strong>
77232
+ <small
77233
+ >{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{
77234
+ opponent().leaderMaxHp
77235
+ }}</small
77236
+ >
77237
+ </div>
77238
+ </article>
77239
+ </section>
76915
77240
 
76916
- <section
76917
- class="christianity-turn-banner"
76918
- data-testid="christianity-turn-banner"
76919
- [class.is-own-turn]="isOwnTurn()"
76920
- [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
76921
- [class.is-finished]="game().status === 'finished'"
76922
- aria-live="polite"
76923
- >
76924
- <div>
76925
- <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
76926
- <strong>{{ turnBannerTitle() }}</strong>
76927
- <p>{{ turnBannerBody() }}</p>
76928
- </div>
76929
- <div class="christianity-turn-actions">
76930
- <button
76931
- type="button"
76932
- class="christianity-end-turn"
76933
- data-testid="christianity-end-turn"
76934
- [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
76935
- (click)="endTurn()"
76936
- >
76937
- {{ t('christianityEndTurn') }}
76938
- </button>
76939
- <button type="button" class="christianity-options-button" data-testid="christianity-options-button" valLightRipple (click)="toggleOptions()">
76940
- {{ t('christianityOptions') }}
76941
- </button>
76942
- </div>
76943
- @if (showOptions()) {
76944
- <div class="christianity-options-menu" data-testid="christianity-options-menu">
76945
- <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
76946
- <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">{{ t('christianityResign') }}</button>
76947
- <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
77241
+ <section
77242
+ class="christianity-turn-banner"
77243
+ data-testid="christianity-turn-banner"
77244
+ [class.is-own-turn]="isOwnTurn()"
77245
+ [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
77246
+ [class.is-finished]="game().status === 'finished'"
77247
+ aria-live="polite"
77248
+ >
77249
+ <div>
77250
+ <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
77251
+ <strong>{{ turnBannerTitle() }}</strong>
77252
+ <p>{{ turnBannerBody() }}</p>
76948
77253
  </div>
76949
- }
76950
- </section>
76951
-
76952
- <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
76953
- <div class="christianity-hand-meter christianity-hand-meter--opponent">
76954
- <span>{{ t('christianityHand') }}</span>
76955
- <strong>{{ opponentHandCount() }}</strong>
76956
- </div>
76957
- <section class="christianity-hand-fan christianity-hand-fan--opponent" data-testid="christianity-opponent-hand" [attr.aria-label]="t('christianityOpponentHand')">
76958
- @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
77254
+ <div class="christianity-turn-actions">
76959
77255
  <button
76960
77256
  type="button"
76961
- class="christianity-hidden-card-button christianity-render-card-button"
77257
+ class="christianity-end-turn"
77258
+ data-testid="christianity-end-turn"
77259
+ [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
77260
+ (click)="endTurn()"
77261
+ >
77262
+ {{ t('christianityEndTurn') }}
77263
+ </button>
77264
+ <button
77265
+ type="button"
77266
+ class="christianity-options-button"
77267
+ data-testid="christianity-options-button"
76962
77268
  valLightRipple
76963
- [style.transform]="fanTransform($index, opponentHandCount(), true)"
76964
- [attr.aria-label]="t('christianityHiddenCard')"
77269
+ (click)="toggleOptions()"
76965
77270
  >
76966
- <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
77271
+ {{ t('christianityOptions') }}
76967
77272
  </button>
77273
+ </div>
77274
+ @if (showOptions()) {
77275
+ <div class="christianity-options-menu" data-testid="christianity-options-menu">
77276
+ <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
77277
+ <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">
77278
+ {{ t('christianityResign') }}
77279
+ </button>
77280
+ <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
77281
+ </div>
76968
77282
  }
76969
77283
  </section>
76970
- </section>
76971
77284
 
76972
- <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
76973
- @if (tutorialPhase() !== 'in_progress' && tutorialPhase() !== 'finished') {
77285
+ <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
77286
+ <div class="christianity-hand-meter christianity-hand-meter--opponent">
77287
+ <span>{{ t('christianityHand') }}</span>
77288
+ <strong>{{ opponentHandCount() }}</strong>
77289
+ </div>
76974
77290
  <section
76975
- class="christianity-setup-panel"
76976
- [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
76977
- [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
76978
- data-testid="christianity-setup-panel"
76979
- aria-live="polite"
77291
+ class="christianity-hand-fan christianity-hand-fan--opponent"
77292
+ data-testid="christianity-opponent-hand"
77293
+ [attr.aria-label]="t('christianityOpponentHand')"
76980
77294
  >
76981
- @if (tutorialPhase() === 'coin') {
76982
- <div
76983
- class="christianity-setup-coin"
76984
- [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
76985
- aria-hidden="true"
76986
- >
76987
- <span></span>
76988
- <span></span>
76989
- </div>
76990
- }
76991
- <div>
76992
- <span>{{ t('christianityMatchSetup') }}</span>
76993
- <h2>{{ setupTitle() }}</h2>
76994
- <p>{{ setupBody() }}</p>
76995
- </div>
76996
- @if (tutorialPhase() === 'coin') {
76997
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
76998
- {{ t('christianityContinue') }}
76999
- </button>
77000
- }
77001
- @if (tutorialPhase() === 'leader_pick') {
77002
- @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
77003
- <div class="christianity-leader-choice-meter" aria-live="polite">
77004
- <span>{{ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString()) }}</span>
77005
- <i [style.transform]="'scaleX(' + (leaderChoiceSeconds() / leaderChoiceTotalSeconds) + ')'"></i>
77006
- </div>
77007
- <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
77008
- @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
77009
- @let leader = cardDefinition(leaderId);
77010
- <article class="christianity-leader-choice-option" [class.is-selected]="selectedTutorialLeaderId() === leaderId">
77011
- <button
77012
- type="button"
77013
- class="christianity-leader-choice-card christianity-render-card-button"
77014
- valLightRipple
77015
- [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
77016
- (click)="selectTutorialLeader(leaderId)"
77017
- >
77018
- <val-christianity-card [card]="definitionToRenderCard(leader)" variant="baby"></val-christianity-card>
77019
- </button>
77020
- <div>
77021
- <span>{{ selectedTutorialLeaderId() === leaderId ? t('christianityLeaderSelected') : t('christianitySelectLeader') }}</span>
77022
- <button type="button" class="christianity-leader-choice-detail" valLightRipple (click)="inspectLeaderChoice(leaderId)">
77023
- {{ t('christianityViewDetails') }}
77024
- </button>
77025
- </div>
77026
- </article>
77027
- }
77028
- </div>
77029
- <button type="button" class="christianity-primary-action" data-testid="christianity-confirm-leader" valLightRipple (click)="confirmTutorialLeader()">
77030
- {{ t('christianityChooseThisLeader') }}
77031
- </button>
77032
- } @else {
77033
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77034
- {{ t('christianityContinue') }}
77035
- </button>
77036
- }
77037
- }
77038
- </section>
77039
- }
77040
- @if (game().status === 'finished') {
77041
- <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
77042
- <span>{{ t('christianityGameOver') }}</span>
77043
- <h2>{{ resultTitle() }}</h2>
77044
- <p>{{ resultBody() }}</p>
77045
- <div>
77046
- <button type="button" class="christianity-primary-action" data-testid="christianity-play-again" valLightRipple (click)="restartTutorial()">
77047
- {{ t('christianityPlayAgain') }}
77048
- </button>
77049
- <button type="button" class="christianity-secondary-action" valLightRipple (click)="returnToChristianityHome()">
77050
- {{ t('christianityBackToMenu') }}
77051
- </button>
77052
- </div>
77053
- </section>
77054
- }
77055
- <div class="christianity-impact-layer">
77056
- @if (combatTrail(); as trail) {
77057
- <span
77058
- class="christianity-combat-trail"
77059
- aria-hidden="true"
77060
- [class.is-leader-target]="trail.tone === 'leader'"
77061
- [class.is-character-target]="trail.tone === 'character'"
77062
- [style.--christianity-combat-trail-x]="trail.fromX"
77063
- [style.--christianity-combat-trail-y]="trail.fromY"
77064
- [style.--christianity-combat-trail-mid-x]="trail.midX"
77065
- [style.--christianity-combat-trail-mid-y]="trail.midY"
77066
- [style.--christianity-combat-trail-length]="trail.length"
77067
- [style.--christianity-combat-trail-angle]="trail.angle"
77068
- >
77069
- <span></span>
77070
- </span>
77071
- }
77072
- @for (impact of impactEvents(); track impact.id) {
77073
- <val-mini-game-score-pop [props]="impactPopProps(impact)" />
77074
- }
77075
- @if (combatCallout(); as callout) {
77076
- <val-mini-game-callout [props]="callout" />
77077
- }
77078
- </div>
77079
- <section class="christianity-player-side christianity-player-side--opponent">
77080
- <section class="christianity-player-rack christianity-player-rack--opponent">
77081
- <div class="christianity-offerings-zone">
77082
- <button type="button" class="christianity-offering-pile" data-testid="christianity-offerings-P2" [class.is-empty-zone]="!topOffering('P2')" valLightRipple (click)="openOfferings('P2')">
77083
- @if (offeringStack('P2').length) {
77084
- <span class="christianity-zone-card-stack" aria-hidden="true">
77085
- @for (offering of offeringStack('P2'); track offering.instanceId) {
77086
- <val-christianity-card
77087
- [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77088
- [variant]="offering.ready ? 'baby' : 'back'"
77089
- [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77090
- ></val-christianity-card>
77091
- }
77092
- </span>
77093
- } @else {
77094
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77095
- }
77096
- <span>{{ t('christianityOfferingReserve') }}</span>
77097
- <strong>{{ readyOfferings(opponent()) }}</strong>
77098
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
77099
- </button>
77100
- </div>
77101
- <div
77102
- class="christianity-attack-drop-target"
77103
- cdkDropList
77104
- id="christianity-opponent-leader-drop"
77105
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77106
- [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
77107
- [cdkDropListSortingDisabled]="true"
77108
- (cdkDropListDropped)="dropOnOpponentLeader($event)"
77109
- >
77295
+ @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
77110
77296
  <button
77111
77297
  type="button"
77112
- class="christianity-leader-card christianity-render-card-button"
77113
- data-testid="christianity-leader-P2"
77298
+ class="christianity-hidden-card-button christianity-render-card-button"
77114
77299
  valLightRipple
77115
- [class.is-target]="isOpponentLeaderTarget()"
77116
- [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
77117
- [class.is-combat-target]="isCombatLeaderTarget('P2')"
77118
- (click)="handleOpponentLeader()"
77300
+ [style.transform]="fanTransform($index, opponentHandCount(), true)"
77301
+ [attr.aria-label]="t('christianityHiddenCard')"
77119
77302
  >
77120
- <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
77303
+ <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
77121
77304
  </button>
77122
- </div>
77123
- <span class="christianity-rack-spacer" aria-hidden="true"></span>
77305
+ }
77124
77306
  </section>
77307
+ </section>
77125
77308
 
77126
- <section class="christianity-battle-zone christianity-battle-zone--opponent" data-testid="christianity-battle-P2" [attr.aria-label]="t('christianityOpponentBattle')">
77127
- @for (slot of battleSlots('P2'); track slot.trackId) {
77128
- @if (slot.card) {
77309
+ <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
77310
+ @if (tutorialPhase() !== 'in_progress' && tutorialPhase() !== 'finished') {
77311
+ <section
77312
+ class="christianity-setup-panel"
77313
+ [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
77314
+ [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
77315
+ data-testid="christianity-setup-panel"
77316
+ aria-live="polite"
77317
+ >
77318
+ @if (tutorialPhase() === 'coin') {
77319
+ <div
77320
+ class="christianity-setup-coin"
77321
+ [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
77322
+ aria-hidden="true"
77323
+ >
77324
+ <span></span>
77325
+ <span></span>
77326
+ </div>
77327
+ }
77328
+ <div>
77329
+ <span>{{ t('christianityMatchSetup') }}</span>
77330
+ <h2>{{ setupTitle() }}</h2>
77331
+ <p>{{ setupBody() }}</p>
77332
+ </div>
77333
+ @if (tutorialPhase() === 'coin') {
77129
77334
  <button
77130
77335
  type="button"
77131
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77132
- [attr.data-testid]="battleCardTestId('P2', slot.card)"
77133
- cdkDropList
77134
- [id]="opponentTargetDropId(slot.card)"
77135
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77136
- [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
77137
- [cdkDropListSortingDisabled]="true"
77336
+ class="christianity-primary-action"
77337
+ data-testid="christianity-continue-setup"
77138
77338
  valLightRipple
77139
- [class.is-target]="isOpponentCharacterTarget(slot.card)"
77140
- [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
77141
- (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
77142
- (click)="handleOpponentCharacter(slot.card)"
77339
+ (click)="continueAfterCoin()"
77143
77340
  >
77144
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77341
+ {{ t('christianityContinue') }}
77145
77342
  </button>
77146
- } @else {
77147
- <span class="christianity-slot"></span>
77148
77343
  }
77149
- }
77150
- </section>
77151
- </section>
77152
-
77153
- <section class="christianity-shared-zone" data-testid="christianity-shared-zone" [attr.aria-label]="t('christianitySharedZone')">
77154
- <button type="button" class="christianity-zone-pile christianity-zone-pile--history" data-testid="christianity-history-zone" [class.is-empty-zone]="!topHistoryCard()" [class.has-new-card]="historyPulse()" valLightRipple (click)="openHistory()">
77155
- @if (historyStack().length) {
77156
- <span class="christianity-zone-card-stack" aria-hidden="true">
77157
- @for (cardId of historyStack(); track cardId + $index) {
77158
- <val-christianity-card
77159
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
77160
- variant="baby"
77161
- [style.transform]="zoneStackTransform($index, historyStack().length)"
77162
- ></val-christianity-card>
77344
+ @if (tutorialPhase() === 'leader_pick') {
77345
+ @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
77346
+ <div class="christianity-leader-choice-meter" aria-live="polite">
77347
+ <span>{{
77348
+ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString())
77349
+ }}</span>
77350
+ <i [style.transform]="'scaleX(' + leaderChoiceSeconds() / leaderChoiceTotalSeconds + ')'"></i>
77351
+ </div>
77352
+ <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
77353
+ @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
77354
+ @let leader = cardDefinition(leaderId);
77355
+ <article
77356
+ class="christianity-leader-choice-option"
77357
+ [class.is-selected]="selectedTutorialLeaderId() === leaderId"
77358
+ >
77359
+ <button
77360
+ type="button"
77361
+ class="christianity-leader-choice-card christianity-render-card-button"
77362
+ valLightRipple
77363
+ [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
77364
+ (click)="selectTutorialLeader(leaderId)"
77365
+ >
77366
+ <val-christianity-card
77367
+ [card]="definitionToRenderCard(leader)"
77368
+ variant="baby"
77369
+ ></val-christianity-card>
77370
+ </button>
77371
+ <div>
77372
+ <span>{{
77373
+ selectedTutorialLeaderId() === leaderId
77374
+ ? t('christianityLeaderSelected')
77375
+ : t('christianitySelectLeader')
77376
+ }}</span>
77377
+ <button
77378
+ type="button"
77379
+ class="christianity-leader-choice-detail"
77380
+ valLightRipple
77381
+ (click)="inspectLeaderChoice(leaderId)"
77382
+ >
77383
+ {{ t('christianityViewDetails') }}
77384
+ </button>
77385
+ </div>
77386
+ </article>
77387
+ }
77388
+ </div>
77389
+ <button
77390
+ type="button"
77391
+ class="christianity-primary-action"
77392
+ data-testid="christianity-confirm-leader"
77393
+ valLightRipple
77394
+ (click)="confirmTutorialLeader()"
77395
+ >
77396
+ {{ t('christianityChooseThisLeader') }}
77397
+ </button>
77398
+ } @else {
77399
+ <button
77400
+ type="button"
77401
+ class="christianity-primary-action"
77402
+ data-testid="christianity-continue-setup"
77403
+ valLightRipple
77404
+ (click)="continueAfterCoin()"
77405
+ >
77406
+ {{ t('christianityContinue') }}
77407
+ </button>
77163
77408
  }
77164
- </span>
77165
- } @else {
77166
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77167
- }
77168
- <span>{{ t('christianityHistory') }}</span>
77169
- <strong>{{ game().historyZone.length }}</strong>
77170
- </button>
77171
- <button type="button" class="christianity-zone-pile christianity-zone-pile--deck" data-testid="christianity-deck-zone" valLightRipple aria-live="polite" (click)="inspectDeck()">
77172
- <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
77173
- <i></i>
77174
- </span>
77175
- <span>{{ t('christianityDeck') }}</span>
77176
- <strong>{{ game().sharedDeck.length }}</strong>
77177
- </button>
77178
- <button
77179
- type="button"
77180
- class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
77181
- data-testid="christianity-place-zone"
77182
- cdkDropList
77183
- id="christianity-place-drop"
77184
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77185
- [cdkDropListEnterPredicate]="canDropOnPlace"
77186
- [cdkDropListSortingDisabled]="true"
77187
- [class.is-empty-zone]="!activePlaceCard()"
77188
- [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
77189
- valLightRipple
77190
- (cdkDropListDropped)="dropOnPlace($event)"
77191
- (click)="playSelectedPlaceOrInspect()"
77192
- >
77193
- @if (placeStack().length) {
77194
- @if (activePlaceCard(); as placeCard) {
77195
- <div class="christianity-place-backdrop" [style.background-image]="placeBackdropImage(placeCard)" aria-hidden="true"></div>
77196
77409
  }
77197
- <span class="christianity-zone-card-stack" aria-hidden="true">
77198
- @for (cardId of placeStack(); track cardId + $index) {
77199
- <val-christianity-card
77200
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
77201
- variant="baby"
77202
- [style.transform]="zoneStackTransform($index, placeStack().length)"
77203
- ></val-christianity-card>
77204
- }
77205
- </span>
77206
- } @else {
77207
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77208
- }
77209
- <span>{{ t('christianityPlace') }}</span>
77210
- <strong>{{ game().placeZone.length }}</strong>
77211
- @if (canPlaceSelectedPlace() || isDraggingPlace()) {
77212
- <em>{{ t('christianityPlayHere') }}</em>
77213
- }
77214
- </button>
77215
- </section>
77216
-
77217
- <section class="christianity-player-side">
77218
- <section
77219
- class="christianity-battle-zone"
77220
- data-testid="christianity-battle-P1"
77221
- cdkDropList
77222
- id="christianity-player-battle-source"
77223
- [cdkDropListConnectedTo]="attackTargetDropIds()"
77224
- [cdkDropListSortingDisabled]="true"
77225
- [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77226
- [attr.aria-label]="t('christianityYourBattle')"
77227
- >
77228
- @for (slot of battleSlots('P1'); track slot.trackId) {
77229
- @if (slot.card) {
77410
+ </section>
77411
+ }
77412
+ @if (game().status === 'finished') {
77413
+ <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
77414
+ <span>{{ t('christianityGameOver') }}</span>
77415
+ <h2>{{ resultTitle() }}</h2>
77416
+ <p>{{ resultBody() }}</p>
77417
+ <div>
77230
77418
  <button
77231
77419
  type="button"
77232
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77233
- [attr.data-testid]="battleCardTestId('P1', slot.card)"
77234
- cdkDrag
77235
- [cdkDragData]="slot.card.instanceId"
77236
- [cdkDragDisabled]="!canDragAttacker(slot.card)"
77420
+ class="christianity-primary-action"
77421
+ data-testid="christianity-play-again"
77237
77422
  valLightRipple
77238
- [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
77239
- [class.is-exhausted]="!slot.card.canAttack"
77240
- [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
77241
- [class.is-combat-attacker]="isCombatAttacker(slot.card)"
77242
- (cdkDragStarted)="startDraggingAttacker(slot.card)"
77243
- (cdkDragEnded)="endDraggingAttacker()"
77244
- (click)="selectAttacker(slot.card)"
77423
+ (click)="restartTutorial()"
77245
77424
  >
77246
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77247
- <ng-template cdkDragPreview>
77248
- <div class="christianity-card-drag-preview">
77249
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77250
- </div>
77251
- </ng-template>
77252
- <ng-template cdkDragPlaceholder>
77253
- <div class="christianity-card-drag-placeholder"></div>
77254
- </ng-template>
77425
+ {{ t('christianityPlayAgain') }}
77255
77426
  </button>
77256
- } @else {
77257
77427
  <button
77258
77428
  type="button"
77259
- class="christianity-slot"
77260
- [attr.data-testid]="battleSlotTestId('P1', $index)"
77261
- cdkDropList
77262
- [id]="battleSlotDropId($index)"
77263
- [cdkDropListData]="$index"
77264
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77265
- [cdkDropListEnterPredicate]="canDropOnBattleSlot"
77266
- [cdkDropListSortingDisabled]="true"
77267
- [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77429
+ class="christianity-secondary-action"
77268
77430
  valLightRipple
77269
- (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
77270
- (click)="playSelectedCard($index)"
77431
+ (click)="returnToChristianityHome()"
77271
77432
  >
77272
- @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
77273
- <span>{{ t('christianityPlayHere') }}</span>
77274
- }
77433
+ {{ t('christianityBackToMenu') }}
77275
77434
  </button>
77276
- }
77277
- }
77278
- </section>
77279
-
77280
- <section class="christianity-player-rack">
77281
- <div class="christianity-offerings-zone">
77282
- <div
77283
- class="christianity-offering-drop-target"
77284
- cdkDropList
77285
- id="christianity-offerings-drop"
77286
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77287
- [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
77288
- [cdkDropListEnterPredicate]="canDropOnOfferings"
77289
- [cdkDropListSortingDisabled]="true"
77290
- (cdkDropListDropped)="dropOnOfferings($event)"
77435
+ </div>
77436
+ </section>
77437
+ }
77438
+ <div class="christianity-impact-layer">
77439
+ @if (combatTrail(); as trail) {
77440
+ <span
77441
+ class="christianity-combat-trail"
77442
+ aria-hidden="true"
77443
+ [class.is-leader-target]="trail.tone === 'leader'"
77444
+ [class.is-character-target]="trail.tone === 'character'"
77445
+ [style.--christianity-combat-trail-x]="trail.fromX"
77446
+ [style.--christianity-combat-trail-y]="trail.fromY"
77447
+ [style.--christianity-combat-trail-mid-x]="trail.midX"
77448
+ [style.--christianity-combat-trail-mid-y]="trail.midY"
77449
+ [style.--christianity-combat-trail-length]="trail.length"
77450
+ [style.--christianity-combat-trail-angle]="trail.angle"
77291
77451
  >
77452
+ <span></span>
77453
+ </span>
77454
+ }
77455
+ @for (impact of impactEvents(); track impact.id) {
77456
+ <val-mini-game-score-pop [props]="impactPopProps(impact)" />
77457
+ }
77458
+ @if (combatCallout(); as callout) {
77459
+ <val-mini-game-callout [props]="callout" />
77460
+ }
77461
+ </div>
77462
+ <section class="christianity-player-side christianity-player-side--opponent">
77463
+ <section class="christianity-player-rack christianity-player-rack--opponent">
77464
+ <div class="christianity-offerings-zone">
77292
77465
  <button
77293
77466
  type="button"
77294
77467
  class="christianity-offering-pile"
77295
- data-testid="christianity-offerings-P1"
77296
- [class.is-empty-zone]="!topOffering('P1')"
77468
+ data-testid="christianity-offerings-P2"
77469
+ [class.is-empty-zone]="!topOffering('P2')"
77297
77470
  valLightRipple
77298
- (click)="handleOfferingPile('P1')"
77471
+ (click)="openOfferings('P2')"
77299
77472
  >
77300
- @if (offeringStack('P1').length) {
77473
+ @if (offeringStack('P2').length) {
77301
77474
  <span class="christianity-zone-card-stack" aria-hidden="true">
77302
- @for (offering of offeringStack('P1'); track offering.instanceId) {
77475
+ @for (offering of offeringStack('P2'); track offering.instanceId) {
77303
77476
  <val-christianity-card
77304
77477
  [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77305
77478
  [variant]="offering.ready ? 'baby' : 'back'"
77306
- [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
77479
+ [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77307
77480
  ></val-christianity-card>
77308
77481
  }
77309
77482
  </span>
@@ -77311,89 +77484,358 @@ class ChristianityGameComponent {
77311
77484
  <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77312
77485
  }
77313
77486
  <span>{{ t('christianityOfferingReserve') }}</span>
77314
- <strong>{{ readyOfferings(player()) }}</strong>
77315
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
77487
+ <strong>{{ readyOfferings(opponent()) }}</strong>
77488
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
77316
77489
  </button>
77317
77490
  </div>
77318
- </div>
77319
- <button type="button" class="christianity-leader-card christianity-render-card-button" data-testid="christianity-leader-P1" valLightRipple (click)="inspectLeader('P1')">
77320
- <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
77321
- </button>
77322
- <div class="christianity-player-command">
77323
- <div class="christianity-player-badge">
77324
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
77325
- <strong>{{ player().displayName }}</strong>
77491
+ <div
77492
+ class="christianity-attack-drop-target"
77493
+ cdkDropList
77494
+ id="christianity-opponent-leader-drop"
77495
+ [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77496
+ [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
77497
+ [cdkDropListSortingDisabled]="true"
77498
+ (cdkDropListDropped)="dropOnOpponentLeader($event)"
77499
+ >
77500
+ <button
77501
+ type="button"
77502
+ class="christianity-leader-card christianity-render-card-button"
77503
+ data-testid="christianity-leader-P2"
77504
+ valLightRipple
77505
+ [class.is-target]="isOpponentLeaderTarget()"
77506
+ [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
77507
+ [class.is-combat-target]="isCombatLeaderTarget('P2')"
77508
+ (click)="handleOpponentLeader()"
77509
+ >
77510
+ <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
77511
+ </button>
77326
77512
  </div>
77327
- </div>
77513
+ <span class="christianity-rack-spacer" aria-hidden="true"></span>
77514
+ </section>
77515
+
77516
+ <section
77517
+ class="christianity-battle-zone christianity-battle-zone--opponent"
77518
+ data-testid="christianity-battle-P2"
77519
+ [attr.aria-label]="t('christianityOpponentBattle')"
77520
+ >
77521
+ @for (slot of battleSlots('P2'); track slot.trackId) {
77522
+ @if (slot.card) {
77523
+ <button
77524
+ type="button"
77525
+ class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77526
+ [attr.data-testid]="battleCardTestId('P2', slot.card)"
77527
+ cdkDropList
77528
+ [id]="opponentTargetDropId(slot.card)"
77529
+ [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77530
+ [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
77531
+ [cdkDropListSortingDisabled]="true"
77532
+ valLightRipple
77533
+ [class.is-target]="isOpponentCharacterTarget(slot.card)"
77534
+ [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
77535
+ (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
77536
+ (click)="handleOpponentCharacter(slot.card)"
77537
+ >
77538
+ <val-christianity-card
77539
+ [card]="characterRenderCard(slot.card)"
77540
+ variant="board"
77541
+ ></val-christianity-card>
77542
+ </button>
77543
+ } @else {
77544
+ <span class="christianity-slot"></span>
77545
+ }
77546
+ }
77547
+ </section>
77328
77548
  </section>
77329
- </section>
77330
- </section>
77331
77549
 
77332
- <section class="christianity-hand-shelf">
77333
- <div class="christianity-hand-meter">
77334
- <span>{{ t('christianityHand') }}</span>
77335
- <strong>{{ player().hand?.length ?? 0 }}</strong>
77336
- </div>
77337
- <section
77338
- class="christianity-hand-fan"
77339
- data-testid="christianity-hand-P1"
77340
- cdkDropList
77341
- id="christianity-hand-source"
77342
- [cdkDropListConnectedTo]="dropTargetIds()"
77343
- [attr.aria-label]="t('christianityYourHand')"
77344
- (pointerleave)="clearHoveredHandIndex()"
77345
- >
77346
- @for (cardId of player().hand ?? []; track cardId + $index) {
77347
- @let card = cardDefinition(cardId);
77348
- <div
77349
- class="christianity-hand-card-wrap"
77350
- [class.is-selected]="selectedHandCard() === cardId"
77351
- [class.is-dragging]="draggingCardId() === cardId"
77352
- [style.z-index]="handCardZIndex($index)"
77353
- [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
77354
- (pointerenter)="setHoveredHandIndex($index)"
77355
- (pointerdown)="setHoveredHandIndex($index)"
77356
- (pointerup)="releaseHoveredHand($event)"
77550
+ <section
77551
+ class="christianity-shared-zone"
77552
+ data-testid="christianity-shared-zone"
77553
+ [attr.aria-label]="t('christianitySharedZone')"
77554
+ >
77555
+ <button
77556
+ type="button"
77557
+ class="christianity-zone-pile christianity-zone-pile--history"
77558
+ data-testid="christianity-history-zone"
77559
+ [class.is-empty-zone]="!topHistoryCard()"
77560
+ [class.has-new-card]="historyPulse()"
77561
+ valLightRipple
77562
+ (click)="openHistory()"
77563
+ >
77564
+ @if (historyStack().length) {
77565
+ <span class="christianity-zone-card-stack" aria-hidden="true">
77566
+ @for (cardId of historyStack(); track cardId + $index) {
77567
+ <val-christianity-card
77568
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
77569
+ variant="baby"
77570
+ [style.transform]="zoneStackTransform($index, historyStack().length)"
77571
+ ></val-christianity-card>
77572
+ }
77573
+ </span>
77574
+ } @else {
77575
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77576
+ }
77577
+ <span>{{ t('christianityHistory') }}</span>
77578
+ <strong>{{ game().historyZone.length }}</strong>
77579
+ </button>
77580
+ <button
77581
+ type="button"
77582
+ class="christianity-zone-pile christianity-zone-pile--deck"
77583
+ data-testid="christianity-deck-zone"
77584
+ valLightRipple
77585
+ aria-live="polite"
77586
+ (click)="inspectDeck()"
77587
+ >
77588
+ <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
77589
+ <i></i>
77590
+ </span>
77591
+ <span>{{ t('christianityDeck') }}</span>
77592
+ <strong>{{ game().sharedDeck.length }}</strong>
77593
+ </button>
77594
+ <button
77595
+ type="button"
77596
+ class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
77597
+ data-testid="christianity-place-zone"
77598
+ cdkDropList
77599
+ id="christianity-place-drop"
77600
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
77601
+ [cdkDropListEnterPredicate]="canDropOnPlace"
77602
+ [cdkDropListSortingDisabled]="true"
77603
+ [class.is-empty-zone]="!activePlaceCard()"
77604
+ [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
77605
+ valLightRipple
77606
+ (cdkDropListDropped)="dropOnPlace($event)"
77607
+ (click)="playSelectedPlaceOrInspect()"
77608
+ >
77609
+ @if (placeStack().length) {
77610
+ @if (activePlaceCard(); as placeCard) {
77611
+ <div
77612
+ class="christianity-place-backdrop"
77613
+ [style.background-image]="placeBackdropImage(placeCard)"
77614
+ aria-hidden="true"
77615
+ ></div>
77616
+ }
77617
+ <span class="christianity-zone-card-stack" aria-hidden="true">
77618
+ @for (cardId of placeStack(); track cardId + $index) {
77619
+ <val-christianity-card
77620
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
77621
+ variant="baby"
77622
+ [style.transform]="zoneStackTransform($index, placeStack().length)"
77623
+ ></val-christianity-card>
77624
+ }
77625
+ </span>
77626
+ } @else {
77627
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77628
+ }
77629
+ <span>{{ t('christianityPlace') }}</span>
77630
+ <strong>{{ game().placeZone.length }}</strong>
77631
+ @if (canPlaceSelectedPlace() || isDraggingPlace()) {
77632
+ <em>{{ t('christianityPlayHere') }}</em>
77633
+ }
77634
+ </button>
77635
+ </section>
77636
+
77637
+ <section class="christianity-player-side">
77638
+ <section
77639
+ class="christianity-battle-zone"
77640
+ data-testid="christianity-battle-P1"
77641
+ cdkDropList
77642
+ id="christianity-player-battle-source"
77643
+ [cdkDropListConnectedTo]="attackTargetDropIds()"
77644
+ [cdkDropListSortingDisabled]="true"
77645
+ [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77646
+ [attr.aria-label]="t('christianityYourBattle')"
77357
77647
  >
77648
+ @for (slot of battleSlots('P1'); track slot.trackId) {
77649
+ @if (slot.card) {
77650
+ <button
77651
+ type="button"
77652
+ class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77653
+ [attr.data-testid]="battleCardTestId('P1', slot.card)"
77654
+ cdkDrag
77655
+ [cdkDragData]="slot.card.instanceId"
77656
+ [cdkDragDisabled]="!canDragAttacker(slot.card)"
77657
+ valLightRipple
77658
+ [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
77659
+ [class.is-exhausted]="!slot.card.canAttack"
77660
+ [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
77661
+ [class.is-combat-attacker]="isCombatAttacker(slot.card)"
77662
+ (cdkDragStarted)="startDraggingAttacker(slot.card)"
77663
+ (cdkDragEnded)="endDraggingAttacker()"
77664
+ (click)="selectAttacker(slot.card)"
77665
+ >
77666
+ <val-christianity-card
77667
+ [card]="characterRenderCard(slot.card)"
77668
+ variant="board"
77669
+ ></val-christianity-card>
77670
+ <ng-template cdkDragPreview>
77671
+ <div class="christianity-card-drag-preview">
77672
+ <val-christianity-card
77673
+ [card]="characterRenderCard(slot.card)"
77674
+ variant="board"
77675
+ ></val-christianity-card>
77676
+ </div>
77677
+ </ng-template>
77678
+ <ng-template cdkDragPlaceholder>
77679
+ <div class="christianity-card-drag-placeholder"></div>
77680
+ </ng-template>
77681
+ </button>
77682
+ } @else {
77683
+ <button
77684
+ type="button"
77685
+ class="christianity-slot"
77686
+ [attr.data-testid]="battleSlotTestId('P1', $index)"
77687
+ cdkDropList
77688
+ [id]="battleSlotDropId($index)"
77689
+ [cdkDropListData]="$index"
77690
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
77691
+ [cdkDropListEnterPredicate]="canDropOnBattleSlot"
77692
+ [cdkDropListSortingDisabled]="true"
77693
+ [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77694
+ valLightRipple
77695
+ (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
77696
+ (click)="playSelectedCard($index)"
77697
+ >
77698
+ @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
77699
+ <span>{{ t('christianityPlayHere') }}</span>
77700
+ }
77701
+ </button>
77702
+ }
77703
+ }
77704
+ </section>
77705
+
77706
+ <section class="christianity-player-rack">
77707
+ <div class="christianity-offerings-zone">
77708
+ <div
77709
+ class="christianity-offering-drop-target"
77710
+ cdkDropList
77711
+ id="christianity-offerings-drop"
77712
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
77713
+ [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
77714
+ [cdkDropListEnterPredicate]="canDropOnOfferings"
77715
+ [cdkDropListSortingDisabled]="true"
77716
+ (cdkDropListDropped)="dropOnOfferings($event)"
77717
+ >
77718
+ <button
77719
+ type="button"
77720
+ class="christianity-offering-pile"
77721
+ data-testid="christianity-offerings-P1"
77722
+ [class.is-empty-zone]="!topOffering('P1')"
77723
+ valLightRipple
77724
+ (click)="handleOfferingPile('P1')"
77725
+ >
77726
+ @if (offeringStack('P1').length) {
77727
+ <span class="christianity-zone-card-stack" aria-hidden="true">
77728
+ @for (offering of offeringStack('P1'); track offering.instanceId) {
77729
+ <val-christianity-card
77730
+ [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77731
+ [variant]="offering.ready ? 'baby' : 'back'"
77732
+ [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
77733
+ ></val-christianity-card>
77734
+ }
77735
+ </span>
77736
+ } @else {
77737
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77738
+ }
77739
+ <span>{{ t('christianityOfferingReserve') }}</span>
77740
+ <strong>{{ readyOfferings(player()) }}</strong>
77741
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
77742
+ </button>
77743
+ </div>
77744
+ </div>
77358
77745
  <button
77359
77746
  type="button"
77360
- class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
77361
- [attr.data-testid]="handCardTestId(cardId, $index)"
77362
- cdkDrag
77747
+ class="christianity-leader-card christianity-render-card-button"
77748
+ data-testid="christianity-leader-P1"
77363
77749
  valLightRipple
77364
- [cdkDragData]="cardId"
77365
- [cdkDragDisabled]="!canPlay(card)"
77366
- [attr.aria-label]="handCardActionLabel(card)"
77367
- [class.is-selected]="selectedHandCard() === cardId"
77368
- [class.is-disabled]="!canPlay(card)"
77369
- (cdkDragStarted)="startDraggingCard(cardId)"
77370
- (cdkDragEnded)="endDraggingCard()"
77371
- (click)="selectHandCard(cardId)"
77750
+ (click)="inspectLeader('P1')"
77372
77751
  >
77373
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77374
- <ng-template cdkDragPreview>
77375
- <div class="christianity-card-drag-preview">
77376
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77377
- </div>
77378
- </ng-template>
77379
- <ng-template cdkDragPlaceholder>
77380
- <div class="christianity-card-drag-placeholder"></div>
77381
- </ng-template>
77752
+ <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
77382
77753
  </button>
77383
- <button
77384
- type="button"
77385
- class="christianity-card-eye"
77386
- [attr.aria-label]="t('christianityPreviewCard')"
77387
- (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
77388
- ></button>
77389
- </div>
77390
- }
77754
+ <div class="christianity-player-command">
77755
+ <div class="christianity-player-badge">
77756
+ <span class="christianity-avatar" [style.background]="player().avatarColor">{{
77757
+ player().avatar
77758
+ }}</span>
77759
+ <strong>{{ player().displayName }}</strong>
77760
+ </div>
77761
+ </div>
77762
+ </section>
77763
+ </section>
77764
+ </section>
77765
+
77766
+ <section class="christianity-hand-shelf">
77767
+ <div class="christianity-hand-meter">
77768
+ <span>{{ t('christianityHand') }}</span>
77769
+ <strong>{{ player().hand?.length ?? 0 }}</strong>
77770
+ </div>
77771
+ <section
77772
+ class="christianity-hand-fan"
77773
+ data-testid="christianity-hand-P1"
77774
+ cdkDropList
77775
+ id="christianity-hand-source"
77776
+ [cdkDropListConnectedTo]="dropTargetIds()"
77777
+ [attr.aria-label]="t('christianityYourHand')"
77778
+ (pointerleave)="clearHoveredHandIndex()"
77779
+ >
77780
+ @for (cardId of player().hand ?? []; track cardId + $index) {
77781
+ @let card = cardDefinition(cardId);
77782
+ <div
77783
+ class="christianity-hand-card-wrap"
77784
+ [class.is-selected]="selectedHandCard() === cardId"
77785
+ [class.is-dragging]="draggingCardId() === cardId"
77786
+ [style.z-index]="handCardZIndex($index)"
77787
+ [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
77788
+ (pointerenter)="setHoveredHandIndex($index)"
77789
+ (pointerdown)="setHoveredHandIndex($index)"
77790
+ (pointerup)="releaseHoveredHand($event)"
77791
+ >
77792
+ <button
77793
+ type="button"
77794
+ class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
77795
+ [attr.data-testid]="handCardTestId(cardId, $index)"
77796
+ cdkDrag
77797
+ valLightRipple
77798
+ [cdkDragData]="cardId"
77799
+ [cdkDragDisabled]="!canPlay(card)"
77800
+ [attr.aria-label]="handCardActionLabel(card)"
77801
+ [class.is-selected]="selectedHandCard() === cardId"
77802
+ [class.is-disabled]="!canPlay(card)"
77803
+ (cdkDragStarted)="startDraggingCard(cardId)"
77804
+ (cdkDragEnded)="endDraggingCard()"
77805
+ (click)="selectHandCard(cardId)"
77806
+ >
77807
+ <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77808
+ <ng-template cdkDragPreview>
77809
+ <div class="christianity-card-drag-preview">
77810
+ <val-christianity-card
77811
+ [card]="definitionToRenderCard(card)"
77812
+ variant="baby"
77813
+ ></val-christianity-card>
77814
+ </div>
77815
+ </ng-template>
77816
+ <ng-template cdkDragPlaceholder>
77817
+ <div class="christianity-card-drag-placeholder"></div>
77818
+ </ng-template>
77819
+ </button>
77820
+ <button
77821
+ type="button"
77822
+ class="christianity-card-eye"
77823
+ [attr.aria-label]="t('christianityPreviewCard')"
77824
+ (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
77825
+ ></button>
77826
+ </div>
77827
+ }
77828
+ </section>
77391
77829
  </section>
77392
- </section>
77393
77830
  }
77394
77831
 
77395
77832
  @if (selectedPreview()) {
77396
- <button type="button" class="christianity-preview-backdrop" [attr.aria-label]="t('christianityClosePreview')" (click)="closePreview()"></button>
77833
+ <button
77834
+ type="button"
77835
+ class="christianity-preview-backdrop"
77836
+ [attr.aria-label]="t('christianityClosePreview')"
77837
+ (click)="closePreview()"
77838
+ ></button>
77397
77839
  <aside
77398
77840
  class="christianity-floating-card-shell"
77399
77841
  [class.is-history-preview]="canNavigatePreview()"
@@ -77433,7 +77875,11 @@ class ChristianityGameComponent {
77433
77875
  </button>
77434
77876
  }
77435
77877
  @if (selectedPreviewNeighborRenderCard(-1); as previousCard) {
77436
- <val-christianity-card class="christianity-preview-ghost christianity-preview-ghost--previous" [card]="previousCard" variant="full"></val-christianity-card>
77878
+ <val-christianity-card
77879
+ class="christianity-preview-ghost christianity-preview-ghost--previous"
77880
+ [card]="previousCard"
77881
+ variant="full"
77882
+ ></val-christianity-card>
77437
77883
  }
77438
77884
  <div
77439
77885
  class="christianity-floating-card"
@@ -77452,7 +77898,11 @@ class ChristianityGameComponent {
77452
77898
  }
77453
77899
  </div>
77454
77900
  @if (selectedPreviewNeighborRenderCard(1); as nextCard) {
77455
- <val-christianity-card class="christianity-preview-ghost christianity-preview-ghost--next" [card]="nextCard" variant="full"></val-christianity-card>
77901
+ <val-christianity-card
77902
+ class="christianity-preview-ghost christianity-preview-ghost--next"
77903
+ [card]="nextCard"
77904
+ variant="full"
77905
+ ></val-christianity-card>
77456
77906
  }
77457
77907
  </div>
77458
77908
  <div class="christianity-preview-side">
@@ -77536,7 +77986,12 @@ class ChristianityGameComponent {
77536
77986
  }
77537
77987
 
77538
77988
  @if (selectedInfo(); as info) {
77539
- <button type="button" class="christianity-info-backdrop" [attr.aria-label]="t('close')" (click)="closeInfo()"></button>
77989
+ <button
77990
+ type="button"
77991
+ class="christianity-info-backdrop"
77992
+ [attr.aria-label]="t('close')"
77993
+ (click)="closeInfo()"
77994
+ ></button>
77540
77995
  <aside class="christianity-info-panel" aria-live="polite">
77541
77996
  <button type="button" class="christianity-dialog-close christianity-info-close" (click)="closeInfo()">
77542
77997
  {{ t('close') }}
@@ -77561,14 +78016,24 @@ class ChristianityGameComponent {
77561
78016
  @if (screen() === 'table') {
77562
78017
  <section class="christianity-actions" data-testid="christianity-actions" aria-live="polite">
77563
78018
  <p class="christianity-sr-only" data-testid="christianity-status">{{ statusText() }}</p>
77564
- <button type="button" data-testid="christianity-primary-action" [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()" (click)="playSelectedCard()">
78019
+ <button
78020
+ type="button"
78021
+ data-testid="christianity-primary-action"
78022
+ [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()"
78023
+ (click)="playSelectedCard()"
78024
+ >
77565
78025
  {{ selectedActionLabel() }}
77566
78026
  </button>
77567
78027
  </section>
77568
78028
  }
77569
78029
 
77570
78030
  @if (showHistory()) {
77571
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseHistory')" (click)="closeHistory()"></button>
78031
+ <button
78032
+ type="button"
78033
+ class="christianity-history-backdrop"
78034
+ [attr.aria-label]="t('christianityCloseHistory')"
78035
+ (click)="closeHistory()"
78036
+ ></button>
77572
78037
  <section class="christianity-history-drawer" data-testid="christianity-history-drawer">
77573
78038
  <header>
77574
78039
  <h2>{{ t('christianityHistory') }}</h2>
@@ -77583,7 +78048,13 @@ class ChristianityGameComponent {
77583
78048
  <div class="christianity-history-grid" data-testid="christianity-history-grid">
77584
78049
  @for (cardId of game().historyZone; track cardId + $index) {
77585
78050
  @let card = cardDefinition(cardId);
77586
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="historyCardTestId(cardId, $index)" valLightRipple (click)="inspectHistoryCard(cardId)">
78051
+ <button
78052
+ type="button"
78053
+ class="christianity-gallery-card-button christianity-render-card-button"
78054
+ [attr.data-testid]="historyCardTestId(cardId, $index)"
78055
+ valLightRipple
78056
+ (click)="inspectHistoryCard(cardId)"
78057
+ >
77587
78058
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77588
78059
  </button>
77589
78060
  }
@@ -77595,7 +78066,12 @@ class ChristianityGameComponent {
77595
78066
  }
77596
78067
 
77597
78068
  @if (showPlace()) {
77598
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseHistory')" (click)="closePlace()"></button>
78069
+ <button
78070
+ type="button"
78071
+ class="christianity-history-backdrop"
78072
+ [attr.aria-label]="t('christianityCloseHistory')"
78073
+ (click)="closePlace()"
78074
+ ></button>
77599
78075
  <section class="christianity-history-drawer christianity-place-drawer" data-testid="christianity-place-drawer">
77600
78076
  <header>
77601
78077
  <h2>{{ t('christianityPlace') }}</h2>
@@ -77608,8 +78084,17 @@ class ChristianityGameComponent {
77608
78084
  </header>
77609
78085
  <div class="christianity-history-grid" data-testid="christianity-place-grid">
77610
78086
  @for (cardId of game().placeZone; track cardId + $index) {
77611
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="'christianity-place-card-' + $index" valLightRipple (click)="inspectPlaceCard(cardId)">
77612
- <val-christianity-card [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })" variant="baby"></val-christianity-card>
78087
+ <button
78088
+ type="button"
78089
+ class="christianity-gallery-card-button christianity-render-card-button"
78090
+ [attr.data-testid]="'christianity-place-card-' + $index"
78091
+ valLightRipple
78092
+ (click)="inspectPlaceCard(cardId)"
78093
+ >
78094
+ <val-christianity-card
78095
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
78096
+ variant="baby"
78097
+ ></val-christianity-card>
77613
78098
  </button>
77614
78099
  }
77615
78100
  </div>
@@ -77617,8 +78102,16 @@ class ChristianityGameComponent {
77617
78102
  }
77618
78103
 
77619
78104
  @if (showGallery()) {
77620
- <button type="button" class="christianity-history-backdrop christianity-gallery-backdrop" [attr.aria-label]="t('christianityCloseGallery')" (click)="closeGallery()"></button>
77621
- <section class="christianity-history-drawer christianity-gallery-drawer" data-testid="christianity-gallery-drawer">
78105
+ <button
78106
+ type="button"
78107
+ class="christianity-history-backdrop christianity-gallery-backdrop"
78108
+ [attr.aria-label]="t('christianityCloseGallery')"
78109
+ (click)="closeGallery()"
78110
+ ></button>
78111
+ <section
78112
+ class="christianity-history-drawer christianity-gallery-drawer"
78113
+ data-testid="christianity-gallery-drawer"
78114
+ >
77622
78115
  <header>
77623
78116
  <div>
77624
78117
  <h2>{{ t('christianityGallery') }}</h2>
@@ -77634,7 +78127,12 @@ class ChristianityGameComponent {
77634
78127
  <div class="christianity-gallery-controls">
77635
78128
  <label>
77636
78129
  <span>{{ t('christianityGallerySearch') }}</span>
77637
- <input type="search" [value]="gallerySearch()" (input)="setGallerySearch($event)" [placeholder]="t('christianityGallerySearchPlaceholder')" />
78130
+ <input
78131
+ type="search"
78132
+ [value]="gallerySearch()"
78133
+ (input)="setGallerySearch($event)"
78134
+ [placeholder]="t('christianityGallerySearchPlaceholder')"
78135
+ />
77638
78136
  </label>
77639
78137
  <label>
77640
78138
  <span>{{ t('christianityGalleryFilter') }}</span>
@@ -77653,7 +78151,12 @@ class ChristianityGameComponent {
77653
78151
  </div>
77654
78152
  <div class="christianity-gallery-grid">
77655
78153
  @for (card of galleryCards(); track card.id) {
77656
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" valLightRipple (click)="inspectGalleryCard(card.id)">
78154
+ <button
78155
+ type="button"
78156
+ class="christianity-gallery-card-button christianity-render-card-button"
78157
+ valLightRipple
78158
+ (click)="inspectGalleryCard(card.id)"
78159
+ >
77657
78160
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77658
78161
  </button>
77659
78162
  } @empty {
@@ -77665,12 +78168,23 @@ class ChristianityGameComponent {
77665
78168
 
77666
78169
  @if (offeringsSeat()) {
77667
78170
  @let offeringPlayer = game().players[offeringsSeat()!];
77668
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseOfferings')" (click)="closeOfferings()"></button>
77669
- <section class="christianity-history-drawer christianity-offerings-drawer" [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)">
78171
+ <button
78172
+ type="button"
78173
+ class="christianity-history-backdrop"
78174
+ [attr.aria-label]="t('christianityCloseOfferings')"
78175
+ (click)="closeOfferings()"
78176
+ ></button>
78177
+ <section
78178
+ class="christianity-history-drawer christianity-offerings-drawer"
78179
+ [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)"
78180
+ >
77670
78181
  <header>
77671
78182
  <h2>{{ t('christianityOfferings') }}</h2>
77672
78183
  <div class="christianity-dialog-actions">
77673
- <span>{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} · {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span>
78184
+ <span
78185
+ >{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} ·
78186
+ {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span
78187
+ >
77674
78188
  <button type="button" class="christianity-dialog-close" (click)="closeOfferings()">
77675
78189
  {{ t('close') }}
77676
78190
  </button>
@@ -77680,14 +78194,25 @@ class ChristianityGameComponent {
77680
78194
  <span class="christianity-rule-note-icon" aria-hidden="true">i</span>
77681
78195
  <p>{{ t('christianityOfferingsRuleBody') }}</p>
77682
78196
  </div>
77683
- <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
77684
- @for (offering of offeringPlayer.offerings; track offering.instanceId) {
77685
- <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
77686
- <button type="button" class="christianity-offering-card christianity-render-card-button" [attr.data-testid]="offeringCardTestId(offering)" valLightRipple (click)="inspectOffering(offering)">
77687
- <val-christianity-card [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()" [variant]="offering.ready ? 'baby' : 'back'"></val-christianity-card>
78197
+ <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
78198
+ @for (offering of offeringPlayer.offerings; track offering.instanceId) {
78199
+ <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
78200
+ <button
78201
+ type="button"
78202
+ class="christianity-offering-card christianity-render-card-button"
78203
+ [attr.data-testid]="offeringCardTestId(offering)"
78204
+ valLightRipple
78205
+ (click)="inspectOffering(offering)"
78206
+ >
78207
+ <val-christianity-card
78208
+ [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
78209
+ [variant]="offering.ready ? 'baby' : 'back'"
78210
+ ></val-christianity-card>
77688
78211
  </button>
77689
78212
  <div>
77690
- <strong>{{ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted') }}</strong>
78213
+ <strong>{{
78214
+ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted')
78215
+ }}</strong>
77691
78216
  <p>{{ offeringAbilityText(offering) }}</p>
77692
78217
  @if (offering.abilityName) {
77693
78218
  <button
@@ -77796,418 +78321,276 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
77796
78321
  </section>
77797
78322
  </section>
77798
78323
  } @else {
77799
- <section class="christianity-match-strip" data-testid="christianity-match-strip" [attr.aria-label]="t('christianityMatchScore')">
77800
- <article class="christianity-match-player">
77801
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
77802
- <div>
77803
- <strong>{{ player().displayName }}</strong>
77804
- <small>{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small>
77805
- </div>
77806
- </article>
77807
- <div class="christianity-scoreboard">
77808
- <span>{{ t('christianityMatch') }}</span>
77809
- <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
77810
- </div>
77811
- <article class="christianity-match-player christianity-match-player--opponent">
77812
- <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
77813
- <div>
77814
- <strong>{{ opponent().displayName }}</strong>
77815
- <small>{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{ opponent().leaderMaxHp }}</small>
78324
+ <section
78325
+ class="christianity-match-strip"
78326
+ data-testid="christianity-match-strip"
78327
+ [attr.aria-label]="t('christianityMatchScore')"
78328
+ >
78329
+ <article class="christianity-match-player">
78330
+ <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
78331
+ <div>
78332
+ <strong>{{ player().displayName }}</strong>
78333
+ <small
78334
+ >{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small
78335
+ >
78336
+ </div>
78337
+ </article>
78338
+ <div class="christianity-scoreboard">
78339
+ <span>{{ t('christianityMatch') }}</span>
78340
+ <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
77816
78341
  </div>
77817
- </article>
77818
- </section>
78342
+ <article class="christianity-match-player christianity-match-player--opponent">
78343
+ <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
78344
+ <div>
78345
+ <strong>{{ opponent().displayName }}</strong>
78346
+ <small
78347
+ >{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{
78348
+ opponent().leaderMaxHp
78349
+ }}</small
78350
+ >
78351
+ </div>
78352
+ </article>
78353
+ </section>
77819
78354
 
77820
- <section
77821
- class="christianity-turn-banner"
77822
- data-testid="christianity-turn-banner"
77823
- [class.is-own-turn]="isOwnTurn()"
77824
- [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
77825
- [class.is-finished]="game().status === 'finished'"
77826
- aria-live="polite"
77827
- >
77828
- <div>
77829
- <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
77830
- <strong>{{ turnBannerTitle() }}</strong>
77831
- <p>{{ turnBannerBody() }}</p>
77832
- </div>
77833
- <div class="christianity-turn-actions">
77834
- <button
77835
- type="button"
77836
- class="christianity-end-turn"
77837
- data-testid="christianity-end-turn"
77838
- [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
77839
- (click)="endTurn()"
77840
- >
77841
- {{ t('christianityEndTurn') }}
77842
- </button>
77843
- <button type="button" class="christianity-options-button" data-testid="christianity-options-button" valLightRipple (click)="toggleOptions()">
77844
- {{ t('christianityOptions') }}
77845
- </button>
77846
- </div>
77847
- @if (showOptions()) {
77848
- <div class="christianity-options-menu" data-testid="christianity-options-menu">
77849
- <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
77850
- <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">{{ t('christianityResign') }}</button>
77851
- <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
78355
+ <section
78356
+ class="christianity-turn-banner"
78357
+ data-testid="christianity-turn-banner"
78358
+ [class.is-own-turn]="isOwnTurn()"
78359
+ [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
78360
+ [class.is-finished]="game().status === 'finished'"
78361
+ aria-live="polite"
78362
+ >
78363
+ <div>
78364
+ <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
78365
+ <strong>{{ turnBannerTitle() }}</strong>
78366
+ <p>{{ turnBannerBody() }}</p>
77852
78367
  </div>
77853
- }
77854
- </section>
77855
-
77856
- <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
77857
- <div class="christianity-hand-meter christianity-hand-meter--opponent">
77858
- <span>{{ t('christianityHand') }}</span>
77859
- <strong>{{ opponentHandCount() }}</strong>
77860
- </div>
77861
- <section class="christianity-hand-fan christianity-hand-fan--opponent" data-testid="christianity-opponent-hand" [attr.aria-label]="t('christianityOpponentHand')">
77862
- @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
78368
+ <div class="christianity-turn-actions">
78369
+ <button
78370
+ type="button"
78371
+ class="christianity-end-turn"
78372
+ data-testid="christianity-end-turn"
78373
+ [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
78374
+ (click)="endTurn()"
78375
+ >
78376
+ {{ t('christianityEndTurn') }}
78377
+ </button>
77863
78378
  <button
77864
78379
  type="button"
77865
- class="christianity-hidden-card-button christianity-render-card-button"
78380
+ class="christianity-options-button"
78381
+ data-testid="christianity-options-button"
77866
78382
  valLightRipple
77867
- [style.transform]="fanTransform($index, opponentHandCount(), true)"
77868
- [attr.aria-label]="t('christianityHiddenCard')"
78383
+ (click)="toggleOptions()"
77869
78384
  >
77870
- <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
78385
+ {{ t('christianityOptions') }}
77871
78386
  </button>
78387
+ </div>
78388
+ @if (showOptions()) {
78389
+ <div class="christianity-options-menu" data-testid="christianity-options-menu">
78390
+ <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
78391
+ <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">
78392
+ {{ t('christianityResign') }}
78393
+ </button>
78394
+ <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
78395
+ </div>
77872
78396
  }
77873
78397
  </section>
77874
- </section>
77875
78398
 
77876
- <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
77877
- @if (tutorialPhase() !== 'in_progress' && tutorialPhase() !== 'finished') {
78399
+ <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
78400
+ <div class="christianity-hand-meter christianity-hand-meter--opponent">
78401
+ <span>{{ t('christianityHand') }}</span>
78402
+ <strong>{{ opponentHandCount() }}</strong>
78403
+ </div>
77878
78404
  <section
77879
- class="christianity-setup-panel"
77880
- [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
77881
- [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
77882
- data-testid="christianity-setup-panel"
77883
- aria-live="polite"
78405
+ class="christianity-hand-fan christianity-hand-fan--opponent"
78406
+ data-testid="christianity-opponent-hand"
78407
+ [attr.aria-label]="t('christianityOpponentHand')"
77884
78408
  >
77885
- @if (tutorialPhase() === 'coin') {
77886
- <div
77887
- class="christianity-setup-coin"
77888
- [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
77889
- aria-hidden="true"
77890
- >
77891
- <span></span>
77892
- <span></span>
77893
- </div>
77894
- }
77895
- <div>
77896
- <span>{{ t('christianityMatchSetup') }}</span>
77897
- <h2>{{ setupTitle() }}</h2>
77898
- <p>{{ setupBody() }}</p>
77899
- </div>
77900
- @if (tutorialPhase() === 'coin') {
77901
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77902
- {{ t('christianityContinue') }}
77903
- </button>
77904
- }
77905
- @if (tutorialPhase() === 'leader_pick') {
77906
- @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
77907
- <div class="christianity-leader-choice-meter" aria-live="polite">
77908
- <span>{{ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString()) }}</span>
77909
- <i [style.transform]="'scaleX(' + (leaderChoiceSeconds() / leaderChoiceTotalSeconds) + ')'"></i>
77910
- </div>
77911
- <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
77912
- @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
77913
- @let leader = cardDefinition(leaderId);
77914
- <article class="christianity-leader-choice-option" [class.is-selected]="selectedTutorialLeaderId() === leaderId">
77915
- <button
77916
- type="button"
77917
- class="christianity-leader-choice-card christianity-render-card-button"
77918
- valLightRipple
77919
- [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
77920
- (click)="selectTutorialLeader(leaderId)"
77921
- >
77922
- <val-christianity-card [card]="definitionToRenderCard(leader)" variant="baby"></val-christianity-card>
77923
- </button>
77924
- <div>
77925
- <span>{{ selectedTutorialLeaderId() === leaderId ? t('christianityLeaderSelected') : t('christianitySelectLeader') }}</span>
77926
- <button type="button" class="christianity-leader-choice-detail" valLightRipple (click)="inspectLeaderChoice(leaderId)">
77927
- {{ t('christianityViewDetails') }}
77928
- </button>
77929
- </div>
77930
- </article>
77931
- }
77932
- </div>
77933
- <button type="button" class="christianity-primary-action" data-testid="christianity-confirm-leader" valLightRipple (click)="confirmTutorialLeader()">
77934
- {{ t('christianityChooseThisLeader') }}
77935
- </button>
77936
- } @else {
77937
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77938
- {{ t('christianityContinue') }}
77939
- </button>
77940
- }
77941
- }
77942
- </section>
77943
- }
77944
- @if (game().status === 'finished') {
77945
- <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
77946
- <span>{{ t('christianityGameOver') }}</span>
77947
- <h2>{{ resultTitle() }}</h2>
77948
- <p>{{ resultBody() }}</p>
77949
- <div>
77950
- <button type="button" class="christianity-primary-action" data-testid="christianity-play-again" valLightRipple (click)="restartTutorial()">
77951
- {{ t('christianityPlayAgain') }}
77952
- </button>
77953
- <button type="button" class="christianity-secondary-action" valLightRipple (click)="returnToChristianityHome()">
77954
- {{ t('christianityBackToMenu') }}
77955
- </button>
77956
- </div>
77957
- </section>
77958
- }
77959
- <div class="christianity-impact-layer">
77960
- @if (combatTrail(); as trail) {
77961
- <span
77962
- class="christianity-combat-trail"
77963
- aria-hidden="true"
77964
- [class.is-leader-target]="trail.tone === 'leader'"
77965
- [class.is-character-target]="trail.tone === 'character'"
77966
- [style.--christianity-combat-trail-x]="trail.fromX"
77967
- [style.--christianity-combat-trail-y]="trail.fromY"
77968
- [style.--christianity-combat-trail-mid-x]="trail.midX"
77969
- [style.--christianity-combat-trail-mid-y]="trail.midY"
77970
- [style.--christianity-combat-trail-length]="trail.length"
77971
- [style.--christianity-combat-trail-angle]="trail.angle"
77972
- >
77973
- <span></span>
77974
- </span>
77975
- }
77976
- @for (impact of impactEvents(); track impact.id) {
77977
- <val-mini-game-score-pop [props]="impactPopProps(impact)" />
77978
- }
77979
- @if (combatCallout(); as callout) {
77980
- <val-mini-game-callout [props]="callout" />
77981
- }
77982
- </div>
77983
- <section class="christianity-player-side christianity-player-side--opponent">
77984
- <section class="christianity-player-rack christianity-player-rack--opponent">
77985
- <div class="christianity-offerings-zone">
77986
- <button type="button" class="christianity-offering-pile" data-testid="christianity-offerings-P2" [class.is-empty-zone]="!topOffering('P2')" valLightRipple (click)="openOfferings('P2')">
77987
- @if (offeringStack('P2').length) {
77988
- <span class="christianity-zone-card-stack" aria-hidden="true">
77989
- @for (offering of offeringStack('P2'); track offering.instanceId) {
77990
- <val-christianity-card
77991
- [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77992
- [variant]="offering.ready ? 'baby' : 'back'"
77993
- [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77994
- ></val-christianity-card>
77995
- }
77996
- </span>
77997
- } @else {
77998
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77999
- }
78000
- <span>{{ t('christianityOfferingReserve') }}</span>
78001
- <strong>{{ readyOfferings(opponent()) }}</strong>
78002
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
78003
- </button>
78004
- </div>
78005
- <div
78006
- class="christianity-attack-drop-target"
78007
- cdkDropList
78008
- id="christianity-opponent-leader-drop"
78009
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78010
- [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
78011
- [cdkDropListSortingDisabled]="true"
78012
- (cdkDropListDropped)="dropOnOpponentLeader($event)"
78013
- >
78409
+ @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
78014
78410
  <button
78015
78411
  type="button"
78016
- class="christianity-leader-card christianity-render-card-button"
78017
- data-testid="christianity-leader-P2"
78412
+ class="christianity-hidden-card-button christianity-render-card-button"
78018
78413
  valLightRipple
78019
- [class.is-target]="isOpponentLeaderTarget()"
78020
- [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
78021
- [class.is-combat-target]="isCombatLeaderTarget('P2')"
78022
- (click)="handleOpponentLeader()"
78414
+ [style.transform]="fanTransform($index, opponentHandCount(), true)"
78415
+ [attr.aria-label]="t('christianityHiddenCard')"
78023
78416
  >
78024
- <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
78417
+ <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
78025
78418
  </button>
78026
- </div>
78027
- <span class="christianity-rack-spacer" aria-hidden="true"></span>
78419
+ }
78028
78420
  </section>
78421
+ </section>
78029
78422
 
78030
- <section class="christianity-battle-zone christianity-battle-zone--opponent" data-testid="christianity-battle-P2" [attr.aria-label]="t('christianityOpponentBattle')">
78031
- @for (slot of battleSlots('P2'); track slot.trackId) {
78032
- @if (slot.card) {
78423
+ <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
78424
+ @if (tutorialPhase() !== 'in_progress' && tutorialPhase() !== 'finished') {
78425
+ <section
78426
+ class="christianity-setup-panel"
78427
+ [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
78428
+ [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
78429
+ data-testid="christianity-setup-panel"
78430
+ aria-live="polite"
78431
+ >
78432
+ @if (tutorialPhase() === 'coin') {
78433
+ <div
78434
+ class="christianity-setup-coin"
78435
+ [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
78436
+ aria-hidden="true"
78437
+ >
78438
+ <span></span>
78439
+ <span></span>
78440
+ </div>
78441
+ }
78442
+ <div>
78443
+ <span>{{ t('christianityMatchSetup') }}</span>
78444
+ <h2>{{ setupTitle() }}</h2>
78445
+ <p>{{ setupBody() }}</p>
78446
+ </div>
78447
+ @if (tutorialPhase() === 'coin') {
78033
78448
  <button
78034
78449
  type="button"
78035
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78036
- [attr.data-testid]="battleCardTestId('P2', slot.card)"
78037
- cdkDropList
78038
- [id]="opponentTargetDropId(slot.card)"
78039
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78040
- [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
78041
- [cdkDropListSortingDisabled]="true"
78450
+ class="christianity-primary-action"
78451
+ data-testid="christianity-continue-setup"
78042
78452
  valLightRipple
78043
- [class.is-target]="isOpponentCharacterTarget(slot.card)"
78044
- [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
78045
- (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
78046
- (click)="handleOpponentCharacter(slot.card)"
78453
+ (click)="continueAfterCoin()"
78047
78454
  >
78048
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78455
+ {{ t('christianityContinue') }}
78049
78456
  </button>
78050
- } @else {
78051
- <span class="christianity-slot"></span>
78052
78457
  }
78053
- }
78054
- </section>
78055
- </section>
78056
-
78057
- <section class="christianity-shared-zone" data-testid="christianity-shared-zone" [attr.aria-label]="t('christianitySharedZone')">
78058
- <button type="button" class="christianity-zone-pile christianity-zone-pile--history" data-testid="christianity-history-zone" [class.is-empty-zone]="!topHistoryCard()" [class.has-new-card]="historyPulse()" valLightRipple (click)="openHistory()">
78059
- @if (historyStack().length) {
78060
- <span class="christianity-zone-card-stack" aria-hidden="true">
78061
- @for (cardId of historyStack(); track cardId + $index) {
78062
- <val-christianity-card
78063
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
78064
- variant="baby"
78065
- [style.transform]="zoneStackTransform($index, historyStack().length)"
78066
- ></val-christianity-card>
78458
+ @if (tutorialPhase() === 'leader_pick') {
78459
+ @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
78460
+ <div class="christianity-leader-choice-meter" aria-live="polite">
78461
+ <span>{{
78462
+ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString())
78463
+ }}</span>
78464
+ <i [style.transform]="'scaleX(' + leaderChoiceSeconds() / leaderChoiceTotalSeconds + ')'"></i>
78465
+ </div>
78466
+ <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
78467
+ @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
78468
+ @let leader = cardDefinition(leaderId);
78469
+ <article
78470
+ class="christianity-leader-choice-option"
78471
+ [class.is-selected]="selectedTutorialLeaderId() === leaderId"
78472
+ >
78473
+ <button
78474
+ type="button"
78475
+ class="christianity-leader-choice-card christianity-render-card-button"
78476
+ valLightRipple
78477
+ [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
78478
+ (click)="selectTutorialLeader(leaderId)"
78479
+ >
78480
+ <val-christianity-card
78481
+ [card]="definitionToRenderCard(leader)"
78482
+ variant="baby"
78483
+ ></val-christianity-card>
78484
+ </button>
78485
+ <div>
78486
+ <span>{{
78487
+ selectedTutorialLeaderId() === leaderId
78488
+ ? t('christianityLeaderSelected')
78489
+ : t('christianitySelectLeader')
78490
+ }}</span>
78491
+ <button
78492
+ type="button"
78493
+ class="christianity-leader-choice-detail"
78494
+ valLightRipple
78495
+ (click)="inspectLeaderChoice(leaderId)"
78496
+ >
78497
+ {{ t('christianityViewDetails') }}
78498
+ </button>
78499
+ </div>
78500
+ </article>
78501
+ }
78502
+ </div>
78503
+ <button
78504
+ type="button"
78505
+ class="christianity-primary-action"
78506
+ data-testid="christianity-confirm-leader"
78507
+ valLightRipple
78508
+ (click)="confirmTutorialLeader()"
78509
+ >
78510
+ {{ t('christianityChooseThisLeader') }}
78511
+ </button>
78512
+ } @else {
78513
+ <button
78514
+ type="button"
78515
+ class="christianity-primary-action"
78516
+ data-testid="christianity-continue-setup"
78517
+ valLightRipple
78518
+ (click)="continueAfterCoin()"
78519
+ >
78520
+ {{ t('christianityContinue') }}
78521
+ </button>
78067
78522
  }
78068
- </span>
78069
- } @else {
78070
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78071
- }
78072
- <span>{{ t('christianityHistory') }}</span>
78073
- <strong>{{ game().historyZone.length }}</strong>
78074
- </button>
78075
- <button type="button" class="christianity-zone-pile christianity-zone-pile--deck" data-testid="christianity-deck-zone" valLightRipple aria-live="polite" (click)="inspectDeck()">
78076
- <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
78077
- <i></i>
78078
- </span>
78079
- <span>{{ t('christianityDeck') }}</span>
78080
- <strong>{{ game().sharedDeck.length }}</strong>
78081
- </button>
78082
- <button
78083
- type="button"
78084
- class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
78085
- data-testid="christianity-place-zone"
78086
- cdkDropList
78087
- id="christianity-place-drop"
78088
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78089
- [cdkDropListEnterPredicate]="canDropOnPlace"
78090
- [cdkDropListSortingDisabled]="true"
78091
- [class.is-empty-zone]="!activePlaceCard()"
78092
- [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
78093
- valLightRipple
78094
- (cdkDropListDropped)="dropOnPlace($event)"
78095
- (click)="playSelectedPlaceOrInspect()"
78096
- >
78097
- @if (placeStack().length) {
78098
- @if (activePlaceCard(); as placeCard) {
78099
- <div class="christianity-place-backdrop" [style.background-image]="placeBackdropImage(placeCard)" aria-hidden="true"></div>
78100
78523
  }
78101
- <span class="christianity-zone-card-stack" aria-hidden="true">
78102
- @for (cardId of placeStack(); track cardId + $index) {
78103
- <val-christianity-card
78104
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
78105
- variant="baby"
78106
- [style.transform]="zoneStackTransform($index, placeStack().length)"
78107
- ></val-christianity-card>
78108
- }
78109
- </span>
78110
- } @else {
78111
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78112
- }
78113
- <span>{{ t('christianityPlace') }}</span>
78114
- <strong>{{ game().placeZone.length }}</strong>
78115
- @if (canPlaceSelectedPlace() || isDraggingPlace()) {
78116
- <em>{{ t('christianityPlayHere') }}</em>
78117
- }
78118
- </button>
78119
- </section>
78120
-
78121
- <section class="christianity-player-side">
78122
- <section
78123
- class="christianity-battle-zone"
78124
- data-testid="christianity-battle-P1"
78125
- cdkDropList
78126
- id="christianity-player-battle-source"
78127
- [cdkDropListConnectedTo]="attackTargetDropIds()"
78128
- [cdkDropListSortingDisabled]="true"
78129
- [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78130
- [attr.aria-label]="t('christianityYourBattle')"
78131
- >
78132
- @for (slot of battleSlots('P1'); track slot.trackId) {
78133
- @if (slot.card) {
78524
+ </section>
78525
+ }
78526
+ @if (game().status === 'finished') {
78527
+ <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
78528
+ <span>{{ t('christianityGameOver') }}</span>
78529
+ <h2>{{ resultTitle() }}</h2>
78530
+ <p>{{ resultBody() }}</p>
78531
+ <div>
78134
78532
  <button
78135
78533
  type="button"
78136
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78137
- [attr.data-testid]="battleCardTestId('P1', slot.card)"
78138
- cdkDrag
78139
- [cdkDragData]="slot.card.instanceId"
78140
- [cdkDragDisabled]="!canDragAttacker(slot.card)"
78534
+ class="christianity-primary-action"
78535
+ data-testid="christianity-play-again"
78141
78536
  valLightRipple
78142
- [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
78143
- [class.is-exhausted]="!slot.card.canAttack"
78144
- [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
78145
- [class.is-combat-attacker]="isCombatAttacker(slot.card)"
78146
- (cdkDragStarted)="startDraggingAttacker(slot.card)"
78147
- (cdkDragEnded)="endDraggingAttacker()"
78148
- (click)="selectAttacker(slot.card)"
78537
+ (click)="restartTutorial()"
78149
78538
  >
78150
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78151
- <ng-template cdkDragPreview>
78152
- <div class="christianity-card-drag-preview">
78153
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78154
- </div>
78155
- </ng-template>
78156
- <ng-template cdkDragPlaceholder>
78157
- <div class="christianity-card-drag-placeholder"></div>
78158
- </ng-template>
78539
+ {{ t('christianityPlayAgain') }}
78159
78540
  </button>
78160
- } @else {
78161
78541
  <button
78162
78542
  type="button"
78163
- class="christianity-slot"
78164
- [attr.data-testid]="battleSlotTestId('P1', $index)"
78165
- cdkDropList
78166
- [id]="battleSlotDropId($index)"
78167
- [cdkDropListData]="$index"
78168
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78169
- [cdkDropListEnterPredicate]="canDropOnBattleSlot"
78170
- [cdkDropListSortingDisabled]="true"
78171
- [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78543
+ class="christianity-secondary-action"
78172
78544
  valLightRipple
78173
- (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
78174
- (click)="playSelectedCard($index)"
78545
+ (click)="returnToChristianityHome()"
78175
78546
  >
78176
- @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
78177
- <span>{{ t('christianityPlayHere') }}</span>
78178
- }
78547
+ {{ t('christianityBackToMenu') }}
78179
78548
  </button>
78180
- }
78181
- }
78182
- </section>
78183
-
78184
- <section class="christianity-player-rack">
78185
- <div class="christianity-offerings-zone">
78186
- <div
78187
- class="christianity-offering-drop-target"
78188
- cdkDropList
78189
- id="christianity-offerings-drop"
78190
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78191
- [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
78192
- [cdkDropListEnterPredicate]="canDropOnOfferings"
78193
- [cdkDropListSortingDisabled]="true"
78194
- (cdkDropListDropped)="dropOnOfferings($event)"
78549
+ </div>
78550
+ </section>
78551
+ }
78552
+ <div class="christianity-impact-layer">
78553
+ @if (combatTrail(); as trail) {
78554
+ <span
78555
+ class="christianity-combat-trail"
78556
+ aria-hidden="true"
78557
+ [class.is-leader-target]="trail.tone === 'leader'"
78558
+ [class.is-character-target]="trail.tone === 'character'"
78559
+ [style.--christianity-combat-trail-x]="trail.fromX"
78560
+ [style.--christianity-combat-trail-y]="trail.fromY"
78561
+ [style.--christianity-combat-trail-mid-x]="trail.midX"
78562
+ [style.--christianity-combat-trail-mid-y]="trail.midY"
78563
+ [style.--christianity-combat-trail-length]="trail.length"
78564
+ [style.--christianity-combat-trail-angle]="trail.angle"
78195
78565
  >
78566
+ <span></span>
78567
+ </span>
78568
+ }
78569
+ @for (impact of impactEvents(); track impact.id) {
78570
+ <val-mini-game-score-pop [props]="impactPopProps(impact)" />
78571
+ }
78572
+ @if (combatCallout(); as callout) {
78573
+ <val-mini-game-callout [props]="callout" />
78574
+ }
78575
+ </div>
78576
+ <section class="christianity-player-side christianity-player-side--opponent">
78577
+ <section class="christianity-player-rack christianity-player-rack--opponent">
78578
+ <div class="christianity-offerings-zone">
78196
78579
  <button
78197
78580
  type="button"
78198
78581
  class="christianity-offering-pile"
78199
- data-testid="christianity-offerings-P1"
78200
- [class.is-empty-zone]="!topOffering('P1')"
78582
+ data-testid="christianity-offerings-P2"
78583
+ [class.is-empty-zone]="!topOffering('P2')"
78201
78584
  valLightRipple
78202
- (click)="handleOfferingPile('P1')"
78585
+ (click)="openOfferings('P2')"
78203
78586
  >
78204
- @if (offeringStack('P1').length) {
78587
+ @if (offeringStack('P2').length) {
78205
78588
  <span class="christianity-zone-card-stack" aria-hidden="true">
78206
- @for (offering of offeringStack('P1'); track offering.instanceId) {
78589
+ @for (offering of offeringStack('P2'); track offering.instanceId) {
78207
78590
  <val-christianity-card
78208
78591
  [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
78209
78592
  [variant]="offering.ready ? 'baby' : 'back'"
78210
- [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
78593
+ [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
78211
78594
  ></val-christianity-card>
78212
78595
  }
78213
78596
  </span>
@@ -78215,89 +78598,358 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78215
78598
  <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78216
78599
  }
78217
78600
  <span>{{ t('christianityOfferingReserve') }}</span>
78218
- <strong>{{ readyOfferings(player()) }}</strong>
78219
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
78601
+ <strong>{{ readyOfferings(opponent()) }}</strong>
78602
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
78220
78603
  </button>
78221
78604
  </div>
78222
- </div>
78223
- <button type="button" class="christianity-leader-card christianity-render-card-button" data-testid="christianity-leader-P1" valLightRipple (click)="inspectLeader('P1')">
78224
- <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
78225
- </button>
78226
- <div class="christianity-player-command">
78227
- <div class="christianity-player-badge">
78228
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
78229
- <strong>{{ player().displayName }}</strong>
78605
+ <div
78606
+ class="christianity-attack-drop-target"
78607
+ cdkDropList
78608
+ id="christianity-opponent-leader-drop"
78609
+ [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78610
+ [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
78611
+ [cdkDropListSortingDisabled]="true"
78612
+ (cdkDropListDropped)="dropOnOpponentLeader($event)"
78613
+ >
78614
+ <button
78615
+ type="button"
78616
+ class="christianity-leader-card christianity-render-card-button"
78617
+ data-testid="christianity-leader-P2"
78618
+ valLightRipple
78619
+ [class.is-target]="isOpponentLeaderTarget()"
78620
+ [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
78621
+ [class.is-combat-target]="isCombatLeaderTarget('P2')"
78622
+ (click)="handleOpponentLeader()"
78623
+ >
78624
+ <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
78625
+ </button>
78230
78626
  </div>
78231
- </div>
78627
+ <span class="christianity-rack-spacer" aria-hidden="true"></span>
78628
+ </section>
78629
+
78630
+ <section
78631
+ class="christianity-battle-zone christianity-battle-zone--opponent"
78632
+ data-testid="christianity-battle-P2"
78633
+ [attr.aria-label]="t('christianityOpponentBattle')"
78634
+ >
78635
+ @for (slot of battleSlots('P2'); track slot.trackId) {
78636
+ @if (slot.card) {
78637
+ <button
78638
+ type="button"
78639
+ class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78640
+ [attr.data-testid]="battleCardTestId('P2', slot.card)"
78641
+ cdkDropList
78642
+ [id]="opponentTargetDropId(slot.card)"
78643
+ [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78644
+ [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
78645
+ [cdkDropListSortingDisabled]="true"
78646
+ valLightRipple
78647
+ [class.is-target]="isOpponentCharacterTarget(slot.card)"
78648
+ [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
78649
+ (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
78650
+ (click)="handleOpponentCharacter(slot.card)"
78651
+ >
78652
+ <val-christianity-card
78653
+ [card]="characterRenderCard(slot.card)"
78654
+ variant="board"
78655
+ ></val-christianity-card>
78656
+ </button>
78657
+ } @else {
78658
+ <span class="christianity-slot"></span>
78659
+ }
78660
+ }
78661
+ </section>
78232
78662
  </section>
78233
- </section>
78234
- </section>
78235
78663
 
78236
- <section class="christianity-hand-shelf">
78237
- <div class="christianity-hand-meter">
78238
- <span>{{ t('christianityHand') }}</span>
78239
- <strong>{{ player().hand?.length ?? 0 }}</strong>
78240
- </div>
78241
- <section
78242
- class="christianity-hand-fan"
78243
- data-testid="christianity-hand-P1"
78244
- cdkDropList
78245
- id="christianity-hand-source"
78246
- [cdkDropListConnectedTo]="dropTargetIds()"
78247
- [attr.aria-label]="t('christianityYourHand')"
78248
- (pointerleave)="clearHoveredHandIndex()"
78249
- >
78250
- @for (cardId of player().hand ?? []; track cardId + $index) {
78251
- @let card = cardDefinition(cardId);
78252
- <div
78253
- class="christianity-hand-card-wrap"
78254
- [class.is-selected]="selectedHandCard() === cardId"
78255
- [class.is-dragging]="draggingCardId() === cardId"
78256
- [style.z-index]="handCardZIndex($index)"
78257
- [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
78258
- (pointerenter)="setHoveredHandIndex($index)"
78259
- (pointerdown)="setHoveredHandIndex($index)"
78260
- (pointerup)="releaseHoveredHand($event)"
78664
+ <section
78665
+ class="christianity-shared-zone"
78666
+ data-testid="christianity-shared-zone"
78667
+ [attr.aria-label]="t('christianitySharedZone')"
78668
+ >
78669
+ <button
78670
+ type="button"
78671
+ class="christianity-zone-pile christianity-zone-pile--history"
78672
+ data-testid="christianity-history-zone"
78673
+ [class.is-empty-zone]="!topHistoryCard()"
78674
+ [class.has-new-card]="historyPulse()"
78675
+ valLightRipple
78676
+ (click)="openHistory()"
78677
+ >
78678
+ @if (historyStack().length) {
78679
+ <span class="christianity-zone-card-stack" aria-hidden="true">
78680
+ @for (cardId of historyStack(); track cardId + $index) {
78681
+ <val-christianity-card
78682
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
78683
+ variant="baby"
78684
+ [style.transform]="zoneStackTransform($index, historyStack().length)"
78685
+ ></val-christianity-card>
78686
+ }
78687
+ </span>
78688
+ } @else {
78689
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78690
+ }
78691
+ <span>{{ t('christianityHistory') }}</span>
78692
+ <strong>{{ game().historyZone.length }}</strong>
78693
+ </button>
78694
+ <button
78695
+ type="button"
78696
+ class="christianity-zone-pile christianity-zone-pile--deck"
78697
+ data-testid="christianity-deck-zone"
78698
+ valLightRipple
78699
+ aria-live="polite"
78700
+ (click)="inspectDeck()"
78701
+ >
78702
+ <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
78703
+ <i></i>
78704
+ </span>
78705
+ <span>{{ t('christianityDeck') }}</span>
78706
+ <strong>{{ game().sharedDeck.length }}</strong>
78707
+ </button>
78708
+ <button
78709
+ type="button"
78710
+ class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
78711
+ data-testid="christianity-place-zone"
78712
+ cdkDropList
78713
+ id="christianity-place-drop"
78714
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
78715
+ [cdkDropListEnterPredicate]="canDropOnPlace"
78716
+ [cdkDropListSortingDisabled]="true"
78717
+ [class.is-empty-zone]="!activePlaceCard()"
78718
+ [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
78719
+ valLightRipple
78720
+ (cdkDropListDropped)="dropOnPlace($event)"
78721
+ (click)="playSelectedPlaceOrInspect()"
78722
+ >
78723
+ @if (placeStack().length) {
78724
+ @if (activePlaceCard(); as placeCard) {
78725
+ <div
78726
+ class="christianity-place-backdrop"
78727
+ [style.background-image]="placeBackdropImage(placeCard)"
78728
+ aria-hidden="true"
78729
+ ></div>
78730
+ }
78731
+ <span class="christianity-zone-card-stack" aria-hidden="true">
78732
+ @for (cardId of placeStack(); track cardId + $index) {
78733
+ <val-christianity-card
78734
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
78735
+ variant="baby"
78736
+ [style.transform]="zoneStackTransform($index, placeStack().length)"
78737
+ ></val-christianity-card>
78738
+ }
78739
+ </span>
78740
+ } @else {
78741
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78742
+ }
78743
+ <span>{{ t('christianityPlace') }}</span>
78744
+ <strong>{{ game().placeZone.length }}</strong>
78745
+ @if (canPlaceSelectedPlace() || isDraggingPlace()) {
78746
+ <em>{{ t('christianityPlayHere') }}</em>
78747
+ }
78748
+ </button>
78749
+ </section>
78750
+
78751
+ <section class="christianity-player-side">
78752
+ <section
78753
+ class="christianity-battle-zone"
78754
+ data-testid="christianity-battle-P1"
78755
+ cdkDropList
78756
+ id="christianity-player-battle-source"
78757
+ [cdkDropListConnectedTo]="attackTargetDropIds()"
78758
+ [cdkDropListSortingDisabled]="true"
78759
+ [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78760
+ [attr.aria-label]="t('christianityYourBattle')"
78261
78761
  >
78762
+ @for (slot of battleSlots('P1'); track slot.trackId) {
78763
+ @if (slot.card) {
78764
+ <button
78765
+ type="button"
78766
+ class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78767
+ [attr.data-testid]="battleCardTestId('P1', slot.card)"
78768
+ cdkDrag
78769
+ [cdkDragData]="slot.card.instanceId"
78770
+ [cdkDragDisabled]="!canDragAttacker(slot.card)"
78771
+ valLightRipple
78772
+ [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
78773
+ [class.is-exhausted]="!slot.card.canAttack"
78774
+ [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
78775
+ [class.is-combat-attacker]="isCombatAttacker(slot.card)"
78776
+ (cdkDragStarted)="startDraggingAttacker(slot.card)"
78777
+ (cdkDragEnded)="endDraggingAttacker()"
78778
+ (click)="selectAttacker(slot.card)"
78779
+ >
78780
+ <val-christianity-card
78781
+ [card]="characterRenderCard(slot.card)"
78782
+ variant="board"
78783
+ ></val-christianity-card>
78784
+ <ng-template cdkDragPreview>
78785
+ <div class="christianity-card-drag-preview">
78786
+ <val-christianity-card
78787
+ [card]="characterRenderCard(slot.card)"
78788
+ variant="board"
78789
+ ></val-christianity-card>
78790
+ </div>
78791
+ </ng-template>
78792
+ <ng-template cdkDragPlaceholder>
78793
+ <div class="christianity-card-drag-placeholder"></div>
78794
+ </ng-template>
78795
+ </button>
78796
+ } @else {
78797
+ <button
78798
+ type="button"
78799
+ class="christianity-slot"
78800
+ [attr.data-testid]="battleSlotTestId('P1', $index)"
78801
+ cdkDropList
78802
+ [id]="battleSlotDropId($index)"
78803
+ [cdkDropListData]="$index"
78804
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
78805
+ [cdkDropListEnterPredicate]="canDropOnBattleSlot"
78806
+ [cdkDropListSortingDisabled]="true"
78807
+ [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78808
+ valLightRipple
78809
+ (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
78810
+ (click)="playSelectedCard($index)"
78811
+ >
78812
+ @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
78813
+ <span>{{ t('christianityPlayHere') }}</span>
78814
+ }
78815
+ </button>
78816
+ }
78817
+ }
78818
+ </section>
78819
+
78820
+ <section class="christianity-player-rack">
78821
+ <div class="christianity-offerings-zone">
78822
+ <div
78823
+ class="christianity-offering-drop-target"
78824
+ cdkDropList
78825
+ id="christianity-offerings-drop"
78826
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
78827
+ [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
78828
+ [cdkDropListEnterPredicate]="canDropOnOfferings"
78829
+ [cdkDropListSortingDisabled]="true"
78830
+ (cdkDropListDropped)="dropOnOfferings($event)"
78831
+ >
78832
+ <button
78833
+ type="button"
78834
+ class="christianity-offering-pile"
78835
+ data-testid="christianity-offerings-P1"
78836
+ [class.is-empty-zone]="!topOffering('P1')"
78837
+ valLightRipple
78838
+ (click)="handleOfferingPile('P1')"
78839
+ >
78840
+ @if (offeringStack('P1').length) {
78841
+ <span class="christianity-zone-card-stack" aria-hidden="true">
78842
+ @for (offering of offeringStack('P1'); track offering.instanceId) {
78843
+ <val-christianity-card
78844
+ [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
78845
+ [variant]="offering.ready ? 'baby' : 'back'"
78846
+ [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
78847
+ ></val-christianity-card>
78848
+ }
78849
+ </span>
78850
+ } @else {
78851
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78852
+ }
78853
+ <span>{{ t('christianityOfferingReserve') }}</span>
78854
+ <strong>{{ readyOfferings(player()) }}</strong>
78855
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
78856
+ </button>
78857
+ </div>
78858
+ </div>
78262
78859
  <button
78263
78860
  type="button"
78264
- class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
78265
- [attr.data-testid]="handCardTestId(cardId, $index)"
78266
- cdkDrag
78861
+ class="christianity-leader-card christianity-render-card-button"
78862
+ data-testid="christianity-leader-P1"
78267
78863
  valLightRipple
78268
- [cdkDragData]="cardId"
78269
- [cdkDragDisabled]="!canPlay(card)"
78270
- [attr.aria-label]="handCardActionLabel(card)"
78271
- [class.is-selected]="selectedHandCard() === cardId"
78272
- [class.is-disabled]="!canPlay(card)"
78273
- (cdkDragStarted)="startDraggingCard(cardId)"
78274
- (cdkDragEnded)="endDraggingCard()"
78275
- (click)="selectHandCard(cardId)"
78864
+ (click)="inspectLeader('P1')"
78276
78865
  >
78277
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78278
- <ng-template cdkDragPreview>
78279
- <div class="christianity-card-drag-preview">
78280
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78281
- </div>
78282
- </ng-template>
78283
- <ng-template cdkDragPlaceholder>
78284
- <div class="christianity-card-drag-placeholder"></div>
78285
- </ng-template>
78866
+ <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
78286
78867
  </button>
78287
- <button
78288
- type="button"
78289
- class="christianity-card-eye"
78290
- [attr.aria-label]="t('christianityPreviewCard')"
78291
- (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
78292
- ></button>
78293
- </div>
78294
- }
78868
+ <div class="christianity-player-command">
78869
+ <div class="christianity-player-badge">
78870
+ <span class="christianity-avatar" [style.background]="player().avatarColor">{{
78871
+ player().avatar
78872
+ }}</span>
78873
+ <strong>{{ player().displayName }}</strong>
78874
+ </div>
78875
+ </div>
78876
+ </section>
78877
+ </section>
78878
+ </section>
78879
+
78880
+ <section class="christianity-hand-shelf">
78881
+ <div class="christianity-hand-meter">
78882
+ <span>{{ t('christianityHand') }}</span>
78883
+ <strong>{{ player().hand?.length ?? 0 }}</strong>
78884
+ </div>
78885
+ <section
78886
+ class="christianity-hand-fan"
78887
+ data-testid="christianity-hand-P1"
78888
+ cdkDropList
78889
+ id="christianity-hand-source"
78890
+ [cdkDropListConnectedTo]="dropTargetIds()"
78891
+ [attr.aria-label]="t('christianityYourHand')"
78892
+ (pointerleave)="clearHoveredHandIndex()"
78893
+ >
78894
+ @for (cardId of player().hand ?? []; track cardId + $index) {
78895
+ @let card = cardDefinition(cardId);
78896
+ <div
78897
+ class="christianity-hand-card-wrap"
78898
+ [class.is-selected]="selectedHandCard() === cardId"
78899
+ [class.is-dragging]="draggingCardId() === cardId"
78900
+ [style.z-index]="handCardZIndex($index)"
78901
+ [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
78902
+ (pointerenter)="setHoveredHandIndex($index)"
78903
+ (pointerdown)="setHoveredHandIndex($index)"
78904
+ (pointerup)="releaseHoveredHand($event)"
78905
+ >
78906
+ <button
78907
+ type="button"
78908
+ class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
78909
+ [attr.data-testid]="handCardTestId(cardId, $index)"
78910
+ cdkDrag
78911
+ valLightRipple
78912
+ [cdkDragData]="cardId"
78913
+ [cdkDragDisabled]="!canPlay(card)"
78914
+ [attr.aria-label]="handCardActionLabel(card)"
78915
+ [class.is-selected]="selectedHandCard() === cardId"
78916
+ [class.is-disabled]="!canPlay(card)"
78917
+ (cdkDragStarted)="startDraggingCard(cardId)"
78918
+ (cdkDragEnded)="endDraggingCard()"
78919
+ (click)="selectHandCard(cardId)"
78920
+ >
78921
+ <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78922
+ <ng-template cdkDragPreview>
78923
+ <div class="christianity-card-drag-preview">
78924
+ <val-christianity-card
78925
+ [card]="definitionToRenderCard(card)"
78926
+ variant="baby"
78927
+ ></val-christianity-card>
78928
+ </div>
78929
+ </ng-template>
78930
+ <ng-template cdkDragPlaceholder>
78931
+ <div class="christianity-card-drag-placeholder"></div>
78932
+ </ng-template>
78933
+ </button>
78934
+ <button
78935
+ type="button"
78936
+ class="christianity-card-eye"
78937
+ [attr.aria-label]="t('christianityPreviewCard')"
78938
+ (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
78939
+ ></button>
78940
+ </div>
78941
+ }
78942
+ </section>
78295
78943
  </section>
78296
- </section>
78297
78944
  }
78298
78945
 
78299
78946
  @if (selectedPreview()) {
78300
- <button type="button" class="christianity-preview-backdrop" [attr.aria-label]="t('christianityClosePreview')" (click)="closePreview()"></button>
78947
+ <button
78948
+ type="button"
78949
+ class="christianity-preview-backdrop"
78950
+ [attr.aria-label]="t('christianityClosePreview')"
78951
+ (click)="closePreview()"
78952
+ ></button>
78301
78953
  <aside
78302
78954
  class="christianity-floating-card-shell"
78303
78955
  [class.is-history-preview]="canNavigatePreview()"
@@ -78337,7 +78989,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78337
78989
  </button>
78338
78990
  }
78339
78991
  @if (selectedPreviewNeighborRenderCard(-1); as previousCard) {
78340
- <val-christianity-card class="christianity-preview-ghost christianity-preview-ghost--previous" [card]="previousCard" variant="full"></val-christianity-card>
78992
+ <val-christianity-card
78993
+ class="christianity-preview-ghost christianity-preview-ghost--previous"
78994
+ [card]="previousCard"
78995
+ variant="full"
78996
+ ></val-christianity-card>
78341
78997
  }
78342
78998
  <div
78343
78999
  class="christianity-floating-card"
@@ -78356,7 +79012,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78356
79012
  }
78357
79013
  </div>
78358
79014
  @if (selectedPreviewNeighborRenderCard(1); as nextCard) {
78359
- <val-christianity-card class="christianity-preview-ghost christianity-preview-ghost--next" [card]="nextCard" variant="full"></val-christianity-card>
79015
+ <val-christianity-card
79016
+ class="christianity-preview-ghost christianity-preview-ghost--next"
79017
+ [card]="nextCard"
79018
+ variant="full"
79019
+ ></val-christianity-card>
78360
79020
  }
78361
79021
  </div>
78362
79022
  <div class="christianity-preview-side">
@@ -78440,7 +79100,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78440
79100
  }
78441
79101
 
78442
79102
  @if (selectedInfo(); as info) {
78443
- <button type="button" class="christianity-info-backdrop" [attr.aria-label]="t('close')" (click)="closeInfo()"></button>
79103
+ <button
79104
+ type="button"
79105
+ class="christianity-info-backdrop"
79106
+ [attr.aria-label]="t('close')"
79107
+ (click)="closeInfo()"
79108
+ ></button>
78444
79109
  <aside class="christianity-info-panel" aria-live="polite">
78445
79110
  <button type="button" class="christianity-dialog-close christianity-info-close" (click)="closeInfo()">
78446
79111
  {{ t('close') }}
@@ -78465,14 +79130,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78465
79130
  @if (screen() === 'table') {
78466
79131
  <section class="christianity-actions" data-testid="christianity-actions" aria-live="polite">
78467
79132
  <p class="christianity-sr-only" data-testid="christianity-status">{{ statusText() }}</p>
78468
- <button type="button" data-testid="christianity-primary-action" [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()" (click)="playSelectedCard()">
79133
+ <button
79134
+ type="button"
79135
+ data-testid="christianity-primary-action"
79136
+ [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()"
79137
+ (click)="playSelectedCard()"
79138
+ >
78469
79139
  {{ selectedActionLabel() }}
78470
79140
  </button>
78471
79141
  </section>
78472
79142
  }
78473
79143
 
78474
79144
  @if (showHistory()) {
78475
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseHistory')" (click)="closeHistory()"></button>
79145
+ <button
79146
+ type="button"
79147
+ class="christianity-history-backdrop"
79148
+ [attr.aria-label]="t('christianityCloseHistory')"
79149
+ (click)="closeHistory()"
79150
+ ></button>
78476
79151
  <section class="christianity-history-drawer" data-testid="christianity-history-drawer">
78477
79152
  <header>
78478
79153
  <h2>{{ t('christianityHistory') }}</h2>
@@ -78487,7 +79162,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78487
79162
  <div class="christianity-history-grid" data-testid="christianity-history-grid">
78488
79163
  @for (cardId of game().historyZone; track cardId + $index) {
78489
79164
  @let card = cardDefinition(cardId);
78490
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="historyCardTestId(cardId, $index)" valLightRipple (click)="inspectHistoryCard(cardId)">
79165
+ <button
79166
+ type="button"
79167
+ class="christianity-gallery-card-button christianity-render-card-button"
79168
+ [attr.data-testid]="historyCardTestId(cardId, $index)"
79169
+ valLightRipple
79170
+ (click)="inspectHistoryCard(cardId)"
79171
+ >
78491
79172
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78492
79173
  </button>
78493
79174
  }
@@ -78499,7 +79180,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78499
79180
  }
78500
79181
 
78501
79182
  @if (showPlace()) {
78502
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseHistory')" (click)="closePlace()"></button>
79183
+ <button
79184
+ type="button"
79185
+ class="christianity-history-backdrop"
79186
+ [attr.aria-label]="t('christianityCloseHistory')"
79187
+ (click)="closePlace()"
79188
+ ></button>
78503
79189
  <section class="christianity-history-drawer christianity-place-drawer" data-testid="christianity-place-drawer">
78504
79190
  <header>
78505
79191
  <h2>{{ t('christianityPlace') }}</h2>
@@ -78512,8 +79198,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78512
79198
  </header>
78513
79199
  <div class="christianity-history-grid" data-testid="christianity-place-grid">
78514
79200
  @for (cardId of game().placeZone; track cardId + $index) {
78515
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="'christianity-place-card-' + $index" valLightRipple (click)="inspectPlaceCard(cardId)">
78516
- <val-christianity-card [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })" variant="baby"></val-christianity-card>
79201
+ <button
79202
+ type="button"
79203
+ class="christianity-gallery-card-button christianity-render-card-button"
79204
+ [attr.data-testid]="'christianity-place-card-' + $index"
79205
+ valLightRipple
79206
+ (click)="inspectPlaceCard(cardId)"
79207
+ >
79208
+ <val-christianity-card
79209
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
79210
+ variant="baby"
79211
+ ></val-christianity-card>
78517
79212
  </button>
78518
79213
  }
78519
79214
  </div>
@@ -78521,8 +79216,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78521
79216
  }
78522
79217
 
78523
79218
  @if (showGallery()) {
78524
- <button type="button" class="christianity-history-backdrop christianity-gallery-backdrop" [attr.aria-label]="t('christianityCloseGallery')" (click)="closeGallery()"></button>
78525
- <section class="christianity-history-drawer christianity-gallery-drawer" data-testid="christianity-gallery-drawer">
79219
+ <button
79220
+ type="button"
79221
+ class="christianity-history-backdrop christianity-gallery-backdrop"
79222
+ [attr.aria-label]="t('christianityCloseGallery')"
79223
+ (click)="closeGallery()"
79224
+ ></button>
79225
+ <section
79226
+ class="christianity-history-drawer christianity-gallery-drawer"
79227
+ data-testid="christianity-gallery-drawer"
79228
+ >
78526
79229
  <header>
78527
79230
  <div>
78528
79231
  <h2>{{ t('christianityGallery') }}</h2>
@@ -78538,7 +79241,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78538
79241
  <div class="christianity-gallery-controls">
78539
79242
  <label>
78540
79243
  <span>{{ t('christianityGallerySearch') }}</span>
78541
- <input type="search" [value]="gallerySearch()" (input)="setGallerySearch($event)" [placeholder]="t('christianityGallerySearchPlaceholder')" />
79244
+ <input
79245
+ type="search"
79246
+ [value]="gallerySearch()"
79247
+ (input)="setGallerySearch($event)"
79248
+ [placeholder]="t('christianityGallerySearchPlaceholder')"
79249
+ />
78542
79250
  </label>
78543
79251
  <label>
78544
79252
  <span>{{ t('christianityGalleryFilter') }}</span>
@@ -78557,7 +79265,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78557
79265
  </div>
78558
79266
  <div class="christianity-gallery-grid">
78559
79267
  @for (card of galleryCards(); track card.id) {
78560
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" valLightRipple (click)="inspectGalleryCard(card.id)">
79268
+ <button
79269
+ type="button"
79270
+ class="christianity-gallery-card-button christianity-render-card-button"
79271
+ valLightRipple
79272
+ (click)="inspectGalleryCard(card.id)"
79273
+ >
78561
79274
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78562
79275
  </button>
78563
79276
  } @empty {
@@ -78569,12 +79282,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78569
79282
 
78570
79283
  @if (offeringsSeat()) {
78571
79284
  @let offeringPlayer = game().players[offeringsSeat()!];
78572
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseOfferings')" (click)="closeOfferings()"></button>
78573
- <section class="christianity-history-drawer christianity-offerings-drawer" [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)">
79285
+ <button
79286
+ type="button"
79287
+ class="christianity-history-backdrop"
79288
+ [attr.aria-label]="t('christianityCloseOfferings')"
79289
+ (click)="closeOfferings()"
79290
+ ></button>
79291
+ <section
79292
+ class="christianity-history-drawer christianity-offerings-drawer"
79293
+ [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)"
79294
+ >
78574
79295
  <header>
78575
79296
  <h2>{{ t('christianityOfferings') }}</h2>
78576
79297
  <div class="christianity-dialog-actions">
78577
- <span>{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} · {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span>
79298
+ <span
79299
+ >{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} ·
79300
+ {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span
79301
+ >
78578
79302
  <button type="button" class="christianity-dialog-close" (click)="closeOfferings()">
78579
79303
  {{ t('close') }}
78580
79304
  </button>
@@ -78584,14 +79308,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78584
79308
  <span class="christianity-rule-note-icon" aria-hidden="true">i</span>
78585
79309
  <p>{{ t('christianityOfferingsRuleBody') }}</p>
78586
79310
  </div>
78587
- <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
78588
- @for (offering of offeringPlayer.offerings; track offering.instanceId) {
78589
- <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
78590
- <button type="button" class="christianity-offering-card christianity-render-card-button" [attr.data-testid]="offeringCardTestId(offering)" valLightRipple (click)="inspectOffering(offering)">
78591
- <val-christianity-card [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()" [variant]="offering.ready ? 'baby' : 'back'"></val-christianity-card>
79311
+ <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
79312
+ @for (offering of offeringPlayer.offerings; track offering.instanceId) {
79313
+ <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
79314
+ <button
79315
+ type="button"
79316
+ class="christianity-offering-card christianity-render-card-button"
79317
+ [attr.data-testid]="offeringCardTestId(offering)"
79318
+ valLightRipple
79319
+ (click)="inspectOffering(offering)"
79320
+ >
79321
+ <val-christianity-card
79322
+ [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
79323
+ [variant]="offering.ready ? 'baby' : 'back'"
79324
+ ></val-christianity-card>
78592
79325
  </button>
78593
79326
  <div>
78594
- <strong>{{ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted') }}</strong>
79327
+ <strong>{{
79328
+ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted')
79329
+ }}</strong>
78595
79330
  <p>{{ offeringAbilityText(offering) }}</p>
78596
79331
  @if (offering.abilityName) {
78597
79332
  <button