valtech-components 4.0.585 → 4.0.587

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.585';
62
+ const VERSION = '4.0.587';
63
63
 
64
64
  // Control de estado de refresco (singleton a nivel de módulo)
65
65
  let isRefreshing = false;
@@ -62373,6 +62373,8 @@ const MINI_GAMES_I18N = {
62373
62373
  chessimalsAdventureHome: 'Modo Aventura',
62374
62374
  chessimalsAdventureHomeBody: 'Cuatro actos, dieciséis rivales. Gana estrellas.',
62375
62375
  chessimalsAdventureBoss: 'Jefe',
62376
+ chessimalsAdventureRestDone: 'Descansaste. Ganaste 1 estrella.',
62377
+ chessimalsAdventureMysteryReveal: 'Rival desconocido. A ver que pasa.',
62376
62378
  chessimalsAdventureAct1Title: 'La Sabana',
62377
62379
  chessimalsAdventureAct1Desc: 'Rivales lentos, tablero pequeño. El punto de partida.',
62378
62380
  chessimalsAdventureAct2Title: 'La Jungla',
@@ -63220,6 +63222,8 @@ const MINI_GAMES_I18N = {
63220
63222
  chessimalsAdventureHome: 'Adventure Mode',
63221
63223
  chessimalsAdventureHomeBody: 'Four acts, sixteen rivals. Earn stars.',
63222
63224
  chessimalsAdventureBoss: 'Boss',
63225
+ chessimalsAdventureRestDone: 'You rested. You earned 1 star.',
63226
+ chessimalsAdventureMysteryReveal: "Unknown rival. Let's see what happens.",
63223
63227
  chessimalsAdventureAct1Title: 'The Savanna',
63224
63228
  chessimalsAdventureAct1Desc: 'Slow rivals, small board. The starting point.',
63225
63229
  chessimalsAdventureAct2Title: 'The Jungle',
@@ -63627,6 +63631,7 @@ class ChessimalsAdventureComponent {
63627
63631
  this.i18n = inject(I18nService);
63628
63632
  this.router = inject(Router);
63629
63633
  this.progressService = inject(ChessimalsAdventureProgressService);
63634
+ this.toast = inject(ToastService);
63630
63635
  this.progress = toSignal(this.progressService.watchProgress(), {
63631
63636
  initialValue: undefined,
63632
63637
  });
@@ -63682,8 +63687,14 @@ class ChessimalsAdventureComponent {
63682
63687
  onNodeTap(nodeView) {
63683
63688
  if (nodeView.status === 'locked')
63684
63689
  return;
63685
- if (nodeView.node.type === 'rest' || nodeView.node.type === 'mystery')
63690
+ if (nodeView.node.type === 'rest') {
63691
+ this.onRestTap(nodeView);
63692
+ return;
63693
+ }
63694
+ if (nodeView.node.type === 'mystery') {
63695
+ this.onMysteryTap(nodeView);
63686
63696
  return;
63697
+ }
63687
63698
  void this.router.navigate([ADVENTURE_ROUTE_BASE], {
63688
63699
  queryParams: {
63689
63700
  adventureNode: nodeView.node.id,
@@ -63693,6 +63704,29 @@ class ChessimalsAdventureComponent {
63693
63704
  },
63694
63705
  });
63695
63706
  }
63707
+ onRestTap(nodeView) {
63708
+ if (nodeView.status === 'completed')
63709
+ return;
63710
+ void this.progressService.recordNodeCompleted(nodeView.node.id, 1).then(() => {
63711
+ this.toast.show({ message: this.t('chessimalsAdventureRestDone'), color: 'dark', duration: 2500 });
63712
+ });
63713
+ }
63714
+ onMysteryTap(nodeView) {
63715
+ const act = CHESSIMALS_ADVENTURE_ACTS.find(a => a.id === nodeView.node.actId);
63716
+ const battleNodes = act?.nodes.filter(n => n.type === 'battle' && n.profileId) ?? [];
63717
+ if (!battleNodes.length)
63718
+ return;
63719
+ const pick = battleNodes[Math.floor(Math.random() * battleNodes.length)];
63720
+ this.toast.show({ message: this.t('chessimalsAdventureMysteryReveal'), color: 'dark', duration: 2000 });
63721
+ void this.router.navigate([ADVENTURE_ROUTE_BASE], {
63722
+ queryParams: {
63723
+ adventureNode: nodeView.node.id,
63724
+ profileId: pick.profileId,
63725
+ difficulty: pick.difficulty,
63726
+ boardSize: pick.boardSize ?? 6,
63727
+ },
63728
+ });
63729
+ }
63696
63730
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ChessimalsAdventureComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
63697
63731
  static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: ChessimalsAdventureComponent, isStandalone: true, selector: "val-chessimals-adventure", ngImport: i0, template: `
63698
63732
  <div class="adventure-hub">
@@ -73958,7 +73992,14 @@ function canPlayCard(game, seat, card) {
73958
73992
  if (card.type === 'offering')
73959
73993
  return !player.offeringPlayedThisTurn;
73960
73994
  if (card.type === 'character') {
73961
- return readyOfferings(player) >= (card.cost ?? 0) && player.battleZone.length < CHRISTIANITY_BATTLE_SLOTS;
73995
+ return (readyOfferings(player) >= (card.cost ?? 0) &&
73996
+ player.battleZone.length < CHRISTIANITY_BATTLE_SLOTS);
73997
+ }
73998
+ if (card.type === 'action' ||
73999
+ card.type === 'wisdom' ||
74000
+ card.type === 'event' ||
74001
+ card.type === 'tool') {
74002
+ return readyOfferings(player) >= (card.cost ?? 0);
73962
74003
  }
73963
74004
  if (card.type === 'place')
73964
74005
  return true;
@@ -73994,7 +74035,7 @@ function playCardFromHand(game, seat, card, options = {}) {
73994
74035
  player.offeringPlayedThisTurn = true;
73995
74036
  }
73996
74037
  else if (card.type === 'character') {
73997
- const occupiedSlots = new Set(player.battleZone.map((character, index) => Number.isInteger(character.slot) ? character.slot ?? index : index));
74038
+ const occupiedSlots = new Set(player.battleZone.map((character, index) => Number.isInteger(character.slot) ? (character.slot ?? index) : index));
73998
74039
  const targetSlot = options.slotIndex ?? [0, 1, 2, 3].find(index => !occupiedSlots.has(index));
73999
74040
  if (targetSlot === undefined || occupiedSlots.has(targetSlot)) {
74000
74041
  return { game, played: false, playedOfferingValue: 0, spentOfferings: 0, log: [] };
@@ -74016,6 +74057,16 @@ function playCardFromHand(game, seat, card, options = {}) {
74016
74057
  });
74017
74058
  player.battleZone.sort((left, right) => (left.slot ?? 0) - (right.slot ?? 0));
74018
74059
  }
74060
+ else if (card.type === 'action' ||
74061
+ card.type === 'wisdom' ||
74062
+ card.type === 'event' ||
74063
+ card.type === 'tool') {
74064
+ spentOfferings = card.cost ?? 0;
74065
+ if (spentOfferings > 0) {
74066
+ spendOfferings(player, spentOfferings);
74067
+ }
74068
+ next.historyZone.push(card.id);
74069
+ }
74019
74070
  else if (card.type === 'place') {
74020
74071
  const previousPlace = next.placeZone[0];
74021
74072
  if (previousPlace) {
@@ -74052,9 +74103,7 @@ function resolveOfferingAbility(game, seat, offeringInstanceId, messages) {
74052
74103
  }
74053
74104
  const reserveGain = offeringAbilityReserveGain(liveOffering.abilityText, liveOffering.value);
74054
74105
  player.temporaryOfferingReserve = (player.temporaryOfferingReserve ?? 0) + reserveGain;
74055
- player.offerings = player.offerings.map(card => (card.instanceId === offeringInstanceId
74056
- ? { ...card, abilityUsedThisTurn: true }
74057
- : card));
74106
+ player.offerings = player.offerings.map(card => card.instanceId === offeringInstanceId ? { ...card, abilityUsedThisTurn: true } : card);
74058
74107
  player.leaderHp = Math.max(0, player.leaderHp - 1);
74059
74108
  next.revision++;
74060
74109
  const log = [`${liveOffering.name}: ${liveOffering.abilityName}.`];
@@ -74069,6 +74118,12 @@ function playCardLog(card, displayName) {
74069
74118
  return `${displayName} queda lista como ofrenda.`;
74070
74119
  if (card.type === 'place')
74071
74120
  return `${displayName} queda como lugar activo.`;
74121
+ if (card.type === 'action' || card.type === 'event')
74122
+ return `${displayName} fue jugada.`;
74123
+ if (card.type === 'wisdom')
74124
+ return `${displayName} fue revelada.`;
74125
+ if (card.type === 'tool')
74126
+ return `${displayName} fue equipada.`;
74072
74127
  return `${displayName} entra en batalla.`;
74073
74128
  }
74074
74129
  function resolveAttackLeader(game, attackerSeat, attackerInstanceId, messages) {
@@ -74083,7 +74138,9 @@ function resolveAttackLeader(game, attackerSeat, attackerInstanceId, messages) {
74083
74138
  const damage = attacker.attack;
74084
74139
  next.players[defenderSeat].leaderHp = Math.max(0, next.players[defenderSeat].leaderHp - damage);
74085
74140
  attacker.canAttack = false;
74086
- const log = [`${attacker.name} ${attackerSeat === 'P1' ? messages.attacksOpponentLeader : messages.attacksYourLeader}`];
74141
+ const log = [
74142
+ `${attacker.name} ${attackerSeat === 'P1' ? messages.attacksOpponentLeader : messages.attacksYourLeader}`,
74143
+ ];
74087
74144
  evaluateVictory(next, log, messages);
74088
74145
  next.revision++;
74089
74146
  next.log = [...log, ...next.log].slice(0, 4);
@@ -74108,7 +74165,13 @@ function resolveAttackCharacter(game, attackerSeat, attackerInstanceId, targetIn
74108
74165
  evaluateVictory(next, log, messages);
74109
74166
  next.revision++;
74110
74167
  next.log = [...log, ...next.log].slice(0, 4);
74111
- return { game: next, attacked: true, attackerDamage: target.attack, targetDamage: attacker.attack, log };
74168
+ return {
74169
+ game: next,
74170
+ attacked: true,
74171
+ attackerDamage: target.attack,
74172
+ targetDamage: attacker.attack,
74173
+ log,
74174
+ };
74112
74175
  }
74113
74176
  function advanceTurn(game, messages) {
74114
74177
  game.players[game.turnOwner].temporaryOfferingReserve = 0;
@@ -74116,7 +74179,11 @@ function advanceTurn(game, messages) {
74116
74179
  game.turnCount++;
74117
74180
  const current = game.players[game.turnOwner];
74118
74181
  current.offeringPlayedThisTurn = false;
74119
- current.offerings = current.offerings.map(offering => ({ ...offering, ready: true, abilityUsedThisTurn: false }));
74182
+ current.offerings = current.offerings.map(offering => ({
74183
+ ...offering,
74184
+ ready: true,
74185
+ abilityUsedThisTurn: false,
74186
+ }));
74120
74187
  current.battleZone = current.battleZone.map(card => ({ ...card, canAttack: true }));
74121
74188
  const drawnCard = game.sharedDeck.shift();
74122
74189
  const log = [];
@@ -74138,16 +74205,16 @@ function advanceTurn(game, messages) {
74138
74205
  ];
74139
74206
  }
74140
74207
  function evaluateVictory(game, log, messages) {
74141
- if (game.players.P1.leaderHp > 0
74142
- && game.players.P2.leaderHp > 0
74143
- && game.sharedDeck.length > 0)
74208
+ if (game.players.P1.leaderHp > 0 && game.players.P2.leaderHp > 0 && game.sharedDeck.length > 0)
74144
74209
  return;
74145
74210
  game.status = 'finished';
74146
74211
  const winner = game.players.P1.leaderHp <= 0
74147
74212
  ? 'P2'
74148
74213
  : game.players.P2.leaderHp <= 0
74149
74214
  ? 'P1'
74150
- : game.players.P1.leaderHp >= game.players.P2.leaderHp ? 'P1' : 'P2';
74215
+ : game.players.P1.leaderHp >= game.players.P2.leaderHp
74216
+ ? 'P1'
74217
+ : 'P2';
74151
74218
  game.matchScore[winner] += 1;
74152
74219
  log.push(winner === 'P1' ? messages.youWin : messages.opponentWins);
74153
74220
  }
@@ -74539,10 +74606,58 @@ const STARTER_CARDS = {
74539
74606
  author: 'MVP',
74540
74607
  rarity: 'normal',
74541
74608
  },
74542
- 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' },
74543
- 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' },
74544
- 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' },
74545
- 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' },
74609
+ p_guard: {
74610
+ id: 'p_guard',
74611
+ name: 'Guard',
74612
+ type: 'character',
74613
+ cost: 2,
74614
+ attack: 1,
74615
+ life: 3,
74616
+ edition: 'GEN',
74617
+ collectorNumber: '003/020',
74618
+ year: '2026',
74619
+ author: 'MVP',
74620
+ rarity: 'normal',
74621
+ },
74622
+ p_warrior: {
74623
+ id: 'p_warrior',
74624
+ name: 'Warrior',
74625
+ type: 'character',
74626
+ cost: 2,
74627
+ attack: 2,
74628
+ life: 2,
74629
+ edition: 'GEN',
74630
+ collectorNumber: '004/020',
74631
+ year: '2026',
74632
+ author: 'MVP',
74633
+ rarity: 'normal',
74634
+ },
74635
+ p_elder: {
74636
+ id: 'p_elder',
74637
+ name: 'Elder',
74638
+ type: 'character',
74639
+ cost: 3,
74640
+ attack: 2,
74641
+ life: 4,
74642
+ edition: 'GEN',
74643
+ collectorNumber: '005/020',
74644
+ year: '2026',
74645
+ author: 'MVP',
74646
+ rarity: 'uncommon',
74647
+ },
74648
+ p_champion: {
74649
+ id: 'p_champion',
74650
+ name: 'Champion',
74651
+ type: 'character',
74652
+ cost: 4,
74653
+ attack: 4,
74654
+ life: 4,
74655
+ edition: 'GEN',
74656
+ collectorNumber: '006/020',
74657
+ year: '2026',
74658
+ author: 'MVP',
74659
+ rarity: 'rare',
74660
+ },
74546
74661
  };
74547
74662
  const STARTING_SHARED_DECK = [
74548
74663
  'o_simple',
@@ -74586,7 +74701,14 @@ const STARTING_GAME = {
74586
74701
  leaderHp: 15,
74587
74702
  leaderMaxHp: 15,
74588
74703
  offerings: [
74589
- { instanceId: 'p1_offering_001', cardId: 'o_simple', name: 'Ofrenda simple', owner: 'P1', ready: true, value: 1 },
74704
+ {
74705
+ instanceId: 'p1_offering_001',
74706
+ cardId: 'o_simple',
74707
+ name: 'Ofrenda simple',
74708
+ owner: 'P1',
74709
+ ready: true,
74710
+ value: 1,
74711
+ },
74590
74712
  {
74591
74713
  instanceId: 'p1_offering_002',
74592
74714
  cardId: 'o_forbidden',
@@ -74611,7 +74733,16 @@ const STARTING_GAME = {
74611
74733
  leaderName: 'Noah',
74612
74734
  leaderHp: 15,
74613
74735
  leaderMaxHp: 15,
74614
- offerings: [{ instanceId: 'p2_offering_001', cardId: 'o_simple', name: 'Ofrenda simple', owner: 'P2', ready: true, value: 1 }],
74736
+ offerings: [
74737
+ {
74738
+ instanceId: 'p2_offering_001',
74739
+ cardId: 'o_simple',
74740
+ name: 'Ofrenda simple',
74741
+ owner: 'P2',
74742
+ ready: true,
74743
+ value: 1,
74744
+ },
74745
+ ],
74615
74746
  offeringPlayedThisTurn: false,
74616
74747
  handCount: 4,
74617
74748
  battleZone: [
@@ -74646,7 +74777,9 @@ class ChristianityGameComponent {
74646
74777
  const nextIndex = (currentIndex + direction + items.length) % items.length;
74647
74778
  const item = items[nextIndex];
74648
74779
  if (item.kind === 'history') {
74649
- return this.definitionToRenderCard(this.cardDefinition(item.cardId), { statusText: this.t('christianityHistory') });
74780
+ return this.definitionToRenderCard(this.cardDefinition(item.cardId), {
74781
+ statusText: this.t('christianityHistory'),
74782
+ });
74650
74783
  }
74651
74784
  if (item.kind === 'hand') {
74652
74785
  return this.definitionToRenderCard(this.cardDefinition(item.cardId));
@@ -74753,7 +74886,9 @@ class ChristianityGameComponent {
74753
74886
  if (this.tutorialPhase() === 'coin') {
74754
74887
  return setup.coinWinner === 'P1' ? this.t('christianityCoinYouWon') : this.t('christianityCoinRivalWon');
74755
74888
  }
74756
- return setup.leaderPicker === 'P1' ? this.t('christianityChooseLeaderTitle') : this.t('christianityRivalChoosesLeaderTitle');
74889
+ return setup.leaderPicker === 'P1'
74890
+ ? this.t('christianityChooseLeaderTitle')
74891
+ : this.t('christianityRivalChoosesLeaderTitle');
74757
74892
  });
74758
74893
  this.setupBody = computed(() => {
74759
74894
  const setup = this.tutorialSetup();
@@ -74780,7 +74915,11 @@ class ChristianityGameComponent {
74780
74915
  this.hasOpponentGuards = computed(() => this.opponent().battleZone.length > 0);
74781
74916
  this.opponentHandCount = computed(() => this.opponent().handCount ?? this.opponent().hand?.length ?? 0);
74782
74917
  this.galleryCards = computed(() => {
74783
- const query = this.gallerySearch().trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
74918
+ const query = this.gallerySearch()
74919
+ .trim()
74920
+ .toLowerCase()
74921
+ .normalize('NFD')
74922
+ .replace(/[\u0300-\u036f]/g, '');
74784
74923
  const type = this.galleryType();
74785
74924
  return this.allCatalogCards()
74786
74925
  .filter(card => this.isClassicGalleryCard(card))
@@ -74815,12 +74954,12 @@ class ChristianityGameComponent {
74815
74954
  { value: 'place', label: this.t('christianityPlaceCard') },
74816
74955
  { value: 'event', label: this.t('christianityEventCard') },
74817
74956
  ]);
74818
- this.hasModalOverlay = computed(() => !!this.selectedPreview()
74819
- || !!this.selectedInfo()
74820
- || this.showHistory()
74821
- || this.showPlace()
74822
- || this.showGallery()
74823
- || !!this.offeringsSeat());
74957
+ this.hasModalOverlay = computed(() => !!this.selectedPreview() ||
74958
+ !!this.selectedInfo() ||
74959
+ this.showHistory() ||
74960
+ this.showPlace() ||
74961
+ this.showGallery() ||
74962
+ !!this.offeringsSeat());
74824
74963
  this.previewCarouselItems = computed(() => this.currentPreviewCarouselItems());
74825
74964
  this.canNavigatePreview = computed(() => this.previewCarouselItems().length > 1);
74826
74965
  this.previewPositionLabel = computed(() => {
@@ -74846,25 +74985,28 @@ class ChristianityGameComponent {
74846
74985
  canUse: this.canUsePreviewAbility(index),
74847
74986
  }))
74848
74987
  : text.abilityName || text.abilityText
74849
- ? [{
74988
+ ? [
74989
+ {
74850
74990
  index: 0,
74851
74991
  title: text.abilityName ?? this.t('christianityAbility'),
74852
74992
  body: text.abilityText ?? '',
74853
74993
  canUse: this.canUsePreviewAbility(0),
74854
- }]
74855
- : [{
74994
+ },
74995
+ ]
74996
+ : [
74997
+ {
74856
74998
  index: 0,
74857
74999
  title: this.t('christianityNoAbilityTitle'),
74858
75000
  body: this.t('christianityNoAbilityBody'),
74859
75001
  canUse: false,
74860
- }];
75002
+ },
75003
+ ];
74861
75004
  return abilities;
74862
75005
  });
74863
75006
  this.showPreviewAbilityTabs = computed(() => {
74864
75007
  const abilities = this.previewAbilities();
74865
- return abilities.length > 1
74866
- || !this.isPreviewAbilityPlaceholder(abilities[0])
74867
- && (abilities[0]?.title ?? '') !== this.t('christianityAbility');
75008
+ return (abilities.length > 1 ||
75009
+ (!this.isPreviewAbilityPlaceholder(abilities[0]) && (abilities[0]?.title ?? '') !== this.t('christianityAbility')));
74868
75010
  });
74869
75011
  this.showPreviewAbilities = computed(() => {
74870
75012
  const preview = this.selectedPreview();
@@ -75037,7 +75179,9 @@ class ChristianityGameComponent {
75037
75179
  return this.t('christianityWaitTurn');
75038
75180
  }
75039
75181
  if (attacker) {
75040
- return this.hasOpponentGuards() ? this.t('christianityChooseCharacterTarget') : this.t('christianityChooseTarget');
75182
+ return this.hasOpponentGuards()
75183
+ ? this.t('christianityChooseCharacterTarget')
75184
+ : this.t('christianityChooseTarget');
75041
75185
  }
75042
75186
  if (handCard) {
75043
75187
  if (!this.canPlay(handCard)) {
@@ -75046,12 +75190,18 @@ class ChristianityGameComponent {
75046
75190
  if (handCard.type === 'place') {
75047
75191
  return this.t('christianityChoosePlaceSlot');
75048
75192
  }
75193
+ if (handCard.type === 'action' ||
75194
+ handCard.type === 'wisdom' ||
75195
+ handCard.type === 'event' ||
75196
+ handCard.type === 'tool') {
75197
+ return this.t('christianityPlayCard');
75198
+ }
75049
75199
  return handCard.type === 'offering' ? this.t('christianityPlaceOffering') : this.t('christianityChooseSlot');
75050
75200
  }
75051
75201
  return this.t('christianityChooseCard');
75052
75202
  });
75053
75203
  this.i18n.registerDefaults('MiniGames', MINI_GAMES_I18N);
75054
- effect((onCleanup) => {
75204
+ effect(onCleanup => {
75055
75205
  if (!this.hasModalOverlay())
75056
75206
  return;
75057
75207
  const previousOverflow = this.document.body.style.overflow;
@@ -75107,7 +75257,7 @@ class ChristianityGameComponent {
75107
75257
  try {
75108
75258
  const response = await fetch(GENESIS_CATALOG_URL);
75109
75259
  if (response.ok) {
75110
- const catalog = await response.json();
75260
+ const catalog = (await response.json());
75111
75261
  const cards = (catalog.cards ?? []).map(card => this.normalizeCatalogCard(card, catalog));
75112
75262
  if (cards.length) {
75113
75263
  this.cardCatalog.set(Object.fromEntries(cards.map(card => [card.id, card])));
@@ -75137,19 +75287,19 @@ class ChristianityGameComponent {
75137
75287
  const editionIcon = card.editionIcon ?? catalog.edition?.icon ?? GENESIS_ICON_URL;
75138
75288
  const verseText = card.verseText ?? card.bible?.text;
75139
75289
  const scripture = card.scripture ?? this.formatScripture(card.bible);
75140
- const offeringValue = card.type === 'offering' ? card.offeringValue ?? card.life ?? card.cost ?? 1 : card.offeringValue;
75290
+ const offeringValue = card.type === 'offering' ? (card.offeringValue ?? card.life ?? card.cost ?? 1) : card.offeringValue;
75141
75291
  const life = card.type === 'leader'
75142
- ? card.leaderLife ?? card.life ?? 10
75292
+ ? (card.leaderLife ?? card.life ?? 10)
75143
75293
  : card.type === 'offering'
75144
75294
  ? offeringValue
75145
- : card.life ?? 0;
75295
+ : (card.life ?? 0);
75146
75296
  return {
75147
75297
  ...card,
75148
- cost: card.type === 'offering' ? 0 : card.cost ?? 0,
75298
+ cost: card.type === 'offering' ? 0 : (card.cost ?? 0),
75149
75299
  offeringValue,
75150
75300
  life,
75151
- leaderLife: card.type === 'leader' ? card.leaderLife ?? life : card.leaderLife,
75152
- attack: card.type === 'character' ? card.attack ?? 0 : card.attack,
75301
+ leaderLife: card.type === 'leader' ? (card.leaderLife ?? life) : card.leaderLife,
75302
+ attack: card.type === 'character' ? (card.attack ?? 0) : card.attack,
75153
75303
  abilityName: card.abilityName ?? card.abilities?.[0]?.name,
75154
75304
  abilityText: card.abilityText ?? card.abilities?.[0]?.text,
75155
75305
  verseText,
@@ -75171,21 +75321,27 @@ class ChristianityGameComponent {
75171
75321
  createTutorialGame(cards, options = {}) {
75172
75322
  const leaders = this.tutorialLeaderCards(cards);
75173
75323
  const playerLeader = this.cardById(options.playerLeaderId) ?? this.findCard(leaders, 'Adán') ?? leaders[0];
75174
- const opponentLeader = this.cardById(options.opponentLeaderId) ?? this.findCard(leaders, 'Noé') ?? leaders.find(card => card.id !== playerLeader?.id) ?? leaders[0];
75175
- const offerings = cards.filter(card => card.type === 'offering' && this.isClassicGalleryCard(card)).slice(0, TUTORIAL_OFFERING_LIMIT);
75324
+ const opponentLeader = this.cardById(options.opponentLeaderId) ??
75325
+ this.findCard(leaders, 'Noé') ??
75326
+ leaders.find(card => card.id !== playerLeader?.id) ??
75327
+ leaders[0];
75328
+ const offerings = cards
75329
+ .filter(card => card.type === 'offering' && this.isClassicGalleryCard(card))
75330
+ .slice(0, TUTORIAL_OFFERING_LIMIT);
75176
75331
  const places = cards.filter(card => card.type === 'place');
75177
75332
  const characters = cards
75178
- .filter(card => card.type === 'character' && this.isClassicGalleryCard(card) && (card.cost ?? 0) <= 4 && (card.attack ?? 0) >= 0 && (card.life ?? 0) > 0)
75333
+ .filter(card => card.type === 'character' &&
75334
+ this.isClassicGalleryCard(card) &&
75335
+ (card.cost ?? 0) <= 4 &&
75336
+ (card.attack ?? 0) >= 0 &&
75337
+ (card.life ?? 0) > 0)
75179
75338
  .slice(0, TUTORIAL_CHARACTER_LIMIT);
75180
- const tutorialDeck = [
75181
- ...offerings,
75182
- ...characters,
75183
- ...offerings,
75184
- ...characters.slice(0, 6),
75185
- ].slice(0, 28);
75339
+ const tutorialDeck = [...offerings, ...characters, ...offerings, ...characters.slice(0, 6)].slice(0, 28);
75186
75340
  const shuffledDeck = this.prepareTutorialOpeningDeck(this.shuffleCards(tutorialDeck, options.seed ?? TUTORIAL_SEED));
75187
75341
  const playerHand = shuffledDeck.slice(0, TUTORIAL_OPENING_HAND_SIZE).map(card => card.id);
75188
- const opponentHand = shuffledDeck.slice(TUTORIAL_OPENING_HAND_SIZE, TUTORIAL_OPENING_HAND_SIZE * 2).map(card => card.id);
75342
+ const opponentHand = shuffledDeck
75343
+ .slice(TUTORIAL_OPENING_HAND_SIZE, TUTORIAL_OPENING_HAND_SIZE * 2)
75344
+ .map(card => card.id);
75189
75345
  const sharedDeck = shuffledDeck.slice(TUTORIAL_OPENING_HAND_SIZE * 2).map(card => card.id);
75190
75346
  return {
75191
75347
  ...structuredClone(STARTING_GAME),
@@ -75255,7 +75411,7 @@ class ChristianityGameComponent {
75255
75411
  return cards.find(card => this.translatedCardText(card).name.toLowerCase().includes(namePart.toLowerCase()));
75256
75412
  }
75257
75413
  cardById(cardId) {
75258
- return cardId ? this.cardCatalog()[cardId] ?? STARTER_CARDS[cardId] : undefined;
75414
+ return cardId ? (this.cardCatalog()[cardId] ?? STARTER_CARDS[cardId]) : undefined;
75259
75415
  }
75260
75416
  tutorialLeaderCards(cards) {
75261
75417
  const leaders = cards
@@ -75357,7 +75513,7 @@ class ChristianityGameComponent {
75357
75513
  type: card.type,
75358
75514
  typeLabel: this.cardTypeLabel(card),
75359
75515
  cost: card.cost,
75360
- offeringValue: card.type === 'offering' ? card.offeringValue ?? card.life : undefined,
75516
+ offeringValue: card.type === 'offering' ? (card.offeringValue ?? card.life) : undefined,
75361
75517
  attack: card.type === 'character' ? card.attack : undefined,
75362
75518
  life: card.life,
75363
75519
  currentHp: card.life,
@@ -75495,8 +75651,17 @@ class ChristianityGameComponent {
75495
75651
  return this.cardTypeIconUrlForType('character');
75496
75652
  }
75497
75653
  cardTypeIconUrlForType(type) {
75498
- const normalized = type.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
75499
- const icon = normalized === 'lider' ? 'leader' : normalized === 'personaje' ? 'character' : normalized === 'ofrenda' ? 'offering' : normalized;
75654
+ const normalized = type
75655
+ .toLowerCase()
75656
+ .normalize('NFD')
75657
+ .replace(/[\u0300-\u036f]/g, '');
75658
+ const icon = normalized === 'lider'
75659
+ ? 'leader'
75660
+ : normalized === 'personaje'
75661
+ ? 'character'
75662
+ : normalized === 'ofrenda'
75663
+ ? 'offering'
75664
+ : normalized;
75500
75665
  return `assets/games/christianity/icons/${icon}.png`;
75501
75666
  }
75502
75667
  offeringPips(value) {
@@ -75509,7 +75674,7 @@ class ChristianityGameComponent {
75509
75674
  const cards = this.game().players[seat].battleZone;
75510
75675
  const positionedCards = new Map();
75511
75676
  cards.forEach((card, index) => {
75512
- const slot = Number.isInteger(card.slot) ? card.slot ?? index : index;
75677
+ const slot = Number.isInteger(card.slot) ? (card.slot ?? index) : index;
75513
75678
  if (slot >= 0 && slot < 4 && !positionedCards.has(slot)) {
75514
75679
  positionedCards.set(slot, card);
75515
75680
  }
@@ -75523,13 +75688,20 @@ class ChristianityGameComponent {
75523
75688
  return `christianity-battle-slot-${index}`;
75524
75689
  }
75525
75690
  dropTargetIds() {
75526
- return ['christianity-offerings-drop', 'christianity-place-drop', ...this.battleSlots('P1').map((_, index) => this.battleSlotDropId(index))];
75691
+ return [
75692
+ 'christianity-offerings-drop',
75693
+ 'christianity-place-drop',
75694
+ ...this.battleSlots('P1').map((_, index) => this.battleSlotDropId(index)),
75695
+ ];
75527
75696
  }
75528
75697
  ownAttackerDropSourceIds() {
75529
75698
  return ['christianity-player-battle-source'];
75530
75699
  }
75531
75700
  attackTargetDropIds() {
75532
- return ['christianity-opponent-leader-drop', ...this.opponent().battleZone.map(card => this.opponentTargetDropId(card))];
75701
+ return [
75702
+ 'christianity-opponent-leader-drop',
75703
+ ...this.opponent().battleZone.map(card => this.opponentTargetDropId(card)),
75704
+ ];
75533
75705
  }
75534
75706
  opponentTargetDropId(card) {
75535
75707
  return `christianity-opponent-target-${card.instanceId}`;
@@ -75665,7 +75837,11 @@ class ChristianityGameComponent {
75665
75837
  return `christianity-use-offering-${offering.owner}-${this.testIdPart(offering.instanceId)}`;
75666
75838
  }
75667
75839
  testIdPart(value) {
75668
- return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 48) || 'item';
75840
+ return (value
75841
+ .toLowerCase()
75842
+ .replace(/[^a-z0-9]+/g, '-')
75843
+ .replace(/^-|-$/g, '')
75844
+ .slice(0, 48) || 'item');
75669
75845
  }
75670
75846
  canPlaySelected() {
75671
75847
  const selected = this.selectedHandCard();
@@ -75781,10 +75957,10 @@ class ChristianityGameComponent {
75781
75957
  this.attackLeader();
75782
75958
  }
75783
75959
  isValidAttackerInstance(instanceId) {
75784
- return this.game().status === 'in_progress'
75785
- && this.isOwnTurn()
75786
- && !this.combatAnimation()
75787
- && this.player().battleZone.some(card => card.instanceId === instanceId && card.canAttack);
75960
+ return (this.game().status === 'in_progress' &&
75961
+ this.isOwnTurn() &&
75962
+ !this.combatAnimation() &&
75963
+ this.player().battleZone.some(card => card.instanceId === instanceId && card.canAttack));
75788
75964
  }
75789
75965
  selectHandCard(cardId) {
75790
75966
  const wasSelected = this.selectedHandCard() === cardId;
@@ -75797,9 +75973,7 @@ class ChristianityGameComponent {
75797
75973
  if (!cardId)
75798
75974
  return;
75799
75975
  const card = this.cardDefinition(cardId);
75800
- const resolvedSlotIndex = card.type === 'character' && slotIndex === undefined
75801
- ? this.firstAvailableBattleSlotIndex()
75802
- : slotIndex;
75976
+ const resolvedSlotIndex = card.type === 'character' && slotIndex === undefined ? this.firstAvailableBattleSlotIndex() : slotIndex;
75803
75977
  this.playCardFromHand(cardId, resolvedSlotIndex);
75804
75978
  }
75805
75979
  firstAvailableBattleSlotIndex() {
@@ -75880,7 +76054,12 @@ class ChristianityGameComponent {
75880
76054
  });
75881
76055
  return;
75882
76056
  }
75883
- this.startCombatAnimation({ attackerInstanceId: attacker.instanceId, attackerSeat: 'P1', targetSeat: 'P2', target: 'leader' });
76057
+ this.startCombatAnimation({
76058
+ attackerInstanceId: attacker.instanceId,
76059
+ attackerSeat: 'P1',
76060
+ targetSeat: 'P2',
76061
+ target: 'leader',
76062
+ });
75884
76063
  this.selectedAttacker.set(null);
75885
76064
  this.combatResolutionTimer = window.setTimeout(() => {
75886
76065
  let damage = 0;
@@ -75975,7 +76154,7 @@ class ChristianityGameComponent {
75975
76154
  this.resetTransientTableState();
75976
76155
  const setup = this.createTutorialSetup();
75977
76156
  this.tutorialSetup.set(setup);
75978
- this.selectedTutorialLeaderId.set(setup.leaderPicker === 'P1' ? setup.availableLeaders[0] ?? null : null);
76157
+ this.selectedTutorialLeaderId.set(setup.leaderPicker === 'P1' ? (setup.availableLeaders[0] ?? null) : null);
75979
76158
  this.leaderChoiceSeconds.set(TUTORIAL_LEADER_CHOICE_SECONDS);
75980
76159
  this.tutorialPhase.set('coin');
75981
76160
  this.screen.set('table');
@@ -76037,7 +76216,7 @@ class ChristianityGameComponent {
76037
76216
  return;
76038
76217
  this.selectTutorialLeader(leaderId);
76039
76218
  this.inspectHandCard(leaderId);
76040
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'leader-choice' } : preview);
76219
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'leader-choice' } : preview));
76041
76220
  }
76042
76221
  toggleOptions() {
76043
76222
  this.showOptions.update(value => !value);
@@ -76394,7 +76573,7 @@ class ChristianityGameComponent {
76394
76573
  }
76395
76574
  }
76396
76575
  combatAnimationAttacker(animation) {
76397
- return this.game().players[animation.attackerSeat].battleZone.find(card => card.instanceId === animation.attackerInstanceId) ?? null;
76576
+ return (this.game().players[animation.attackerSeat].battleZone.find(card => card.instanceId === animation.attackerInstanceId) ?? null);
76398
76577
  }
76399
76578
  combatAnimationTargetLabel(animation) {
76400
76579
  if (animation.target === 'leader') {
@@ -76433,7 +76612,7 @@ class ChristianityGameComponent {
76433
76612
  const dx = to.x - from.x;
76434
76613
  const dy = to.y - from.y;
76435
76614
  const length = Math.max(12, Math.hypot(dx, dy));
76436
- const angle = Math.atan2(dy, dx) * 180 / Math.PI;
76615
+ const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
76437
76616
  return {
76438
76617
  fromX: `${from.x}%`,
76439
76618
  fromY: `${from.y}%`,
@@ -76523,7 +76702,9 @@ class ChristianityGameComponent {
76523
76702
  const offeringId = hand.find(cardId => this.cardDefinition(cardId).type === 'offering' && canPlayCard(game, 'P2', this.cardDefinition(cardId)));
76524
76703
  if (offeringId) {
76525
76704
  const card = this.cardDefinition(offeringId);
76526
- const result = playCardFromHand(game, 'P2', card, { displayName: this.translatedCardText(card).name });
76705
+ const result = playCardFromHand(game, 'P2', card, {
76706
+ displayName: this.translatedCardText(card).name,
76707
+ });
76527
76708
  if (result.played) {
76528
76709
  Object.assign(game, result.game);
76529
76710
  log.push(...result.log);
@@ -76594,7 +76775,9 @@ class ChristianityGameComponent {
76594
76775
  title: leader ? this.translatedCardText(leader).name : player.leaderName,
76595
76776
  eyebrow: this.t('christianityLeaderCard'),
76596
76777
  meta: `${player.leaderHp}/${player.leaderMaxHp}`,
76597
- body: seat === 'P2' && this.hasOpponentGuards() ? this.t('christianityLeaderProtected') : this.t('christianityLeaderBody'),
76778
+ body: seat === 'P2' && this.hasOpponentGuards()
76779
+ ? this.t('christianityLeaderProtected')
76780
+ : this.t('christianityLeaderBody'),
76598
76781
  cardId: player.leaderCardId,
76599
76782
  source: 'board',
76600
76783
  });
@@ -76669,7 +76852,7 @@ class ChristianityGameComponent {
76669
76852
  const preview = this.selectedPreview();
76670
76853
  if (!preview?.seat || !preview.offeringInstanceId)
76671
76854
  return null;
76672
- return this.game().players[preview.seat].offerings.find(card => card.instanceId === preview.offeringInstanceId) ?? null;
76855
+ return (this.game().players[preview.seat].offerings.find(card => card.instanceId === preview.offeringInstanceId) ?? null);
76673
76856
  }
76674
76857
  canUsePreviewAbility(index) {
76675
76858
  const offering = this.selectedPreviewOffering();
@@ -76690,7 +76873,9 @@ class ChristianityGameComponent {
76690
76873
  this.t('christianityDeckRuleShared'),
76691
76874
  this.t('christianityDeckRuleDraw'),
76692
76875
  this.t('christianityDeckRuleEmpty'),
76693
- nextCard ? `${this.t('christianityDeckNextCard')}: ${this.cardDisplayName(nextCard)}` : this.t('christianityDeckNoNextCard'),
76876
+ nextCard
76877
+ ? `${this.t('christianityDeckNextCard')}: ${this.cardDisplayName(nextCard)}`
76878
+ : this.t('christianityDeckNoNextCard'),
76694
76879
  ],
76695
76880
  });
76696
76881
  }
@@ -76705,15 +76890,12 @@ class ChristianityGameComponent {
76705
76890
  eyebrow: this.t('christianitySharedZone'),
76706
76891
  meta: this.t('christianityEmpty'),
76707
76892
  body: this.t('christianityPlaceRuleBody'),
76708
- facts: [
76709
- this.t('christianityPlaceSlotPlayable'),
76710
- this.t('christianityPlaceOnlyOne'),
76711
- ],
76893
+ facts: [this.t('christianityPlaceSlotPlayable'), this.t('christianityPlaceOnlyOne')],
76712
76894
  });
76713
76895
  }
76714
76896
  inspectPlaceCard(cardId) {
76715
76897
  this.inspectHandCard(cardId);
76716
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'place' } : preview);
76898
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'place' } : preview));
76717
76899
  }
76718
76900
  inspectHowToPlay() {
76719
76901
  this.selectedPreview.set(null);
@@ -76750,11 +76932,11 @@ class ChristianityGameComponent {
76750
76932
  }
76751
76933
  inspectGalleryCard(cardId) {
76752
76934
  this.inspectHandCard(cardId);
76753
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'gallery' } : preview);
76935
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'gallery' } : preview));
76754
76936
  }
76755
76937
  inspectHistoryCard(cardId) {
76756
76938
  this.inspectHandCard(cardId);
76757
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'history' } : preview);
76939
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'history' } : preview));
76758
76940
  }
76759
76941
  inspectCharacter(card) {
76760
76942
  this.selectedInfo.set(null);
@@ -76858,418 +77040,276 @@ class ChristianityGameComponent {
76858
77040
  </section>
76859
77041
  </section>
76860
77042
  } @else {
76861
- <section class="christianity-match-strip" data-testid="christianity-match-strip" [attr.aria-label]="t('christianityMatchScore')">
76862
- <article class="christianity-match-player">
76863
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
76864
- <div>
76865
- <strong>{{ player().displayName }}</strong>
76866
- <small>{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small>
76867
- </div>
76868
- </article>
76869
- <div class="christianity-scoreboard">
76870
- <span>{{ t('christianityMatch') }}</span>
76871
- <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
76872
- </div>
76873
- <article class="christianity-match-player christianity-match-player--opponent">
76874
- <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
76875
- <div>
76876
- <strong>{{ opponent().displayName }}</strong>
76877
- <small>{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{ opponent().leaderMaxHp }}</small>
77043
+ <section
77044
+ class="christianity-match-strip"
77045
+ data-testid="christianity-match-strip"
77046
+ [attr.aria-label]="t('christianityMatchScore')"
77047
+ >
77048
+ <article class="christianity-match-player">
77049
+ <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
77050
+ <div>
77051
+ <strong>{{ player().displayName }}</strong>
77052
+ <small
77053
+ >{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small
77054
+ >
77055
+ </div>
77056
+ </article>
77057
+ <div class="christianity-scoreboard">
77058
+ <span>{{ t('christianityMatch') }}</span>
77059
+ <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
76878
77060
  </div>
76879
- </article>
76880
- </section>
77061
+ <article class="christianity-match-player christianity-match-player--opponent">
77062
+ <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
77063
+ <div>
77064
+ <strong>{{ opponent().displayName }}</strong>
77065
+ <small
77066
+ >{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{
77067
+ opponent().leaderMaxHp
77068
+ }}</small
77069
+ >
77070
+ </div>
77071
+ </article>
77072
+ </section>
76881
77073
 
76882
- <section
76883
- class="christianity-turn-banner"
76884
- data-testid="christianity-turn-banner"
76885
- [class.is-own-turn]="isOwnTurn()"
76886
- [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
76887
- [class.is-finished]="game().status === 'finished'"
76888
- aria-live="polite"
76889
- >
76890
- <div>
76891
- <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
76892
- <strong>{{ turnBannerTitle() }}</strong>
76893
- <p>{{ turnBannerBody() }}</p>
76894
- </div>
76895
- <div class="christianity-turn-actions">
76896
- <button
76897
- type="button"
76898
- class="christianity-end-turn"
76899
- data-testid="christianity-end-turn"
76900
- [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
76901
- (click)="endTurn()"
76902
- >
76903
- {{ t('christianityEndTurn') }}
76904
- </button>
76905
- <button type="button" class="christianity-options-button" data-testid="christianity-options-button" valLightRipple (click)="toggleOptions()">
76906
- {{ t('christianityOptions') }}
76907
- </button>
76908
- </div>
76909
- @if (showOptions()) {
76910
- <div class="christianity-options-menu" data-testid="christianity-options-menu">
76911
- <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
76912
- <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">{{ t('christianityResign') }}</button>
76913
- <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
77074
+ <section
77075
+ class="christianity-turn-banner"
77076
+ data-testid="christianity-turn-banner"
77077
+ [class.is-own-turn]="isOwnTurn()"
77078
+ [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
77079
+ [class.is-finished]="game().status === 'finished'"
77080
+ aria-live="polite"
77081
+ >
77082
+ <div>
77083
+ <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
77084
+ <strong>{{ turnBannerTitle() }}</strong>
77085
+ <p>{{ turnBannerBody() }}</p>
76914
77086
  </div>
76915
- }
76916
- </section>
76917
-
76918
- <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
76919
- <div class="christianity-hand-meter christianity-hand-meter--opponent">
76920
- <span>{{ t('christianityHand') }}</span>
76921
- <strong>{{ opponentHandCount() }}</strong>
76922
- </div>
76923
- <section class="christianity-hand-fan christianity-hand-fan--opponent" data-testid="christianity-opponent-hand" [attr.aria-label]="t('christianityOpponentHand')">
76924
- @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
77087
+ <div class="christianity-turn-actions">
76925
77088
  <button
76926
77089
  type="button"
76927
- class="christianity-hidden-card-button christianity-render-card-button"
77090
+ class="christianity-end-turn"
77091
+ data-testid="christianity-end-turn"
77092
+ [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
77093
+ (click)="endTurn()"
77094
+ >
77095
+ {{ t('christianityEndTurn') }}
77096
+ </button>
77097
+ <button
77098
+ type="button"
77099
+ class="christianity-options-button"
77100
+ data-testid="christianity-options-button"
76928
77101
  valLightRipple
76929
- [style.transform]="fanTransform($index, opponentHandCount(), true)"
76930
- [attr.aria-label]="t('christianityHiddenCard')"
77102
+ (click)="toggleOptions()"
76931
77103
  >
76932
- <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
77104
+ {{ t('christianityOptions') }}
76933
77105
  </button>
77106
+ </div>
77107
+ @if (showOptions()) {
77108
+ <div class="christianity-options-menu" data-testid="christianity-options-menu">
77109
+ <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
77110
+ <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">
77111
+ {{ t('christianityResign') }}
77112
+ </button>
77113
+ <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
77114
+ </div>
76934
77115
  }
76935
77116
  </section>
76936
- </section>
76937
77117
 
76938
- <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
76939
- @if (tutorialPhase() !== 'in_progress' && tutorialPhase() !== 'finished') {
77118
+ <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
77119
+ <div class="christianity-hand-meter christianity-hand-meter--opponent">
77120
+ <span>{{ t('christianityHand') }}</span>
77121
+ <strong>{{ opponentHandCount() }}</strong>
77122
+ </div>
76940
77123
  <section
76941
- class="christianity-setup-panel"
76942
- [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
76943
- [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
76944
- data-testid="christianity-setup-panel"
76945
- aria-live="polite"
77124
+ class="christianity-hand-fan christianity-hand-fan--opponent"
77125
+ data-testid="christianity-opponent-hand"
77126
+ [attr.aria-label]="t('christianityOpponentHand')"
76946
77127
  >
76947
- @if (tutorialPhase() === 'coin') {
76948
- <div
76949
- class="christianity-setup-coin"
76950
- [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
76951
- aria-hidden="true"
76952
- >
76953
- <span></span>
76954
- <span></span>
76955
- </div>
76956
- }
76957
- <div>
76958
- <span>{{ t('christianityMatchSetup') }}</span>
76959
- <h2>{{ setupTitle() }}</h2>
76960
- <p>{{ setupBody() }}</p>
76961
- </div>
76962
- @if (tutorialPhase() === 'coin') {
76963
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
76964
- {{ t('christianityContinue') }}
76965
- </button>
76966
- }
76967
- @if (tutorialPhase() === 'leader_pick') {
76968
- @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
76969
- <div class="christianity-leader-choice-meter" aria-live="polite">
76970
- <span>{{ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString()) }}</span>
76971
- <i [style.transform]="'scaleX(' + (leaderChoiceSeconds() / leaderChoiceTotalSeconds) + ')'"></i>
76972
- </div>
76973
- <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
76974
- @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
76975
- @let leader = cardDefinition(leaderId);
76976
- <article class="christianity-leader-choice-option" [class.is-selected]="selectedTutorialLeaderId() === leaderId">
76977
- <button
76978
- type="button"
76979
- class="christianity-leader-choice-card christianity-render-card-button"
76980
- valLightRipple
76981
- [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
76982
- (click)="selectTutorialLeader(leaderId)"
76983
- >
76984
- <val-christianity-card [card]="definitionToRenderCard(leader)" variant="baby"></val-christianity-card>
76985
- </button>
76986
- <div>
76987
- <span>{{ selectedTutorialLeaderId() === leaderId ? t('christianityLeaderSelected') : t('christianitySelectLeader') }}</span>
76988
- <button type="button" class="christianity-leader-choice-detail" valLightRipple (click)="inspectLeaderChoice(leaderId)">
76989
- {{ t('christianityViewDetails') }}
76990
- </button>
76991
- </div>
76992
- </article>
76993
- }
76994
- </div>
76995
- <button type="button" class="christianity-primary-action" data-testid="christianity-confirm-leader" valLightRipple (click)="confirmTutorialLeader()">
76996
- {{ t('christianityChooseThisLeader') }}
76997
- </button>
76998
- } @else {
76999
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77000
- {{ t('christianityContinue') }}
77001
- </button>
77002
- }
77003
- }
77004
- </section>
77005
- }
77006
- @if (game().status === 'finished') {
77007
- <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
77008
- <span>{{ t('christianityGameOver') }}</span>
77009
- <h2>{{ resultTitle() }}</h2>
77010
- <p>{{ resultBody() }}</p>
77011
- <div>
77012
- <button type="button" class="christianity-primary-action" data-testid="christianity-play-again" valLightRipple (click)="restartTutorial()">
77013
- {{ t('christianityPlayAgain') }}
77014
- </button>
77015
- <button type="button" class="christianity-secondary-action" valLightRipple (click)="returnToChristianityHome()">
77016
- {{ t('christianityBackToMenu') }}
77017
- </button>
77018
- </div>
77019
- </section>
77020
- }
77021
- <div class="christianity-impact-layer">
77022
- @if (combatTrail(); as trail) {
77023
- <span
77024
- class="christianity-combat-trail"
77025
- aria-hidden="true"
77026
- [class.is-leader-target]="trail.tone === 'leader'"
77027
- [class.is-character-target]="trail.tone === 'character'"
77028
- [style.--christianity-combat-trail-x]="trail.fromX"
77029
- [style.--christianity-combat-trail-y]="trail.fromY"
77030
- [style.--christianity-combat-trail-mid-x]="trail.midX"
77031
- [style.--christianity-combat-trail-mid-y]="trail.midY"
77032
- [style.--christianity-combat-trail-length]="trail.length"
77033
- [style.--christianity-combat-trail-angle]="trail.angle"
77034
- >
77035
- <span></span>
77036
- </span>
77037
- }
77038
- @for (impact of impactEvents(); track impact.id) {
77039
- <val-mini-game-score-pop [props]="impactPopProps(impact)" />
77040
- }
77041
- @if (combatCallout(); as callout) {
77042
- <val-mini-game-callout [props]="callout" />
77043
- }
77044
- </div>
77045
- <section class="christianity-player-side christianity-player-side--opponent">
77046
- <section class="christianity-player-rack christianity-player-rack--opponent">
77047
- <div class="christianity-offerings-zone">
77048
- <button type="button" class="christianity-offering-pile" data-testid="christianity-offerings-P2" [class.is-empty-zone]="!topOffering('P2')" valLightRipple (click)="openOfferings('P2')">
77049
- @if (offeringStack('P2').length) {
77050
- <span class="christianity-zone-card-stack" aria-hidden="true">
77051
- @for (offering of offeringStack('P2'); track offering.instanceId) {
77052
- <val-christianity-card
77053
- [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77054
- [variant]="offering.ready ? 'baby' : 'back'"
77055
- [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77056
- ></val-christianity-card>
77057
- }
77058
- </span>
77059
- } @else {
77060
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77061
- }
77062
- <span>{{ t('christianityOfferingReserve') }}</span>
77063
- <strong>{{ readyOfferings(opponent()) }}</strong>
77064
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
77065
- </button>
77066
- </div>
77067
- <div
77068
- class="christianity-attack-drop-target"
77069
- cdkDropList
77070
- id="christianity-opponent-leader-drop"
77071
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77072
- [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
77073
- [cdkDropListSortingDisabled]="true"
77074
- (cdkDropListDropped)="dropOnOpponentLeader($event)"
77075
- >
77128
+ @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
77076
77129
  <button
77077
77130
  type="button"
77078
- class="christianity-leader-card christianity-render-card-button"
77079
- data-testid="christianity-leader-P2"
77131
+ class="christianity-hidden-card-button christianity-render-card-button"
77080
77132
  valLightRipple
77081
- [class.is-target]="isOpponentLeaderTarget()"
77082
- [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
77083
- [class.is-combat-target]="isCombatLeaderTarget('P2')"
77084
- (click)="handleOpponentLeader()"
77133
+ [style.transform]="fanTransform($index, opponentHandCount(), true)"
77134
+ [attr.aria-label]="t('christianityHiddenCard')"
77085
77135
  >
77086
- <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
77136
+ <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
77087
77137
  </button>
77088
- </div>
77089
- <span class="christianity-rack-spacer" aria-hidden="true"></span>
77138
+ }
77090
77139
  </section>
77140
+ </section>
77091
77141
 
77092
- <section class="christianity-battle-zone christianity-battle-zone--opponent" data-testid="christianity-battle-P2" [attr.aria-label]="t('christianityOpponentBattle')">
77093
- @for (slot of battleSlots('P2'); track slot.trackId) {
77094
- @if (slot.card) {
77142
+ <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
77143
+ @if (tutorialPhase() !== 'in_progress' && tutorialPhase() !== 'finished') {
77144
+ <section
77145
+ class="christianity-setup-panel"
77146
+ [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
77147
+ [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
77148
+ data-testid="christianity-setup-panel"
77149
+ aria-live="polite"
77150
+ >
77151
+ @if (tutorialPhase() === 'coin') {
77152
+ <div
77153
+ class="christianity-setup-coin"
77154
+ [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
77155
+ aria-hidden="true"
77156
+ >
77157
+ <span></span>
77158
+ <span></span>
77159
+ </div>
77160
+ }
77161
+ <div>
77162
+ <span>{{ t('christianityMatchSetup') }}</span>
77163
+ <h2>{{ setupTitle() }}</h2>
77164
+ <p>{{ setupBody() }}</p>
77165
+ </div>
77166
+ @if (tutorialPhase() === 'coin') {
77095
77167
  <button
77096
77168
  type="button"
77097
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77098
- [attr.data-testid]="battleCardTestId('P2', slot.card)"
77099
- cdkDropList
77100
- [id]="opponentTargetDropId(slot.card)"
77101
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77102
- [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
77103
- [cdkDropListSortingDisabled]="true"
77169
+ class="christianity-primary-action"
77170
+ data-testid="christianity-continue-setup"
77104
77171
  valLightRipple
77105
- [class.is-target]="isOpponentCharacterTarget(slot.card)"
77106
- [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
77107
- (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
77108
- (click)="handleOpponentCharacter(slot.card)"
77172
+ (click)="continueAfterCoin()"
77109
77173
  >
77110
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77174
+ {{ t('christianityContinue') }}
77111
77175
  </button>
77112
- } @else {
77113
- <span class="christianity-slot"></span>
77114
77176
  }
77115
- }
77116
- </section>
77117
- </section>
77118
-
77119
- <section class="christianity-shared-zone" data-testid="christianity-shared-zone" [attr.aria-label]="t('christianitySharedZone')">
77120
- <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()">
77121
- @if (historyStack().length) {
77122
- <span class="christianity-zone-card-stack" aria-hidden="true">
77123
- @for (cardId of historyStack(); track cardId + $index) {
77124
- <val-christianity-card
77125
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
77126
- variant="baby"
77127
- [style.transform]="zoneStackTransform($index, historyStack().length)"
77128
- ></val-christianity-card>
77177
+ @if (tutorialPhase() === 'leader_pick') {
77178
+ @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
77179
+ <div class="christianity-leader-choice-meter" aria-live="polite">
77180
+ <span>{{
77181
+ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString())
77182
+ }}</span>
77183
+ <i [style.transform]="'scaleX(' + leaderChoiceSeconds() / leaderChoiceTotalSeconds + ')'"></i>
77184
+ </div>
77185
+ <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
77186
+ @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
77187
+ @let leader = cardDefinition(leaderId);
77188
+ <article
77189
+ class="christianity-leader-choice-option"
77190
+ [class.is-selected]="selectedTutorialLeaderId() === leaderId"
77191
+ >
77192
+ <button
77193
+ type="button"
77194
+ class="christianity-leader-choice-card christianity-render-card-button"
77195
+ valLightRipple
77196
+ [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
77197
+ (click)="selectTutorialLeader(leaderId)"
77198
+ >
77199
+ <val-christianity-card
77200
+ [card]="definitionToRenderCard(leader)"
77201
+ variant="baby"
77202
+ ></val-christianity-card>
77203
+ </button>
77204
+ <div>
77205
+ <span>{{
77206
+ selectedTutorialLeaderId() === leaderId
77207
+ ? t('christianityLeaderSelected')
77208
+ : t('christianitySelectLeader')
77209
+ }}</span>
77210
+ <button
77211
+ type="button"
77212
+ class="christianity-leader-choice-detail"
77213
+ valLightRipple
77214
+ (click)="inspectLeaderChoice(leaderId)"
77215
+ >
77216
+ {{ t('christianityViewDetails') }}
77217
+ </button>
77218
+ </div>
77219
+ </article>
77220
+ }
77221
+ </div>
77222
+ <button
77223
+ type="button"
77224
+ class="christianity-primary-action"
77225
+ data-testid="christianity-confirm-leader"
77226
+ valLightRipple
77227
+ (click)="confirmTutorialLeader()"
77228
+ >
77229
+ {{ t('christianityChooseThisLeader') }}
77230
+ </button>
77231
+ } @else {
77232
+ <button
77233
+ type="button"
77234
+ class="christianity-primary-action"
77235
+ data-testid="christianity-continue-setup"
77236
+ valLightRipple
77237
+ (click)="continueAfterCoin()"
77238
+ >
77239
+ {{ t('christianityContinue') }}
77240
+ </button>
77129
77241
  }
77130
- </span>
77131
- } @else {
77132
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77133
- }
77134
- <span>{{ t('christianityHistory') }}</span>
77135
- <strong>{{ game().historyZone.length }}</strong>
77136
- </button>
77137
- <button type="button" class="christianity-zone-pile christianity-zone-pile--deck" data-testid="christianity-deck-zone" valLightRipple aria-live="polite" (click)="inspectDeck()">
77138
- <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
77139
- <i></i>
77140
- </span>
77141
- <span>{{ t('christianityDeck') }}</span>
77142
- <strong>{{ game().sharedDeck.length }}</strong>
77143
- </button>
77144
- <button
77145
- type="button"
77146
- class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
77147
- data-testid="christianity-place-zone"
77148
- cdkDropList
77149
- id="christianity-place-drop"
77150
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77151
- [cdkDropListEnterPredicate]="canDropOnPlace"
77152
- [cdkDropListSortingDisabled]="true"
77153
- [class.is-empty-zone]="!activePlaceCard()"
77154
- [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
77155
- valLightRipple
77156
- (cdkDropListDropped)="dropOnPlace($event)"
77157
- (click)="playSelectedPlaceOrInspect()"
77158
- >
77159
- @if (placeStack().length) {
77160
- @if (activePlaceCard(); as placeCard) {
77161
- <div class="christianity-place-backdrop" [style.background-image]="placeBackdropImage(placeCard)" aria-hidden="true"></div>
77162
77242
  }
77163
- <span class="christianity-zone-card-stack" aria-hidden="true">
77164
- @for (cardId of placeStack(); track cardId + $index) {
77165
- <val-christianity-card
77166
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
77167
- variant="baby"
77168
- [style.transform]="zoneStackTransform($index, placeStack().length)"
77169
- ></val-christianity-card>
77170
- }
77171
- </span>
77172
- } @else {
77173
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77174
- }
77175
- <span>{{ t('christianityPlace') }}</span>
77176
- <strong>{{ game().placeZone.length }}</strong>
77177
- @if (canPlaceSelectedPlace() || isDraggingPlace()) {
77178
- <em>{{ t('christianityPlayHere') }}</em>
77179
- }
77180
- </button>
77181
- </section>
77182
-
77183
- <section class="christianity-player-side">
77184
- <section
77185
- class="christianity-battle-zone"
77186
- data-testid="christianity-battle-P1"
77187
- cdkDropList
77188
- id="christianity-player-battle-source"
77189
- [cdkDropListConnectedTo]="attackTargetDropIds()"
77190
- [cdkDropListSortingDisabled]="true"
77191
- [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77192
- [attr.aria-label]="t('christianityYourBattle')"
77193
- >
77194
- @for (slot of battleSlots('P1'); track slot.trackId) {
77195
- @if (slot.card) {
77243
+ </section>
77244
+ }
77245
+ @if (game().status === 'finished') {
77246
+ <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
77247
+ <span>{{ t('christianityGameOver') }}</span>
77248
+ <h2>{{ resultTitle() }}</h2>
77249
+ <p>{{ resultBody() }}</p>
77250
+ <div>
77196
77251
  <button
77197
77252
  type="button"
77198
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77199
- [attr.data-testid]="battleCardTestId('P1', slot.card)"
77200
- cdkDrag
77201
- [cdkDragData]="slot.card.instanceId"
77202
- [cdkDragDisabled]="!canDragAttacker(slot.card)"
77253
+ class="christianity-primary-action"
77254
+ data-testid="christianity-play-again"
77203
77255
  valLightRipple
77204
- [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
77205
- [class.is-exhausted]="!slot.card.canAttack"
77206
- [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
77207
- [class.is-combat-attacker]="isCombatAttacker(slot.card)"
77208
- (cdkDragStarted)="startDraggingAttacker(slot.card)"
77209
- (cdkDragEnded)="endDraggingAttacker()"
77210
- (click)="selectAttacker(slot.card)"
77256
+ (click)="restartTutorial()"
77211
77257
  >
77212
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77213
- <ng-template cdkDragPreview>
77214
- <div class="christianity-card-drag-preview">
77215
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77216
- </div>
77217
- </ng-template>
77218
- <ng-template cdkDragPlaceholder>
77219
- <div class="christianity-card-drag-placeholder"></div>
77220
- </ng-template>
77258
+ {{ t('christianityPlayAgain') }}
77221
77259
  </button>
77222
- } @else {
77223
77260
  <button
77224
77261
  type="button"
77225
- class="christianity-slot"
77226
- [attr.data-testid]="battleSlotTestId('P1', $index)"
77227
- cdkDropList
77228
- [id]="battleSlotDropId($index)"
77229
- [cdkDropListData]="$index"
77230
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77231
- [cdkDropListEnterPredicate]="canDropOnBattleSlot"
77232
- [cdkDropListSortingDisabled]="true"
77233
- [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77262
+ class="christianity-secondary-action"
77234
77263
  valLightRipple
77235
- (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
77236
- (click)="playSelectedCard($index)"
77264
+ (click)="returnToChristianityHome()"
77237
77265
  >
77238
- @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
77239
- <span>{{ t('christianityPlayHere') }}</span>
77240
- }
77266
+ {{ t('christianityBackToMenu') }}
77241
77267
  </button>
77242
- }
77243
- }
77244
- </section>
77245
-
77246
- <section class="christianity-player-rack">
77247
- <div class="christianity-offerings-zone">
77248
- <div
77249
- class="christianity-offering-drop-target"
77250
- cdkDropList
77251
- id="christianity-offerings-drop"
77252
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77253
- [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
77254
- [cdkDropListEnterPredicate]="canDropOnOfferings"
77255
- [cdkDropListSortingDisabled]="true"
77256
- (cdkDropListDropped)="dropOnOfferings($event)"
77268
+ </div>
77269
+ </section>
77270
+ }
77271
+ <div class="christianity-impact-layer">
77272
+ @if (combatTrail(); as trail) {
77273
+ <span
77274
+ class="christianity-combat-trail"
77275
+ aria-hidden="true"
77276
+ [class.is-leader-target]="trail.tone === 'leader'"
77277
+ [class.is-character-target]="trail.tone === 'character'"
77278
+ [style.--christianity-combat-trail-x]="trail.fromX"
77279
+ [style.--christianity-combat-trail-y]="trail.fromY"
77280
+ [style.--christianity-combat-trail-mid-x]="trail.midX"
77281
+ [style.--christianity-combat-trail-mid-y]="trail.midY"
77282
+ [style.--christianity-combat-trail-length]="trail.length"
77283
+ [style.--christianity-combat-trail-angle]="trail.angle"
77257
77284
  >
77285
+ <span></span>
77286
+ </span>
77287
+ }
77288
+ @for (impact of impactEvents(); track impact.id) {
77289
+ <val-mini-game-score-pop [props]="impactPopProps(impact)" />
77290
+ }
77291
+ @if (combatCallout(); as callout) {
77292
+ <val-mini-game-callout [props]="callout" />
77293
+ }
77294
+ </div>
77295
+ <section class="christianity-player-side christianity-player-side--opponent">
77296
+ <section class="christianity-player-rack christianity-player-rack--opponent">
77297
+ <div class="christianity-offerings-zone">
77258
77298
  <button
77259
77299
  type="button"
77260
77300
  class="christianity-offering-pile"
77261
- data-testid="christianity-offerings-P1"
77262
- [class.is-empty-zone]="!topOffering('P1')"
77301
+ data-testid="christianity-offerings-P2"
77302
+ [class.is-empty-zone]="!topOffering('P2')"
77263
77303
  valLightRipple
77264
- (click)="handleOfferingPile('P1')"
77304
+ (click)="openOfferings('P2')"
77265
77305
  >
77266
- @if (offeringStack('P1').length) {
77306
+ @if (offeringStack('P2').length) {
77267
77307
  <span class="christianity-zone-card-stack" aria-hidden="true">
77268
- @for (offering of offeringStack('P1'); track offering.instanceId) {
77308
+ @for (offering of offeringStack('P2'); track offering.instanceId) {
77269
77309
  <val-christianity-card
77270
77310
  [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77271
77311
  [variant]="offering.ready ? 'baby' : 'back'"
77272
- [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
77312
+ [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77273
77313
  ></val-christianity-card>
77274
77314
  }
77275
77315
  </span>
@@ -77277,89 +77317,358 @@ class ChristianityGameComponent {
77277
77317
  <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77278
77318
  }
77279
77319
  <span>{{ t('christianityOfferingReserve') }}</span>
77280
- <strong>{{ readyOfferings(player()) }}</strong>
77281
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
77320
+ <strong>{{ readyOfferings(opponent()) }}</strong>
77321
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
77282
77322
  </button>
77283
77323
  </div>
77284
- </div>
77285
- <button type="button" class="christianity-leader-card christianity-render-card-button" data-testid="christianity-leader-P1" valLightRipple (click)="inspectLeader('P1')">
77286
- <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
77287
- </button>
77288
- <div class="christianity-player-command">
77289
- <div class="christianity-player-badge">
77290
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
77291
- <strong>{{ player().displayName }}</strong>
77324
+ <div
77325
+ class="christianity-attack-drop-target"
77326
+ cdkDropList
77327
+ id="christianity-opponent-leader-drop"
77328
+ [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77329
+ [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
77330
+ [cdkDropListSortingDisabled]="true"
77331
+ (cdkDropListDropped)="dropOnOpponentLeader($event)"
77332
+ >
77333
+ <button
77334
+ type="button"
77335
+ class="christianity-leader-card christianity-render-card-button"
77336
+ data-testid="christianity-leader-P2"
77337
+ valLightRipple
77338
+ [class.is-target]="isOpponentLeaderTarget()"
77339
+ [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
77340
+ [class.is-combat-target]="isCombatLeaderTarget('P2')"
77341
+ (click)="handleOpponentLeader()"
77342
+ >
77343
+ <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
77344
+ </button>
77292
77345
  </div>
77293
- </div>
77346
+ <span class="christianity-rack-spacer" aria-hidden="true"></span>
77347
+ </section>
77348
+
77349
+ <section
77350
+ class="christianity-battle-zone christianity-battle-zone--opponent"
77351
+ data-testid="christianity-battle-P2"
77352
+ [attr.aria-label]="t('christianityOpponentBattle')"
77353
+ >
77354
+ @for (slot of battleSlots('P2'); track slot.trackId) {
77355
+ @if (slot.card) {
77356
+ <button
77357
+ type="button"
77358
+ class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77359
+ [attr.data-testid]="battleCardTestId('P2', slot.card)"
77360
+ cdkDropList
77361
+ [id]="opponentTargetDropId(slot.card)"
77362
+ [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77363
+ [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
77364
+ [cdkDropListSortingDisabled]="true"
77365
+ valLightRipple
77366
+ [class.is-target]="isOpponentCharacterTarget(slot.card)"
77367
+ [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
77368
+ (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
77369
+ (click)="handleOpponentCharacter(slot.card)"
77370
+ >
77371
+ <val-christianity-card
77372
+ [card]="characterRenderCard(slot.card)"
77373
+ variant="board"
77374
+ ></val-christianity-card>
77375
+ </button>
77376
+ } @else {
77377
+ <span class="christianity-slot"></span>
77378
+ }
77379
+ }
77380
+ </section>
77294
77381
  </section>
77295
- </section>
77296
- </section>
77297
77382
 
77298
- <section class="christianity-hand-shelf">
77299
- <div class="christianity-hand-meter">
77300
- <span>{{ t('christianityHand') }}</span>
77301
- <strong>{{ player().hand?.length ?? 0 }}</strong>
77302
- </div>
77303
- <section
77304
- class="christianity-hand-fan"
77305
- data-testid="christianity-hand-P1"
77306
- cdkDropList
77307
- id="christianity-hand-source"
77308
- [cdkDropListConnectedTo]="dropTargetIds()"
77309
- [attr.aria-label]="t('christianityYourHand')"
77310
- (pointerleave)="clearHoveredHandIndex()"
77311
- >
77312
- @for (cardId of player().hand ?? []; track cardId + $index) {
77313
- @let card = cardDefinition(cardId);
77314
- <div
77315
- class="christianity-hand-card-wrap"
77316
- [class.is-selected]="selectedHandCard() === cardId"
77317
- [class.is-dragging]="draggingCardId() === cardId"
77318
- [style.z-index]="handCardZIndex($index)"
77319
- [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
77320
- (pointerenter)="setHoveredHandIndex($index)"
77321
- (pointerdown)="setHoveredHandIndex($index)"
77322
- (pointerup)="releaseHoveredHand($event)"
77383
+ <section
77384
+ class="christianity-shared-zone"
77385
+ data-testid="christianity-shared-zone"
77386
+ [attr.aria-label]="t('christianitySharedZone')"
77387
+ >
77388
+ <button
77389
+ type="button"
77390
+ class="christianity-zone-pile christianity-zone-pile--history"
77391
+ data-testid="christianity-history-zone"
77392
+ [class.is-empty-zone]="!topHistoryCard()"
77393
+ [class.has-new-card]="historyPulse()"
77394
+ valLightRipple
77395
+ (click)="openHistory()"
77396
+ >
77397
+ @if (historyStack().length) {
77398
+ <span class="christianity-zone-card-stack" aria-hidden="true">
77399
+ @for (cardId of historyStack(); track cardId + $index) {
77400
+ <val-christianity-card
77401
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
77402
+ variant="baby"
77403
+ [style.transform]="zoneStackTransform($index, historyStack().length)"
77404
+ ></val-christianity-card>
77405
+ }
77406
+ </span>
77407
+ } @else {
77408
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77409
+ }
77410
+ <span>{{ t('christianityHistory') }}</span>
77411
+ <strong>{{ game().historyZone.length }}</strong>
77412
+ </button>
77413
+ <button
77414
+ type="button"
77415
+ class="christianity-zone-pile christianity-zone-pile--deck"
77416
+ data-testid="christianity-deck-zone"
77417
+ valLightRipple
77418
+ aria-live="polite"
77419
+ (click)="inspectDeck()"
77420
+ >
77421
+ <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
77422
+ <i></i>
77423
+ </span>
77424
+ <span>{{ t('christianityDeck') }}</span>
77425
+ <strong>{{ game().sharedDeck.length }}</strong>
77426
+ </button>
77427
+ <button
77428
+ type="button"
77429
+ class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
77430
+ data-testid="christianity-place-zone"
77431
+ cdkDropList
77432
+ id="christianity-place-drop"
77433
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
77434
+ [cdkDropListEnterPredicate]="canDropOnPlace"
77435
+ [cdkDropListSortingDisabled]="true"
77436
+ [class.is-empty-zone]="!activePlaceCard()"
77437
+ [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
77438
+ valLightRipple
77439
+ (cdkDropListDropped)="dropOnPlace($event)"
77440
+ (click)="playSelectedPlaceOrInspect()"
77441
+ >
77442
+ @if (placeStack().length) {
77443
+ @if (activePlaceCard(); as placeCard) {
77444
+ <div
77445
+ class="christianity-place-backdrop"
77446
+ [style.background-image]="placeBackdropImage(placeCard)"
77447
+ aria-hidden="true"
77448
+ ></div>
77449
+ }
77450
+ <span class="christianity-zone-card-stack" aria-hidden="true">
77451
+ @for (cardId of placeStack(); track cardId + $index) {
77452
+ <val-christianity-card
77453
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
77454
+ variant="baby"
77455
+ [style.transform]="zoneStackTransform($index, placeStack().length)"
77456
+ ></val-christianity-card>
77457
+ }
77458
+ </span>
77459
+ } @else {
77460
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77461
+ }
77462
+ <span>{{ t('christianityPlace') }}</span>
77463
+ <strong>{{ game().placeZone.length }}</strong>
77464
+ @if (canPlaceSelectedPlace() || isDraggingPlace()) {
77465
+ <em>{{ t('christianityPlayHere') }}</em>
77466
+ }
77467
+ </button>
77468
+ </section>
77469
+
77470
+ <section class="christianity-player-side">
77471
+ <section
77472
+ class="christianity-battle-zone"
77473
+ data-testid="christianity-battle-P1"
77474
+ cdkDropList
77475
+ id="christianity-player-battle-source"
77476
+ [cdkDropListConnectedTo]="attackTargetDropIds()"
77477
+ [cdkDropListSortingDisabled]="true"
77478
+ [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77479
+ [attr.aria-label]="t('christianityYourBattle')"
77323
77480
  >
77481
+ @for (slot of battleSlots('P1'); track slot.trackId) {
77482
+ @if (slot.card) {
77483
+ <button
77484
+ type="button"
77485
+ class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77486
+ [attr.data-testid]="battleCardTestId('P1', slot.card)"
77487
+ cdkDrag
77488
+ [cdkDragData]="slot.card.instanceId"
77489
+ [cdkDragDisabled]="!canDragAttacker(slot.card)"
77490
+ valLightRipple
77491
+ [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
77492
+ [class.is-exhausted]="!slot.card.canAttack"
77493
+ [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
77494
+ [class.is-combat-attacker]="isCombatAttacker(slot.card)"
77495
+ (cdkDragStarted)="startDraggingAttacker(slot.card)"
77496
+ (cdkDragEnded)="endDraggingAttacker()"
77497
+ (click)="selectAttacker(slot.card)"
77498
+ >
77499
+ <val-christianity-card
77500
+ [card]="characterRenderCard(slot.card)"
77501
+ variant="board"
77502
+ ></val-christianity-card>
77503
+ <ng-template cdkDragPreview>
77504
+ <div class="christianity-card-drag-preview">
77505
+ <val-christianity-card
77506
+ [card]="characterRenderCard(slot.card)"
77507
+ variant="board"
77508
+ ></val-christianity-card>
77509
+ </div>
77510
+ </ng-template>
77511
+ <ng-template cdkDragPlaceholder>
77512
+ <div class="christianity-card-drag-placeholder"></div>
77513
+ </ng-template>
77514
+ </button>
77515
+ } @else {
77516
+ <button
77517
+ type="button"
77518
+ class="christianity-slot"
77519
+ [attr.data-testid]="battleSlotTestId('P1', $index)"
77520
+ cdkDropList
77521
+ [id]="battleSlotDropId($index)"
77522
+ [cdkDropListData]="$index"
77523
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
77524
+ [cdkDropListEnterPredicate]="canDropOnBattleSlot"
77525
+ [cdkDropListSortingDisabled]="true"
77526
+ [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77527
+ valLightRipple
77528
+ (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
77529
+ (click)="playSelectedCard($index)"
77530
+ >
77531
+ @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
77532
+ <span>{{ t('christianityPlayHere') }}</span>
77533
+ }
77534
+ </button>
77535
+ }
77536
+ }
77537
+ </section>
77538
+
77539
+ <section class="christianity-player-rack">
77540
+ <div class="christianity-offerings-zone">
77541
+ <div
77542
+ class="christianity-offering-drop-target"
77543
+ cdkDropList
77544
+ id="christianity-offerings-drop"
77545
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
77546
+ [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
77547
+ [cdkDropListEnterPredicate]="canDropOnOfferings"
77548
+ [cdkDropListSortingDisabled]="true"
77549
+ (cdkDropListDropped)="dropOnOfferings($event)"
77550
+ >
77551
+ <button
77552
+ type="button"
77553
+ class="christianity-offering-pile"
77554
+ data-testid="christianity-offerings-P1"
77555
+ [class.is-empty-zone]="!topOffering('P1')"
77556
+ valLightRipple
77557
+ (click)="handleOfferingPile('P1')"
77558
+ >
77559
+ @if (offeringStack('P1').length) {
77560
+ <span class="christianity-zone-card-stack" aria-hidden="true">
77561
+ @for (offering of offeringStack('P1'); track offering.instanceId) {
77562
+ <val-christianity-card
77563
+ [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77564
+ [variant]="offering.ready ? 'baby' : 'back'"
77565
+ [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
77566
+ ></val-christianity-card>
77567
+ }
77568
+ </span>
77569
+ } @else {
77570
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77571
+ }
77572
+ <span>{{ t('christianityOfferingReserve') }}</span>
77573
+ <strong>{{ readyOfferings(player()) }}</strong>
77574
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
77575
+ </button>
77576
+ </div>
77577
+ </div>
77324
77578
  <button
77325
77579
  type="button"
77326
- class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
77327
- [attr.data-testid]="handCardTestId(cardId, $index)"
77328
- cdkDrag
77580
+ class="christianity-leader-card christianity-render-card-button"
77581
+ data-testid="christianity-leader-P1"
77329
77582
  valLightRipple
77330
- [cdkDragData]="cardId"
77331
- [cdkDragDisabled]="!canPlay(card)"
77332
- [attr.aria-label]="handCardActionLabel(card)"
77333
- [class.is-selected]="selectedHandCard() === cardId"
77334
- [class.is-disabled]="!canPlay(card)"
77335
- (cdkDragStarted)="startDraggingCard(cardId)"
77336
- (cdkDragEnded)="endDraggingCard()"
77337
- (click)="selectHandCard(cardId)"
77583
+ (click)="inspectLeader('P1')"
77338
77584
  >
77339
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77340
- <ng-template cdkDragPreview>
77341
- <div class="christianity-card-drag-preview">
77342
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77343
- </div>
77344
- </ng-template>
77345
- <ng-template cdkDragPlaceholder>
77346
- <div class="christianity-card-drag-placeholder"></div>
77347
- </ng-template>
77585
+ <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
77348
77586
  </button>
77349
- <button
77350
- type="button"
77351
- class="christianity-card-eye"
77352
- [attr.aria-label]="t('christianityPreviewCard')"
77353
- (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
77354
- ></button>
77355
- </div>
77356
- }
77587
+ <div class="christianity-player-command">
77588
+ <div class="christianity-player-badge">
77589
+ <span class="christianity-avatar" [style.background]="player().avatarColor">{{
77590
+ player().avatar
77591
+ }}</span>
77592
+ <strong>{{ player().displayName }}</strong>
77593
+ </div>
77594
+ </div>
77595
+ </section>
77596
+ </section>
77597
+ </section>
77598
+
77599
+ <section class="christianity-hand-shelf">
77600
+ <div class="christianity-hand-meter">
77601
+ <span>{{ t('christianityHand') }}</span>
77602
+ <strong>{{ player().hand?.length ?? 0 }}</strong>
77603
+ </div>
77604
+ <section
77605
+ class="christianity-hand-fan"
77606
+ data-testid="christianity-hand-P1"
77607
+ cdkDropList
77608
+ id="christianity-hand-source"
77609
+ [cdkDropListConnectedTo]="dropTargetIds()"
77610
+ [attr.aria-label]="t('christianityYourHand')"
77611
+ (pointerleave)="clearHoveredHandIndex()"
77612
+ >
77613
+ @for (cardId of player().hand ?? []; track cardId + $index) {
77614
+ @let card = cardDefinition(cardId);
77615
+ <div
77616
+ class="christianity-hand-card-wrap"
77617
+ [class.is-selected]="selectedHandCard() === cardId"
77618
+ [class.is-dragging]="draggingCardId() === cardId"
77619
+ [style.z-index]="handCardZIndex($index)"
77620
+ [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
77621
+ (pointerenter)="setHoveredHandIndex($index)"
77622
+ (pointerdown)="setHoveredHandIndex($index)"
77623
+ (pointerup)="releaseHoveredHand($event)"
77624
+ >
77625
+ <button
77626
+ type="button"
77627
+ class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
77628
+ [attr.data-testid]="handCardTestId(cardId, $index)"
77629
+ cdkDrag
77630
+ valLightRipple
77631
+ [cdkDragData]="cardId"
77632
+ [cdkDragDisabled]="!canPlay(card)"
77633
+ [attr.aria-label]="handCardActionLabel(card)"
77634
+ [class.is-selected]="selectedHandCard() === cardId"
77635
+ [class.is-disabled]="!canPlay(card)"
77636
+ (cdkDragStarted)="startDraggingCard(cardId)"
77637
+ (cdkDragEnded)="endDraggingCard()"
77638
+ (click)="selectHandCard(cardId)"
77639
+ >
77640
+ <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77641
+ <ng-template cdkDragPreview>
77642
+ <div class="christianity-card-drag-preview">
77643
+ <val-christianity-card
77644
+ [card]="definitionToRenderCard(card)"
77645
+ variant="baby"
77646
+ ></val-christianity-card>
77647
+ </div>
77648
+ </ng-template>
77649
+ <ng-template cdkDragPlaceholder>
77650
+ <div class="christianity-card-drag-placeholder"></div>
77651
+ </ng-template>
77652
+ </button>
77653
+ <button
77654
+ type="button"
77655
+ class="christianity-card-eye"
77656
+ [attr.aria-label]="t('christianityPreviewCard')"
77657
+ (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
77658
+ ></button>
77659
+ </div>
77660
+ }
77661
+ </section>
77357
77662
  </section>
77358
- </section>
77359
77663
  }
77360
77664
 
77361
77665
  @if (selectedPreview()) {
77362
- <button type="button" class="christianity-preview-backdrop" [attr.aria-label]="t('christianityClosePreview')" (click)="closePreview()"></button>
77666
+ <button
77667
+ type="button"
77668
+ class="christianity-preview-backdrop"
77669
+ [attr.aria-label]="t('christianityClosePreview')"
77670
+ (click)="closePreview()"
77671
+ ></button>
77363
77672
  <aside
77364
77673
  class="christianity-floating-card-shell"
77365
77674
  [class.is-history-preview]="canNavigatePreview()"
@@ -77399,7 +77708,11 @@ class ChristianityGameComponent {
77399
77708
  </button>
77400
77709
  }
77401
77710
  @if (selectedPreviewNeighborRenderCard(-1); as previousCard) {
77402
- <val-christianity-card class="christianity-preview-ghost christianity-preview-ghost--previous" [card]="previousCard" variant="full"></val-christianity-card>
77711
+ <val-christianity-card
77712
+ class="christianity-preview-ghost christianity-preview-ghost--previous"
77713
+ [card]="previousCard"
77714
+ variant="full"
77715
+ ></val-christianity-card>
77403
77716
  }
77404
77717
  <div
77405
77718
  class="christianity-floating-card"
@@ -77418,7 +77731,11 @@ class ChristianityGameComponent {
77418
77731
  }
77419
77732
  </div>
77420
77733
  @if (selectedPreviewNeighborRenderCard(1); as nextCard) {
77421
- <val-christianity-card class="christianity-preview-ghost christianity-preview-ghost--next" [card]="nextCard" variant="full"></val-christianity-card>
77734
+ <val-christianity-card
77735
+ class="christianity-preview-ghost christianity-preview-ghost--next"
77736
+ [card]="nextCard"
77737
+ variant="full"
77738
+ ></val-christianity-card>
77422
77739
  }
77423
77740
  </div>
77424
77741
  <div class="christianity-preview-side">
@@ -77502,7 +77819,12 @@ class ChristianityGameComponent {
77502
77819
  }
77503
77820
 
77504
77821
  @if (selectedInfo(); as info) {
77505
- <button type="button" class="christianity-info-backdrop" [attr.aria-label]="t('close')" (click)="closeInfo()"></button>
77822
+ <button
77823
+ type="button"
77824
+ class="christianity-info-backdrop"
77825
+ [attr.aria-label]="t('close')"
77826
+ (click)="closeInfo()"
77827
+ ></button>
77506
77828
  <aside class="christianity-info-panel" aria-live="polite">
77507
77829
  <button type="button" class="christianity-dialog-close christianity-info-close" (click)="closeInfo()">
77508
77830
  {{ t('close') }}
@@ -77527,14 +77849,24 @@ class ChristianityGameComponent {
77527
77849
  @if (screen() === 'table') {
77528
77850
  <section class="christianity-actions" data-testid="christianity-actions" aria-live="polite">
77529
77851
  <p class="christianity-sr-only" data-testid="christianity-status">{{ statusText() }}</p>
77530
- <button type="button" data-testid="christianity-primary-action" [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()" (click)="playSelectedCard()">
77852
+ <button
77853
+ type="button"
77854
+ data-testid="christianity-primary-action"
77855
+ [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()"
77856
+ (click)="playSelectedCard()"
77857
+ >
77531
77858
  {{ selectedActionLabel() }}
77532
77859
  </button>
77533
77860
  </section>
77534
77861
  }
77535
77862
 
77536
77863
  @if (showHistory()) {
77537
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseHistory')" (click)="closeHistory()"></button>
77864
+ <button
77865
+ type="button"
77866
+ class="christianity-history-backdrop"
77867
+ [attr.aria-label]="t('christianityCloseHistory')"
77868
+ (click)="closeHistory()"
77869
+ ></button>
77538
77870
  <section class="christianity-history-drawer" data-testid="christianity-history-drawer">
77539
77871
  <header>
77540
77872
  <h2>{{ t('christianityHistory') }}</h2>
@@ -77549,7 +77881,13 @@ class ChristianityGameComponent {
77549
77881
  <div class="christianity-history-grid" data-testid="christianity-history-grid">
77550
77882
  @for (cardId of game().historyZone; track cardId + $index) {
77551
77883
  @let card = cardDefinition(cardId);
77552
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="historyCardTestId(cardId, $index)" valLightRipple (click)="inspectHistoryCard(cardId)">
77884
+ <button
77885
+ type="button"
77886
+ class="christianity-gallery-card-button christianity-render-card-button"
77887
+ [attr.data-testid]="historyCardTestId(cardId, $index)"
77888
+ valLightRipple
77889
+ (click)="inspectHistoryCard(cardId)"
77890
+ >
77553
77891
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77554
77892
  </button>
77555
77893
  }
@@ -77561,7 +77899,12 @@ class ChristianityGameComponent {
77561
77899
  }
77562
77900
 
77563
77901
  @if (showPlace()) {
77564
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseHistory')" (click)="closePlace()"></button>
77902
+ <button
77903
+ type="button"
77904
+ class="christianity-history-backdrop"
77905
+ [attr.aria-label]="t('christianityCloseHistory')"
77906
+ (click)="closePlace()"
77907
+ ></button>
77565
77908
  <section class="christianity-history-drawer christianity-place-drawer" data-testid="christianity-place-drawer">
77566
77909
  <header>
77567
77910
  <h2>{{ t('christianityPlace') }}</h2>
@@ -77574,8 +77917,17 @@ class ChristianityGameComponent {
77574
77917
  </header>
77575
77918
  <div class="christianity-history-grid" data-testid="christianity-place-grid">
77576
77919
  @for (cardId of game().placeZone; track cardId + $index) {
77577
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="'christianity-place-card-' + $index" valLightRipple (click)="inspectPlaceCard(cardId)">
77578
- <val-christianity-card [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })" variant="baby"></val-christianity-card>
77920
+ <button
77921
+ type="button"
77922
+ class="christianity-gallery-card-button christianity-render-card-button"
77923
+ [attr.data-testid]="'christianity-place-card-' + $index"
77924
+ valLightRipple
77925
+ (click)="inspectPlaceCard(cardId)"
77926
+ >
77927
+ <val-christianity-card
77928
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
77929
+ variant="baby"
77930
+ ></val-christianity-card>
77579
77931
  </button>
77580
77932
  }
77581
77933
  </div>
@@ -77583,8 +77935,16 @@ class ChristianityGameComponent {
77583
77935
  }
77584
77936
 
77585
77937
  @if (showGallery()) {
77586
- <button type="button" class="christianity-history-backdrop christianity-gallery-backdrop" [attr.aria-label]="t('christianityCloseGallery')" (click)="closeGallery()"></button>
77587
- <section class="christianity-history-drawer christianity-gallery-drawer" data-testid="christianity-gallery-drawer">
77938
+ <button
77939
+ type="button"
77940
+ class="christianity-history-backdrop christianity-gallery-backdrop"
77941
+ [attr.aria-label]="t('christianityCloseGallery')"
77942
+ (click)="closeGallery()"
77943
+ ></button>
77944
+ <section
77945
+ class="christianity-history-drawer christianity-gallery-drawer"
77946
+ data-testid="christianity-gallery-drawer"
77947
+ >
77588
77948
  <header>
77589
77949
  <div>
77590
77950
  <h2>{{ t('christianityGallery') }}</h2>
@@ -77600,7 +77960,12 @@ class ChristianityGameComponent {
77600
77960
  <div class="christianity-gallery-controls">
77601
77961
  <label>
77602
77962
  <span>{{ t('christianityGallerySearch') }}</span>
77603
- <input type="search" [value]="gallerySearch()" (input)="setGallerySearch($event)" [placeholder]="t('christianityGallerySearchPlaceholder')" />
77963
+ <input
77964
+ type="search"
77965
+ [value]="gallerySearch()"
77966
+ (input)="setGallerySearch($event)"
77967
+ [placeholder]="t('christianityGallerySearchPlaceholder')"
77968
+ />
77604
77969
  </label>
77605
77970
  <label>
77606
77971
  <span>{{ t('christianityGalleryFilter') }}</span>
@@ -77619,7 +77984,12 @@ class ChristianityGameComponent {
77619
77984
  </div>
77620
77985
  <div class="christianity-gallery-grid">
77621
77986
  @for (card of galleryCards(); track card.id) {
77622
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" valLightRipple (click)="inspectGalleryCard(card.id)">
77987
+ <button
77988
+ type="button"
77989
+ class="christianity-gallery-card-button christianity-render-card-button"
77990
+ valLightRipple
77991
+ (click)="inspectGalleryCard(card.id)"
77992
+ >
77623
77993
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77624
77994
  </button>
77625
77995
  } @empty {
@@ -77631,12 +78001,23 @@ class ChristianityGameComponent {
77631
78001
 
77632
78002
  @if (offeringsSeat()) {
77633
78003
  @let offeringPlayer = game().players[offeringsSeat()!];
77634
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseOfferings')" (click)="closeOfferings()"></button>
77635
- <section class="christianity-history-drawer christianity-offerings-drawer" [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)">
78004
+ <button
78005
+ type="button"
78006
+ class="christianity-history-backdrop"
78007
+ [attr.aria-label]="t('christianityCloseOfferings')"
78008
+ (click)="closeOfferings()"
78009
+ ></button>
78010
+ <section
78011
+ class="christianity-history-drawer christianity-offerings-drawer"
78012
+ [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)"
78013
+ >
77636
78014
  <header>
77637
78015
  <h2>{{ t('christianityOfferings') }}</h2>
77638
78016
  <div class="christianity-dialog-actions">
77639
- <span>{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} · {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span>
78017
+ <span
78018
+ >{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} ·
78019
+ {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span
78020
+ >
77640
78021
  <button type="button" class="christianity-dialog-close" (click)="closeOfferings()">
77641
78022
  {{ t('close') }}
77642
78023
  </button>
@@ -77646,14 +78027,25 @@ class ChristianityGameComponent {
77646
78027
  <span class="christianity-rule-note-icon" aria-hidden="true">i</span>
77647
78028
  <p>{{ t('christianityOfferingsRuleBody') }}</p>
77648
78029
  </div>
77649
- <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
77650
- @for (offering of offeringPlayer.offerings; track offering.instanceId) {
77651
- <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
77652
- <button type="button" class="christianity-offering-card christianity-render-card-button" [attr.data-testid]="offeringCardTestId(offering)" valLightRipple (click)="inspectOffering(offering)">
77653
- <val-christianity-card [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()" [variant]="offering.ready ? 'baby' : 'back'"></val-christianity-card>
78030
+ <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
78031
+ @for (offering of offeringPlayer.offerings; track offering.instanceId) {
78032
+ <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
78033
+ <button
78034
+ type="button"
78035
+ class="christianity-offering-card christianity-render-card-button"
78036
+ [attr.data-testid]="offeringCardTestId(offering)"
78037
+ valLightRipple
78038
+ (click)="inspectOffering(offering)"
78039
+ >
78040
+ <val-christianity-card
78041
+ [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
78042
+ [variant]="offering.ready ? 'baby' : 'back'"
78043
+ ></val-christianity-card>
77654
78044
  </button>
77655
78045
  <div>
77656
- <strong>{{ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted') }}</strong>
78046
+ <strong>{{
78047
+ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted')
78048
+ }}</strong>
77657
78049
  <p>{{ offeringAbilityText(offering) }}</p>
77658
78050
  @if (offering.abilityName) {
77659
78051
  <button
@@ -77762,418 +78154,276 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
77762
78154
  </section>
77763
78155
  </section>
77764
78156
  } @else {
77765
- <section class="christianity-match-strip" data-testid="christianity-match-strip" [attr.aria-label]="t('christianityMatchScore')">
77766
- <article class="christianity-match-player">
77767
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
77768
- <div>
77769
- <strong>{{ player().displayName }}</strong>
77770
- <small>{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small>
77771
- </div>
77772
- </article>
77773
- <div class="christianity-scoreboard">
77774
- <span>{{ t('christianityMatch') }}</span>
77775
- <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
77776
- </div>
77777
- <article class="christianity-match-player christianity-match-player--opponent">
77778
- <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
77779
- <div>
77780
- <strong>{{ opponent().displayName }}</strong>
77781
- <small>{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{ opponent().leaderMaxHp }}</small>
78157
+ <section
78158
+ class="christianity-match-strip"
78159
+ data-testid="christianity-match-strip"
78160
+ [attr.aria-label]="t('christianityMatchScore')"
78161
+ >
78162
+ <article class="christianity-match-player">
78163
+ <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
78164
+ <div>
78165
+ <strong>{{ player().displayName }}</strong>
78166
+ <small
78167
+ >{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small
78168
+ >
78169
+ </div>
78170
+ </article>
78171
+ <div class="christianity-scoreboard">
78172
+ <span>{{ t('christianityMatch') }}</span>
78173
+ <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
77782
78174
  </div>
77783
- </article>
77784
- </section>
78175
+ <article class="christianity-match-player christianity-match-player--opponent">
78176
+ <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
78177
+ <div>
78178
+ <strong>{{ opponent().displayName }}</strong>
78179
+ <small
78180
+ >{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{
78181
+ opponent().leaderMaxHp
78182
+ }}</small
78183
+ >
78184
+ </div>
78185
+ </article>
78186
+ </section>
77785
78187
 
77786
- <section
77787
- class="christianity-turn-banner"
77788
- data-testid="christianity-turn-banner"
77789
- [class.is-own-turn]="isOwnTurn()"
77790
- [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
77791
- [class.is-finished]="game().status === 'finished'"
77792
- aria-live="polite"
77793
- >
77794
- <div>
77795
- <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
77796
- <strong>{{ turnBannerTitle() }}</strong>
77797
- <p>{{ turnBannerBody() }}</p>
77798
- </div>
77799
- <div class="christianity-turn-actions">
77800
- <button
77801
- type="button"
77802
- class="christianity-end-turn"
77803
- data-testid="christianity-end-turn"
77804
- [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
77805
- (click)="endTurn()"
77806
- >
77807
- {{ t('christianityEndTurn') }}
77808
- </button>
77809
- <button type="button" class="christianity-options-button" data-testid="christianity-options-button" valLightRipple (click)="toggleOptions()">
77810
- {{ t('christianityOptions') }}
77811
- </button>
77812
- </div>
77813
- @if (showOptions()) {
77814
- <div class="christianity-options-menu" data-testid="christianity-options-menu">
77815
- <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
77816
- <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">{{ t('christianityResign') }}</button>
77817
- <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
78188
+ <section
78189
+ class="christianity-turn-banner"
78190
+ data-testid="christianity-turn-banner"
78191
+ [class.is-own-turn]="isOwnTurn()"
78192
+ [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
78193
+ [class.is-finished]="game().status === 'finished'"
78194
+ aria-live="polite"
78195
+ >
78196
+ <div>
78197
+ <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
78198
+ <strong>{{ turnBannerTitle() }}</strong>
78199
+ <p>{{ turnBannerBody() }}</p>
77818
78200
  </div>
77819
- }
77820
- </section>
77821
-
77822
- <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
77823
- <div class="christianity-hand-meter christianity-hand-meter--opponent">
77824
- <span>{{ t('christianityHand') }}</span>
77825
- <strong>{{ opponentHandCount() }}</strong>
77826
- </div>
77827
- <section class="christianity-hand-fan christianity-hand-fan--opponent" data-testid="christianity-opponent-hand" [attr.aria-label]="t('christianityOpponentHand')">
77828
- @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
78201
+ <div class="christianity-turn-actions">
77829
78202
  <button
77830
78203
  type="button"
77831
- class="christianity-hidden-card-button christianity-render-card-button"
78204
+ class="christianity-end-turn"
78205
+ data-testid="christianity-end-turn"
78206
+ [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
78207
+ (click)="endTurn()"
78208
+ >
78209
+ {{ t('christianityEndTurn') }}
78210
+ </button>
78211
+ <button
78212
+ type="button"
78213
+ class="christianity-options-button"
78214
+ data-testid="christianity-options-button"
77832
78215
  valLightRipple
77833
- [style.transform]="fanTransform($index, opponentHandCount(), true)"
77834
- [attr.aria-label]="t('christianityHiddenCard')"
78216
+ (click)="toggleOptions()"
77835
78217
  >
77836
- <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
78218
+ {{ t('christianityOptions') }}
77837
78219
  </button>
78220
+ </div>
78221
+ @if (showOptions()) {
78222
+ <div class="christianity-options-menu" data-testid="christianity-options-menu">
78223
+ <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
78224
+ <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">
78225
+ {{ t('christianityResign') }}
78226
+ </button>
78227
+ <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
78228
+ </div>
77838
78229
  }
77839
78230
  </section>
77840
- </section>
77841
78231
 
77842
- <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
77843
- @if (tutorialPhase() !== 'in_progress' && tutorialPhase() !== 'finished') {
78232
+ <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
78233
+ <div class="christianity-hand-meter christianity-hand-meter--opponent">
78234
+ <span>{{ t('christianityHand') }}</span>
78235
+ <strong>{{ opponentHandCount() }}</strong>
78236
+ </div>
77844
78237
  <section
77845
- class="christianity-setup-panel"
77846
- [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
77847
- [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
77848
- data-testid="christianity-setup-panel"
77849
- aria-live="polite"
78238
+ class="christianity-hand-fan christianity-hand-fan--opponent"
78239
+ data-testid="christianity-opponent-hand"
78240
+ [attr.aria-label]="t('christianityOpponentHand')"
77850
78241
  >
77851
- @if (tutorialPhase() === 'coin') {
77852
- <div
77853
- class="christianity-setup-coin"
77854
- [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
77855
- aria-hidden="true"
77856
- >
77857
- <span></span>
77858
- <span></span>
77859
- </div>
77860
- }
77861
- <div>
77862
- <span>{{ t('christianityMatchSetup') }}</span>
77863
- <h2>{{ setupTitle() }}</h2>
77864
- <p>{{ setupBody() }}</p>
77865
- </div>
77866
- @if (tutorialPhase() === 'coin') {
77867
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77868
- {{ t('christianityContinue') }}
77869
- </button>
77870
- }
77871
- @if (tutorialPhase() === 'leader_pick') {
77872
- @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
77873
- <div class="christianity-leader-choice-meter" aria-live="polite">
77874
- <span>{{ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString()) }}</span>
77875
- <i [style.transform]="'scaleX(' + (leaderChoiceSeconds() / leaderChoiceTotalSeconds) + ')'"></i>
77876
- </div>
77877
- <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
77878
- @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
77879
- @let leader = cardDefinition(leaderId);
77880
- <article class="christianity-leader-choice-option" [class.is-selected]="selectedTutorialLeaderId() === leaderId">
77881
- <button
77882
- type="button"
77883
- class="christianity-leader-choice-card christianity-render-card-button"
77884
- valLightRipple
77885
- [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
77886
- (click)="selectTutorialLeader(leaderId)"
77887
- >
77888
- <val-christianity-card [card]="definitionToRenderCard(leader)" variant="baby"></val-christianity-card>
77889
- </button>
77890
- <div>
77891
- <span>{{ selectedTutorialLeaderId() === leaderId ? t('christianityLeaderSelected') : t('christianitySelectLeader') }}</span>
77892
- <button type="button" class="christianity-leader-choice-detail" valLightRipple (click)="inspectLeaderChoice(leaderId)">
77893
- {{ t('christianityViewDetails') }}
77894
- </button>
77895
- </div>
77896
- </article>
77897
- }
77898
- </div>
77899
- <button type="button" class="christianity-primary-action" data-testid="christianity-confirm-leader" valLightRipple (click)="confirmTutorialLeader()">
77900
- {{ t('christianityChooseThisLeader') }}
77901
- </button>
77902
- } @else {
77903
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77904
- {{ t('christianityContinue') }}
77905
- </button>
77906
- }
77907
- }
77908
- </section>
77909
- }
77910
- @if (game().status === 'finished') {
77911
- <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
77912
- <span>{{ t('christianityGameOver') }}</span>
77913
- <h2>{{ resultTitle() }}</h2>
77914
- <p>{{ resultBody() }}</p>
77915
- <div>
77916
- <button type="button" class="christianity-primary-action" data-testid="christianity-play-again" valLightRipple (click)="restartTutorial()">
77917
- {{ t('christianityPlayAgain') }}
77918
- </button>
77919
- <button type="button" class="christianity-secondary-action" valLightRipple (click)="returnToChristianityHome()">
77920
- {{ t('christianityBackToMenu') }}
77921
- </button>
77922
- </div>
77923
- </section>
77924
- }
77925
- <div class="christianity-impact-layer">
77926
- @if (combatTrail(); as trail) {
77927
- <span
77928
- class="christianity-combat-trail"
77929
- aria-hidden="true"
77930
- [class.is-leader-target]="trail.tone === 'leader'"
77931
- [class.is-character-target]="trail.tone === 'character'"
77932
- [style.--christianity-combat-trail-x]="trail.fromX"
77933
- [style.--christianity-combat-trail-y]="trail.fromY"
77934
- [style.--christianity-combat-trail-mid-x]="trail.midX"
77935
- [style.--christianity-combat-trail-mid-y]="trail.midY"
77936
- [style.--christianity-combat-trail-length]="trail.length"
77937
- [style.--christianity-combat-trail-angle]="trail.angle"
77938
- >
77939
- <span></span>
77940
- </span>
77941
- }
77942
- @for (impact of impactEvents(); track impact.id) {
77943
- <val-mini-game-score-pop [props]="impactPopProps(impact)" />
77944
- }
77945
- @if (combatCallout(); as callout) {
77946
- <val-mini-game-callout [props]="callout" />
77947
- }
77948
- </div>
77949
- <section class="christianity-player-side christianity-player-side--opponent">
77950
- <section class="christianity-player-rack christianity-player-rack--opponent">
77951
- <div class="christianity-offerings-zone">
77952
- <button type="button" class="christianity-offering-pile" data-testid="christianity-offerings-P2" [class.is-empty-zone]="!topOffering('P2')" valLightRipple (click)="openOfferings('P2')">
77953
- @if (offeringStack('P2').length) {
77954
- <span class="christianity-zone-card-stack" aria-hidden="true">
77955
- @for (offering of offeringStack('P2'); track offering.instanceId) {
77956
- <val-christianity-card
77957
- [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77958
- [variant]="offering.ready ? 'baby' : 'back'"
77959
- [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77960
- ></val-christianity-card>
77961
- }
77962
- </span>
77963
- } @else {
77964
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77965
- }
77966
- <span>{{ t('christianityOfferingReserve') }}</span>
77967
- <strong>{{ readyOfferings(opponent()) }}</strong>
77968
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
77969
- </button>
77970
- </div>
77971
- <div
77972
- class="christianity-attack-drop-target"
77973
- cdkDropList
77974
- id="christianity-opponent-leader-drop"
77975
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77976
- [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
77977
- [cdkDropListSortingDisabled]="true"
77978
- (cdkDropListDropped)="dropOnOpponentLeader($event)"
77979
- >
78242
+ @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
77980
78243
  <button
77981
78244
  type="button"
77982
- class="christianity-leader-card christianity-render-card-button"
77983
- data-testid="christianity-leader-P2"
78245
+ class="christianity-hidden-card-button christianity-render-card-button"
77984
78246
  valLightRipple
77985
- [class.is-target]="isOpponentLeaderTarget()"
77986
- [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
77987
- [class.is-combat-target]="isCombatLeaderTarget('P2')"
77988
- (click)="handleOpponentLeader()"
78247
+ [style.transform]="fanTransform($index, opponentHandCount(), true)"
78248
+ [attr.aria-label]="t('christianityHiddenCard')"
77989
78249
  >
77990
- <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
78250
+ <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
77991
78251
  </button>
77992
- </div>
77993
- <span class="christianity-rack-spacer" aria-hidden="true"></span>
78252
+ }
77994
78253
  </section>
78254
+ </section>
77995
78255
 
77996
- <section class="christianity-battle-zone christianity-battle-zone--opponent" data-testid="christianity-battle-P2" [attr.aria-label]="t('christianityOpponentBattle')">
77997
- @for (slot of battleSlots('P2'); track slot.trackId) {
77998
- @if (slot.card) {
78256
+ <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
78257
+ @if (tutorialPhase() !== 'in_progress' && tutorialPhase() !== 'finished') {
78258
+ <section
78259
+ class="christianity-setup-panel"
78260
+ [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
78261
+ [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
78262
+ data-testid="christianity-setup-panel"
78263
+ aria-live="polite"
78264
+ >
78265
+ @if (tutorialPhase() === 'coin') {
78266
+ <div
78267
+ class="christianity-setup-coin"
78268
+ [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
78269
+ aria-hidden="true"
78270
+ >
78271
+ <span></span>
78272
+ <span></span>
78273
+ </div>
78274
+ }
78275
+ <div>
78276
+ <span>{{ t('christianityMatchSetup') }}</span>
78277
+ <h2>{{ setupTitle() }}</h2>
78278
+ <p>{{ setupBody() }}</p>
78279
+ </div>
78280
+ @if (tutorialPhase() === 'coin') {
77999
78281
  <button
78000
78282
  type="button"
78001
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78002
- [attr.data-testid]="battleCardTestId('P2', slot.card)"
78003
- cdkDropList
78004
- [id]="opponentTargetDropId(slot.card)"
78005
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78006
- [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
78007
- [cdkDropListSortingDisabled]="true"
78283
+ class="christianity-primary-action"
78284
+ data-testid="christianity-continue-setup"
78008
78285
  valLightRipple
78009
- [class.is-target]="isOpponentCharacterTarget(slot.card)"
78010
- [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
78011
- (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
78012
- (click)="handleOpponentCharacter(slot.card)"
78286
+ (click)="continueAfterCoin()"
78013
78287
  >
78014
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78288
+ {{ t('christianityContinue') }}
78015
78289
  </button>
78016
- } @else {
78017
- <span class="christianity-slot"></span>
78018
78290
  }
78019
- }
78020
- </section>
78021
- </section>
78022
-
78023
- <section class="christianity-shared-zone" data-testid="christianity-shared-zone" [attr.aria-label]="t('christianitySharedZone')">
78024
- <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()">
78025
- @if (historyStack().length) {
78026
- <span class="christianity-zone-card-stack" aria-hidden="true">
78027
- @for (cardId of historyStack(); track cardId + $index) {
78028
- <val-christianity-card
78029
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
78030
- variant="baby"
78031
- [style.transform]="zoneStackTransform($index, historyStack().length)"
78032
- ></val-christianity-card>
78291
+ @if (tutorialPhase() === 'leader_pick') {
78292
+ @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
78293
+ <div class="christianity-leader-choice-meter" aria-live="polite">
78294
+ <span>{{
78295
+ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString())
78296
+ }}</span>
78297
+ <i [style.transform]="'scaleX(' + leaderChoiceSeconds() / leaderChoiceTotalSeconds + ')'"></i>
78298
+ </div>
78299
+ <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
78300
+ @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
78301
+ @let leader = cardDefinition(leaderId);
78302
+ <article
78303
+ class="christianity-leader-choice-option"
78304
+ [class.is-selected]="selectedTutorialLeaderId() === leaderId"
78305
+ >
78306
+ <button
78307
+ type="button"
78308
+ class="christianity-leader-choice-card christianity-render-card-button"
78309
+ valLightRipple
78310
+ [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
78311
+ (click)="selectTutorialLeader(leaderId)"
78312
+ >
78313
+ <val-christianity-card
78314
+ [card]="definitionToRenderCard(leader)"
78315
+ variant="baby"
78316
+ ></val-christianity-card>
78317
+ </button>
78318
+ <div>
78319
+ <span>{{
78320
+ selectedTutorialLeaderId() === leaderId
78321
+ ? t('christianityLeaderSelected')
78322
+ : t('christianitySelectLeader')
78323
+ }}</span>
78324
+ <button
78325
+ type="button"
78326
+ class="christianity-leader-choice-detail"
78327
+ valLightRipple
78328
+ (click)="inspectLeaderChoice(leaderId)"
78329
+ >
78330
+ {{ t('christianityViewDetails') }}
78331
+ </button>
78332
+ </div>
78333
+ </article>
78334
+ }
78335
+ </div>
78336
+ <button
78337
+ type="button"
78338
+ class="christianity-primary-action"
78339
+ data-testid="christianity-confirm-leader"
78340
+ valLightRipple
78341
+ (click)="confirmTutorialLeader()"
78342
+ >
78343
+ {{ t('christianityChooseThisLeader') }}
78344
+ </button>
78345
+ } @else {
78346
+ <button
78347
+ type="button"
78348
+ class="christianity-primary-action"
78349
+ data-testid="christianity-continue-setup"
78350
+ valLightRipple
78351
+ (click)="continueAfterCoin()"
78352
+ >
78353
+ {{ t('christianityContinue') }}
78354
+ </button>
78033
78355
  }
78034
- </span>
78035
- } @else {
78036
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78037
- }
78038
- <span>{{ t('christianityHistory') }}</span>
78039
- <strong>{{ game().historyZone.length }}</strong>
78040
- </button>
78041
- <button type="button" class="christianity-zone-pile christianity-zone-pile--deck" data-testid="christianity-deck-zone" valLightRipple aria-live="polite" (click)="inspectDeck()">
78042
- <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
78043
- <i></i>
78044
- </span>
78045
- <span>{{ t('christianityDeck') }}</span>
78046
- <strong>{{ game().sharedDeck.length }}</strong>
78047
- </button>
78048
- <button
78049
- type="button"
78050
- class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
78051
- data-testid="christianity-place-zone"
78052
- cdkDropList
78053
- id="christianity-place-drop"
78054
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78055
- [cdkDropListEnterPredicate]="canDropOnPlace"
78056
- [cdkDropListSortingDisabled]="true"
78057
- [class.is-empty-zone]="!activePlaceCard()"
78058
- [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
78059
- valLightRipple
78060
- (cdkDropListDropped)="dropOnPlace($event)"
78061
- (click)="playSelectedPlaceOrInspect()"
78062
- >
78063
- @if (placeStack().length) {
78064
- @if (activePlaceCard(); as placeCard) {
78065
- <div class="christianity-place-backdrop" [style.background-image]="placeBackdropImage(placeCard)" aria-hidden="true"></div>
78066
78356
  }
78067
- <span class="christianity-zone-card-stack" aria-hidden="true">
78068
- @for (cardId of placeStack(); track cardId + $index) {
78069
- <val-christianity-card
78070
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
78071
- variant="baby"
78072
- [style.transform]="zoneStackTransform($index, placeStack().length)"
78073
- ></val-christianity-card>
78074
- }
78075
- </span>
78076
- } @else {
78077
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78078
- }
78079
- <span>{{ t('christianityPlace') }}</span>
78080
- <strong>{{ game().placeZone.length }}</strong>
78081
- @if (canPlaceSelectedPlace() || isDraggingPlace()) {
78082
- <em>{{ t('christianityPlayHere') }}</em>
78083
- }
78084
- </button>
78085
- </section>
78086
-
78087
- <section class="christianity-player-side">
78088
- <section
78089
- class="christianity-battle-zone"
78090
- data-testid="christianity-battle-P1"
78091
- cdkDropList
78092
- id="christianity-player-battle-source"
78093
- [cdkDropListConnectedTo]="attackTargetDropIds()"
78094
- [cdkDropListSortingDisabled]="true"
78095
- [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78096
- [attr.aria-label]="t('christianityYourBattle')"
78097
- >
78098
- @for (slot of battleSlots('P1'); track slot.trackId) {
78099
- @if (slot.card) {
78357
+ </section>
78358
+ }
78359
+ @if (game().status === 'finished') {
78360
+ <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
78361
+ <span>{{ t('christianityGameOver') }}</span>
78362
+ <h2>{{ resultTitle() }}</h2>
78363
+ <p>{{ resultBody() }}</p>
78364
+ <div>
78100
78365
  <button
78101
78366
  type="button"
78102
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78103
- [attr.data-testid]="battleCardTestId('P1', slot.card)"
78104
- cdkDrag
78105
- [cdkDragData]="slot.card.instanceId"
78106
- [cdkDragDisabled]="!canDragAttacker(slot.card)"
78367
+ class="christianity-primary-action"
78368
+ data-testid="christianity-play-again"
78107
78369
  valLightRipple
78108
- [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
78109
- [class.is-exhausted]="!slot.card.canAttack"
78110
- [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
78111
- [class.is-combat-attacker]="isCombatAttacker(slot.card)"
78112
- (cdkDragStarted)="startDraggingAttacker(slot.card)"
78113
- (cdkDragEnded)="endDraggingAttacker()"
78114
- (click)="selectAttacker(slot.card)"
78370
+ (click)="restartTutorial()"
78115
78371
  >
78116
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78117
- <ng-template cdkDragPreview>
78118
- <div class="christianity-card-drag-preview">
78119
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78120
- </div>
78121
- </ng-template>
78122
- <ng-template cdkDragPlaceholder>
78123
- <div class="christianity-card-drag-placeholder"></div>
78124
- </ng-template>
78372
+ {{ t('christianityPlayAgain') }}
78125
78373
  </button>
78126
- } @else {
78127
78374
  <button
78128
78375
  type="button"
78129
- class="christianity-slot"
78130
- [attr.data-testid]="battleSlotTestId('P1', $index)"
78131
- cdkDropList
78132
- [id]="battleSlotDropId($index)"
78133
- [cdkDropListData]="$index"
78134
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78135
- [cdkDropListEnterPredicate]="canDropOnBattleSlot"
78136
- [cdkDropListSortingDisabled]="true"
78137
- [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78376
+ class="christianity-secondary-action"
78138
78377
  valLightRipple
78139
- (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
78140
- (click)="playSelectedCard($index)"
78378
+ (click)="returnToChristianityHome()"
78141
78379
  >
78142
- @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
78143
- <span>{{ t('christianityPlayHere') }}</span>
78144
- }
78380
+ {{ t('christianityBackToMenu') }}
78145
78381
  </button>
78146
- }
78147
- }
78148
- </section>
78149
-
78150
- <section class="christianity-player-rack">
78151
- <div class="christianity-offerings-zone">
78152
- <div
78153
- class="christianity-offering-drop-target"
78154
- cdkDropList
78155
- id="christianity-offerings-drop"
78156
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78157
- [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
78158
- [cdkDropListEnterPredicate]="canDropOnOfferings"
78159
- [cdkDropListSortingDisabled]="true"
78160
- (cdkDropListDropped)="dropOnOfferings($event)"
78382
+ </div>
78383
+ </section>
78384
+ }
78385
+ <div class="christianity-impact-layer">
78386
+ @if (combatTrail(); as trail) {
78387
+ <span
78388
+ class="christianity-combat-trail"
78389
+ aria-hidden="true"
78390
+ [class.is-leader-target]="trail.tone === 'leader'"
78391
+ [class.is-character-target]="trail.tone === 'character'"
78392
+ [style.--christianity-combat-trail-x]="trail.fromX"
78393
+ [style.--christianity-combat-trail-y]="trail.fromY"
78394
+ [style.--christianity-combat-trail-mid-x]="trail.midX"
78395
+ [style.--christianity-combat-trail-mid-y]="trail.midY"
78396
+ [style.--christianity-combat-trail-length]="trail.length"
78397
+ [style.--christianity-combat-trail-angle]="trail.angle"
78161
78398
  >
78399
+ <span></span>
78400
+ </span>
78401
+ }
78402
+ @for (impact of impactEvents(); track impact.id) {
78403
+ <val-mini-game-score-pop [props]="impactPopProps(impact)" />
78404
+ }
78405
+ @if (combatCallout(); as callout) {
78406
+ <val-mini-game-callout [props]="callout" />
78407
+ }
78408
+ </div>
78409
+ <section class="christianity-player-side christianity-player-side--opponent">
78410
+ <section class="christianity-player-rack christianity-player-rack--opponent">
78411
+ <div class="christianity-offerings-zone">
78162
78412
  <button
78163
78413
  type="button"
78164
78414
  class="christianity-offering-pile"
78165
- data-testid="christianity-offerings-P1"
78166
- [class.is-empty-zone]="!topOffering('P1')"
78415
+ data-testid="christianity-offerings-P2"
78416
+ [class.is-empty-zone]="!topOffering('P2')"
78167
78417
  valLightRipple
78168
- (click)="handleOfferingPile('P1')"
78418
+ (click)="openOfferings('P2')"
78169
78419
  >
78170
- @if (offeringStack('P1').length) {
78420
+ @if (offeringStack('P2').length) {
78171
78421
  <span class="christianity-zone-card-stack" aria-hidden="true">
78172
- @for (offering of offeringStack('P1'); track offering.instanceId) {
78422
+ @for (offering of offeringStack('P2'); track offering.instanceId) {
78173
78423
  <val-christianity-card
78174
78424
  [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
78175
78425
  [variant]="offering.ready ? 'baby' : 'back'"
78176
- [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
78426
+ [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
78177
78427
  ></val-christianity-card>
78178
78428
  }
78179
78429
  </span>
@@ -78181,89 +78431,358 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78181
78431
  <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78182
78432
  }
78183
78433
  <span>{{ t('christianityOfferingReserve') }}</span>
78184
- <strong>{{ readyOfferings(player()) }}</strong>
78185
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
78434
+ <strong>{{ readyOfferings(opponent()) }}</strong>
78435
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
78186
78436
  </button>
78187
78437
  </div>
78188
- </div>
78189
- <button type="button" class="christianity-leader-card christianity-render-card-button" data-testid="christianity-leader-P1" valLightRipple (click)="inspectLeader('P1')">
78190
- <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
78191
- </button>
78192
- <div class="christianity-player-command">
78193
- <div class="christianity-player-badge">
78194
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
78195
- <strong>{{ player().displayName }}</strong>
78438
+ <div
78439
+ class="christianity-attack-drop-target"
78440
+ cdkDropList
78441
+ id="christianity-opponent-leader-drop"
78442
+ [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78443
+ [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
78444
+ [cdkDropListSortingDisabled]="true"
78445
+ (cdkDropListDropped)="dropOnOpponentLeader($event)"
78446
+ >
78447
+ <button
78448
+ type="button"
78449
+ class="christianity-leader-card christianity-render-card-button"
78450
+ data-testid="christianity-leader-P2"
78451
+ valLightRipple
78452
+ [class.is-target]="isOpponentLeaderTarget()"
78453
+ [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
78454
+ [class.is-combat-target]="isCombatLeaderTarget('P2')"
78455
+ (click)="handleOpponentLeader()"
78456
+ >
78457
+ <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
78458
+ </button>
78196
78459
  </div>
78197
- </div>
78460
+ <span class="christianity-rack-spacer" aria-hidden="true"></span>
78461
+ </section>
78462
+
78463
+ <section
78464
+ class="christianity-battle-zone christianity-battle-zone--opponent"
78465
+ data-testid="christianity-battle-P2"
78466
+ [attr.aria-label]="t('christianityOpponentBattle')"
78467
+ >
78468
+ @for (slot of battleSlots('P2'); track slot.trackId) {
78469
+ @if (slot.card) {
78470
+ <button
78471
+ type="button"
78472
+ class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78473
+ [attr.data-testid]="battleCardTestId('P2', slot.card)"
78474
+ cdkDropList
78475
+ [id]="opponentTargetDropId(slot.card)"
78476
+ [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78477
+ [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
78478
+ [cdkDropListSortingDisabled]="true"
78479
+ valLightRipple
78480
+ [class.is-target]="isOpponentCharacterTarget(slot.card)"
78481
+ [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
78482
+ (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
78483
+ (click)="handleOpponentCharacter(slot.card)"
78484
+ >
78485
+ <val-christianity-card
78486
+ [card]="characterRenderCard(slot.card)"
78487
+ variant="board"
78488
+ ></val-christianity-card>
78489
+ </button>
78490
+ } @else {
78491
+ <span class="christianity-slot"></span>
78492
+ }
78493
+ }
78494
+ </section>
78198
78495
  </section>
78199
- </section>
78200
- </section>
78201
78496
 
78202
- <section class="christianity-hand-shelf">
78203
- <div class="christianity-hand-meter">
78204
- <span>{{ t('christianityHand') }}</span>
78205
- <strong>{{ player().hand?.length ?? 0 }}</strong>
78206
- </div>
78207
- <section
78208
- class="christianity-hand-fan"
78209
- data-testid="christianity-hand-P1"
78210
- cdkDropList
78211
- id="christianity-hand-source"
78212
- [cdkDropListConnectedTo]="dropTargetIds()"
78213
- [attr.aria-label]="t('christianityYourHand')"
78214
- (pointerleave)="clearHoveredHandIndex()"
78215
- >
78216
- @for (cardId of player().hand ?? []; track cardId + $index) {
78217
- @let card = cardDefinition(cardId);
78218
- <div
78219
- class="christianity-hand-card-wrap"
78220
- [class.is-selected]="selectedHandCard() === cardId"
78221
- [class.is-dragging]="draggingCardId() === cardId"
78222
- [style.z-index]="handCardZIndex($index)"
78223
- [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
78224
- (pointerenter)="setHoveredHandIndex($index)"
78225
- (pointerdown)="setHoveredHandIndex($index)"
78226
- (pointerup)="releaseHoveredHand($event)"
78497
+ <section
78498
+ class="christianity-shared-zone"
78499
+ data-testid="christianity-shared-zone"
78500
+ [attr.aria-label]="t('christianitySharedZone')"
78501
+ >
78502
+ <button
78503
+ type="button"
78504
+ class="christianity-zone-pile christianity-zone-pile--history"
78505
+ data-testid="christianity-history-zone"
78506
+ [class.is-empty-zone]="!topHistoryCard()"
78507
+ [class.has-new-card]="historyPulse()"
78508
+ valLightRipple
78509
+ (click)="openHistory()"
78510
+ >
78511
+ @if (historyStack().length) {
78512
+ <span class="christianity-zone-card-stack" aria-hidden="true">
78513
+ @for (cardId of historyStack(); track cardId + $index) {
78514
+ <val-christianity-card
78515
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
78516
+ variant="baby"
78517
+ [style.transform]="zoneStackTransform($index, historyStack().length)"
78518
+ ></val-christianity-card>
78519
+ }
78520
+ </span>
78521
+ } @else {
78522
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78523
+ }
78524
+ <span>{{ t('christianityHistory') }}</span>
78525
+ <strong>{{ game().historyZone.length }}</strong>
78526
+ </button>
78527
+ <button
78528
+ type="button"
78529
+ class="christianity-zone-pile christianity-zone-pile--deck"
78530
+ data-testid="christianity-deck-zone"
78531
+ valLightRipple
78532
+ aria-live="polite"
78533
+ (click)="inspectDeck()"
78534
+ >
78535
+ <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
78536
+ <i></i>
78537
+ </span>
78538
+ <span>{{ t('christianityDeck') }}</span>
78539
+ <strong>{{ game().sharedDeck.length }}</strong>
78540
+ </button>
78541
+ <button
78542
+ type="button"
78543
+ class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
78544
+ data-testid="christianity-place-zone"
78545
+ cdkDropList
78546
+ id="christianity-place-drop"
78547
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
78548
+ [cdkDropListEnterPredicate]="canDropOnPlace"
78549
+ [cdkDropListSortingDisabled]="true"
78550
+ [class.is-empty-zone]="!activePlaceCard()"
78551
+ [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
78552
+ valLightRipple
78553
+ (cdkDropListDropped)="dropOnPlace($event)"
78554
+ (click)="playSelectedPlaceOrInspect()"
78227
78555
  >
78556
+ @if (placeStack().length) {
78557
+ @if (activePlaceCard(); as placeCard) {
78558
+ <div
78559
+ class="christianity-place-backdrop"
78560
+ [style.background-image]="placeBackdropImage(placeCard)"
78561
+ aria-hidden="true"
78562
+ ></div>
78563
+ }
78564
+ <span class="christianity-zone-card-stack" aria-hidden="true">
78565
+ @for (cardId of placeStack(); track cardId + $index) {
78566
+ <val-christianity-card
78567
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
78568
+ variant="baby"
78569
+ [style.transform]="zoneStackTransform($index, placeStack().length)"
78570
+ ></val-christianity-card>
78571
+ }
78572
+ </span>
78573
+ } @else {
78574
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78575
+ }
78576
+ <span>{{ t('christianityPlace') }}</span>
78577
+ <strong>{{ game().placeZone.length }}</strong>
78578
+ @if (canPlaceSelectedPlace() || isDraggingPlace()) {
78579
+ <em>{{ t('christianityPlayHere') }}</em>
78580
+ }
78581
+ </button>
78582
+ </section>
78583
+
78584
+ <section class="christianity-player-side">
78585
+ <section
78586
+ class="christianity-battle-zone"
78587
+ data-testid="christianity-battle-P1"
78588
+ cdkDropList
78589
+ id="christianity-player-battle-source"
78590
+ [cdkDropListConnectedTo]="attackTargetDropIds()"
78591
+ [cdkDropListSortingDisabled]="true"
78592
+ [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78593
+ [attr.aria-label]="t('christianityYourBattle')"
78594
+ >
78595
+ @for (slot of battleSlots('P1'); track slot.trackId) {
78596
+ @if (slot.card) {
78597
+ <button
78598
+ type="button"
78599
+ class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78600
+ [attr.data-testid]="battleCardTestId('P1', slot.card)"
78601
+ cdkDrag
78602
+ [cdkDragData]="slot.card.instanceId"
78603
+ [cdkDragDisabled]="!canDragAttacker(slot.card)"
78604
+ valLightRipple
78605
+ [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
78606
+ [class.is-exhausted]="!slot.card.canAttack"
78607
+ [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
78608
+ [class.is-combat-attacker]="isCombatAttacker(slot.card)"
78609
+ (cdkDragStarted)="startDraggingAttacker(slot.card)"
78610
+ (cdkDragEnded)="endDraggingAttacker()"
78611
+ (click)="selectAttacker(slot.card)"
78612
+ >
78613
+ <val-christianity-card
78614
+ [card]="characterRenderCard(slot.card)"
78615
+ variant="board"
78616
+ ></val-christianity-card>
78617
+ <ng-template cdkDragPreview>
78618
+ <div class="christianity-card-drag-preview">
78619
+ <val-christianity-card
78620
+ [card]="characterRenderCard(slot.card)"
78621
+ variant="board"
78622
+ ></val-christianity-card>
78623
+ </div>
78624
+ </ng-template>
78625
+ <ng-template cdkDragPlaceholder>
78626
+ <div class="christianity-card-drag-placeholder"></div>
78627
+ </ng-template>
78628
+ </button>
78629
+ } @else {
78630
+ <button
78631
+ type="button"
78632
+ class="christianity-slot"
78633
+ [attr.data-testid]="battleSlotTestId('P1', $index)"
78634
+ cdkDropList
78635
+ [id]="battleSlotDropId($index)"
78636
+ [cdkDropListData]="$index"
78637
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
78638
+ [cdkDropListEnterPredicate]="canDropOnBattleSlot"
78639
+ [cdkDropListSortingDisabled]="true"
78640
+ [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78641
+ valLightRipple
78642
+ (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
78643
+ (click)="playSelectedCard($index)"
78644
+ >
78645
+ @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
78646
+ <span>{{ t('christianityPlayHere') }}</span>
78647
+ }
78648
+ </button>
78649
+ }
78650
+ }
78651
+ </section>
78652
+
78653
+ <section class="christianity-player-rack">
78654
+ <div class="christianity-offerings-zone">
78655
+ <div
78656
+ class="christianity-offering-drop-target"
78657
+ cdkDropList
78658
+ id="christianity-offerings-drop"
78659
+ [cdkDropListConnectedTo]="['christianity-hand-source']"
78660
+ [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
78661
+ [cdkDropListEnterPredicate]="canDropOnOfferings"
78662
+ [cdkDropListSortingDisabled]="true"
78663
+ (cdkDropListDropped)="dropOnOfferings($event)"
78664
+ >
78665
+ <button
78666
+ type="button"
78667
+ class="christianity-offering-pile"
78668
+ data-testid="christianity-offerings-P1"
78669
+ [class.is-empty-zone]="!topOffering('P1')"
78670
+ valLightRipple
78671
+ (click)="handleOfferingPile('P1')"
78672
+ >
78673
+ @if (offeringStack('P1').length) {
78674
+ <span class="christianity-zone-card-stack" aria-hidden="true">
78675
+ @for (offering of offeringStack('P1'); track offering.instanceId) {
78676
+ <val-christianity-card
78677
+ [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
78678
+ [variant]="offering.ready ? 'baby' : 'back'"
78679
+ [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
78680
+ ></val-christianity-card>
78681
+ }
78682
+ </span>
78683
+ } @else {
78684
+ <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78685
+ }
78686
+ <span>{{ t('christianityOfferingReserve') }}</span>
78687
+ <strong>{{ readyOfferings(player()) }}</strong>
78688
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
78689
+ </button>
78690
+ </div>
78691
+ </div>
78228
78692
  <button
78229
78693
  type="button"
78230
- class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
78231
- [attr.data-testid]="handCardTestId(cardId, $index)"
78232
- cdkDrag
78694
+ class="christianity-leader-card christianity-render-card-button"
78695
+ data-testid="christianity-leader-P1"
78233
78696
  valLightRipple
78234
- [cdkDragData]="cardId"
78235
- [cdkDragDisabled]="!canPlay(card)"
78236
- [attr.aria-label]="handCardActionLabel(card)"
78237
- [class.is-selected]="selectedHandCard() === cardId"
78238
- [class.is-disabled]="!canPlay(card)"
78239
- (cdkDragStarted)="startDraggingCard(cardId)"
78240
- (cdkDragEnded)="endDraggingCard()"
78241
- (click)="selectHandCard(cardId)"
78697
+ (click)="inspectLeader('P1')"
78242
78698
  >
78243
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78244
- <ng-template cdkDragPreview>
78245
- <div class="christianity-card-drag-preview">
78246
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78247
- </div>
78248
- </ng-template>
78249
- <ng-template cdkDragPlaceholder>
78250
- <div class="christianity-card-drag-placeholder"></div>
78251
- </ng-template>
78699
+ <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
78252
78700
  </button>
78253
- <button
78254
- type="button"
78255
- class="christianity-card-eye"
78256
- [attr.aria-label]="t('christianityPreviewCard')"
78257
- (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
78258
- ></button>
78259
- </div>
78260
- }
78701
+ <div class="christianity-player-command">
78702
+ <div class="christianity-player-badge">
78703
+ <span class="christianity-avatar" [style.background]="player().avatarColor">{{
78704
+ player().avatar
78705
+ }}</span>
78706
+ <strong>{{ player().displayName }}</strong>
78707
+ </div>
78708
+ </div>
78709
+ </section>
78710
+ </section>
78711
+ </section>
78712
+
78713
+ <section class="christianity-hand-shelf">
78714
+ <div class="christianity-hand-meter">
78715
+ <span>{{ t('christianityHand') }}</span>
78716
+ <strong>{{ player().hand?.length ?? 0 }}</strong>
78717
+ </div>
78718
+ <section
78719
+ class="christianity-hand-fan"
78720
+ data-testid="christianity-hand-P1"
78721
+ cdkDropList
78722
+ id="christianity-hand-source"
78723
+ [cdkDropListConnectedTo]="dropTargetIds()"
78724
+ [attr.aria-label]="t('christianityYourHand')"
78725
+ (pointerleave)="clearHoveredHandIndex()"
78726
+ >
78727
+ @for (cardId of player().hand ?? []; track cardId + $index) {
78728
+ @let card = cardDefinition(cardId);
78729
+ <div
78730
+ class="christianity-hand-card-wrap"
78731
+ [class.is-selected]="selectedHandCard() === cardId"
78732
+ [class.is-dragging]="draggingCardId() === cardId"
78733
+ [style.z-index]="handCardZIndex($index)"
78734
+ [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
78735
+ (pointerenter)="setHoveredHandIndex($index)"
78736
+ (pointerdown)="setHoveredHandIndex($index)"
78737
+ (pointerup)="releaseHoveredHand($event)"
78738
+ >
78739
+ <button
78740
+ type="button"
78741
+ class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
78742
+ [attr.data-testid]="handCardTestId(cardId, $index)"
78743
+ cdkDrag
78744
+ valLightRipple
78745
+ [cdkDragData]="cardId"
78746
+ [cdkDragDisabled]="!canPlay(card)"
78747
+ [attr.aria-label]="handCardActionLabel(card)"
78748
+ [class.is-selected]="selectedHandCard() === cardId"
78749
+ [class.is-disabled]="!canPlay(card)"
78750
+ (cdkDragStarted)="startDraggingCard(cardId)"
78751
+ (cdkDragEnded)="endDraggingCard()"
78752
+ (click)="selectHandCard(cardId)"
78753
+ >
78754
+ <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78755
+ <ng-template cdkDragPreview>
78756
+ <div class="christianity-card-drag-preview">
78757
+ <val-christianity-card
78758
+ [card]="definitionToRenderCard(card)"
78759
+ variant="baby"
78760
+ ></val-christianity-card>
78761
+ </div>
78762
+ </ng-template>
78763
+ <ng-template cdkDragPlaceholder>
78764
+ <div class="christianity-card-drag-placeholder"></div>
78765
+ </ng-template>
78766
+ </button>
78767
+ <button
78768
+ type="button"
78769
+ class="christianity-card-eye"
78770
+ [attr.aria-label]="t('christianityPreviewCard')"
78771
+ (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
78772
+ ></button>
78773
+ </div>
78774
+ }
78775
+ </section>
78261
78776
  </section>
78262
- </section>
78263
78777
  }
78264
78778
 
78265
78779
  @if (selectedPreview()) {
78266
- <button type="button" class="christianity-preview-backdrop" [attr.aria-label]="t('christianityClosePreview')" (click)="closePreview()"></button>
78780
+ <button
78781
+ type="button"
78782
+ class="christianity-preview-backdrop"
78783
+ [attr.aria-label]="t('christianityClosePreview')"
78784
+ (click)="closePreview()"
78785
+ ></button>
78267
78786
  <aside
78268
78787
  class="christianity-floating-card-shell"
78269
78788
  [class.is-history-preview]="canNavigatePreview()"
@@ -78303,7 +78822,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78303
78822
  </button>
78304
78823
  }
78305
78824
  @if (selectedPreviewNeighborRenderCard(-1); as previousCard) {
78306
- <val-christianity-card class="christianity-preview-ghost christianity-preview-ghost--previous" [card]="previousCard" variant="full"></val-christianity-card>
78825
+ <val-christianity-card
78826
+ class="christianity-preview-ghost christianity-preview-ghost--previous"
78827
+ [card]="previousCard"
78828
+ variant="full"
78829
+ ></val-christianity-card>
78307
78830
  }
78308
78831
  <div
78309
78832
  class="christianity-floating-card"
@@ -78322,7 +78845,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78322
78845
  }
78323
78846
  </div>
78324
78847
  @if (selectedPreviewNeighborRenderCard(1); as nextCard) {
78325
- <val-christianity-card class="christianity-preview-ghost christianity-preview-ghost--next" [card]="nextCard" variant="full"></val-christianity-card>
78848
+ <val-christianity-card
78849
+ class="christianity-preview-ghost christianity-preview-ghost--next"
78850
+ [card]="nextCard"
78851
+ variant="full"
78852
+ ></val-christianity-card>
78326
78853
  }
78327
78854
  </div>
78328
78855
  <div class="christianity-preview-side">
@@ -78406,7 +78933,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78406
78933
  }
78407
78934
 
78408
78935
  @if (selectedInfo(); as info) {
78409
- <button type="button" class="christianity-info-backdrop" [attr.aria-label]="t('close')" (click)="closeInfo()"></button>
78936
+ <button
78937
+ type="button"
78938
+ class="christianity-info-backdrop"
78939
+ [attr.aria-label]="t('close')"
78940
+ (click)="closeInfo()"
78941
+ ></button>
78410
78942
  <aside class="christianity-info-panel" aria-live="polite">
78411
78943
  <button type="button" class="christianity-dialog-close christianity-info-close" (click)="closeInfo()">
78412
78944
  {{ t('close') }}
@@ -78431,14 +78963,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78431
78963
  @if (screen() === 'table') {
78432
78964
  <section class="christianity-actions" data-testid="christianity-actions" aria-live="polite">
78433
78965
  <p class="christianity-sr-only" data-testid="christianity-status">{{ statusText() }}</p>
78434
- <button type="button" data-testid="christianity-primary-action" [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()" (click)="playSelectedCard()">
78966
+ <button
78967
+ type="button"
78968
+ data-testid="christianity-primary-action"
78969
+ [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()"
78970
+ (click)="playSelectedCard()"
78971
+ >
78435
78972
  {{ selectedActionLabel() }}
78436
78973
  </button>
78437
78974
  </section>
78438
78975
  }
78439
78976
 
78440
78977
  @if (showHistory()) {
78441
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseHistory')" (click)="closeHistory()"></button>
78978
+ <button
78979
+ type="button"
78980
+ class="christianity-history-backdrop"
78981
+ [attr.aria-label]="t('christianityCloseHistory')"
78982
+ (click)="closeHistory()"
78983
+ ></button>
78442
78984
  <section class="christianity-history-drawer" data-testid="christianity-history-drawer">
78443
78985
  <header>
78444
78986
  <h2>{{ t('christianityHistory') }}</h2>
@@ -78453,7 +78995,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78453
78995
  <div class="christianity-history-grid" data-testid="christianity-history-grid">
78454
78996
  @for (cardId of game().historyZone; track cardId + $index) {
78455
78997
  @let card = cardDefinition(cardId);
78456
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="historyCardTestId(cardId, $index)" valLightRipple (click)="inspectHistoryCard(cardId)">
78998
+ <button
78999
+ type="button"
79000
+ class="christianity-gallery-card-button christianity-render-card-button"
79001
+ [attr.data-testid]="historyCardTestId(cardId, $index)"
79002
+ valLightRipple
79003
+ (click)="inspectHistoryCard(cardId)"
79004
+ >
78457
79005
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78458
79006
  </button>
78459
79007
  }
@@ -78465,7 +79013,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78465
79013
  }
78466
79014
 
78467
79015
  @if (showPlace()) {
78468
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseHistory')" (click)="closePlace()"></button>
79016
+ <button
79017
+ type="button"
79018
+ class="christianity-history-backdrop"
79019
+ [attr.aria-label]="t('christianityCloseHistory')"
79020
+ (click)="closePlace()"
79021
+ ></button>
78469
79022
  <section class="christianity-history-drawer christianity-place-drawer" data-testid="christianity-place-drawer">
78470
79023
  <header>
78471
79024
  <h2>{{ t('christianityPlace') }}</h2>
@@ -78478,8 +79031,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78478
79031
  </header>
78479
79032
  <div class="christianity-history-grid" data-testid="christianity-place-grid">
78480
79033
  @for (cardId of game().placeZone; track cardId + $index) {
78481
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="'christianity-place-card-' + $index" valLightRipple (click)="inspectPlaceCard(cardId)">
78482
- <val-christianity-card [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })" variant="baby"></val-christianity-card>
79034
+ <button
79035
+ type="button"
79036
+ class="christianity-gallery-card-button christianity-render-card-button"
79037
+ [attr.data-testid]="'christianity-place-card-' + $index"
79038
+ valLightRipple
79039
+ (click)="inspectPlaceCard(cardId)"
79040
+ >
79041
+ <val-christianity-card
79042
+ [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
79043
+ variant="baby"
79044
+ ></val-christianity-card>
78483
79045
  </button>
78484
79046
  }
78485
79047
  </div>
@@ -78487,8 +79049,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78487
79049
  }
78488
79050
 
78489
79051
  @if (showGallery()) {
78490
- <button type="button" class="christianity-history-backdrop christianity-gallery-backdrop" [attr.aria-label]="t('christianityCloseGallery')" (click)="closeGallery()"></button>
78491
- <section class="christianity-history-drawer christianity-gallery-drawer" data-testid="christianity-gallery-drawer">
79052
+ <button
79053
+ type="button"
79054
+ class="christianity-history-backdrop christianity-gallery-backdrop"
79055
+ [attr.aria-label]="t('christianityCloseGallery')"
79056
+ (click)="closeGallery()"
79057
+ ></button>
79058
+ <section
79059
+ class="christianity-history-drawer christianity-gallery-drawer"
79060
+ data-testid="christianity-gallery-drawer"
79061
+ >
78492
79062
  <header>
78493
79063
  <div>
78494
79064
  <h2>{{ t('christianityGallery') }}</h2>
@@ -78504,7 +79074,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78504
79074
  <div class="christianity-gallery-controls">
78505
79075
  <label>
78506
79076
  <span>{{ t('christianityGallerySearch') }}</span>
78507
- <input type="search" [value]="gallerySearch()" (input)="setGallerySearch($event)" [placeholder]="t('christianityGallerySearchPlaceholder')" />
79077
+ <input
79078
+ type="search"
79079
+ [value]="gallerySearch()"
79080
+ (input)="setGallerySearch($event)"
79081
+ [placeholder]="t('christianityGallerySearchPlaceholder')"
79082
+ />
78508
79083
  </label>
78509
79084
  <label>
78510
79085
  <span>{{ t('christianityGalleryFilter') }}</span>
@@ -78523,7 +79098,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78523
79098
  </div>
78524
79099
  <div class="christianity-gallery-grid">
78525
79100
  @for (card of galleryCards(); track card.id) {
78526
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" valLightRipple (click)="inspectGalleryCard(card.id)">
79101
+ <button
79102
+ type="button"
79103
+ class="christianity-gallery-card-button christianity-render-card-button"
79104
+ valLightRipple
79105
+ (click)="inspectGalleryCard(card.id)"
79106
+ >
78527
79107
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78528
79108
  </button>
78529
79109
  } @empty {
@@ -78535,12 +79115,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78535
79115
 
78536
79116
  @if (offeringsSeat()) {
78537
79117
  @let offeringPlayer = game().players[offeringsSeat()!];
78538
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseOfferings')" (click)="closeOfferings()"></button>
78539
- <section class="christianity-history-drawer christianity-offerings-drawer" [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)">
79118
+ <button
79119
+ type="button"
79120
+ class="christianity-history-backdrop"
79121
+ [attr.aria-label]="t('christianityCloseOfferings')"
79122
+ (click)="closeOfferings()"
79123
+ ></button>
79124
+ <section
79125
+ class="christianity-history-drawer christianity-offerings-drawer"
79126
+ [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)"
79127
+ >
78540
79128
  <header>
78541
79129
  <h2>{{ t('christianityOfferings') }}</h2>
78542
79130
  <div class="christianity-dialog-actions">
78543
- <span>{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} · {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span>
79131
+ <span
79132
+ >{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} ·
79133
+ {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span
79134
+ >
78544
79135
  <button type="button" class="christianity-dialog-close" (click)="closeOfferings()">
78545
79136
  {{ t('close') }}
78546
79137
  </button>
@@ -78550,14 +79141,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78550
79141
  <span class="christianity-rule-note-icon" aria-hidden="true">i</span>
78551
79142
  <p>{{ t('christianityOfferingsRuleBody') }}</p>
78552
79143
  </div>
78553
- <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
78554
- @for (offering of offeringPlayer.offerings; track offering.instanceId) {
78555
- <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
78556
- <button type="button" class="christianity-offering-card christianity-render-card-button" [attr.data-testid]="offeringCardTestId(offering)" valLightRipple (click)="inspectOffering(offering)">
78557
- <val-christianity-card [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()" [variant]="offering.ready ? 'baby' : 'back'"></val-christianity-card>
79144
+ <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
79145
+ @for (offering of offeringPlayer.offerings; track offering.instanceId) {
79146
+ <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
79147
+ <button
79148
+ type="button"
79149
+ class="christianity-offering-card christianity-render-card-button"
79150
+ [attr.data-testid]="offeringCardTestId(offering)"
79151
+ valLightRipple
79152
+ (click)="inspectOffering(offering)"
79153
+ >
79154
+ <val-christianity-card
79155
+ [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
79156
+ [variant]="offering.ready ? 'baby' : 'back'"
79157
+ ></val-christianity-card>
78558
79158
  </button>
78559
79159
  <div>
78560
- <strong>{{ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted') }}</strong>
79160
+ <strong>{{
79161
+ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted')
79162
+ }}</strong>
78561
79163
  <p>{{ offeringAbilityText(offering) }}</p>
78562
79164
  @if (offering.abilityName) {
78563
79165
  <button