valtech-components 4.0.586 → 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.586';
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;
@@ -73992,7 +73992,14 @@ function canPlayCard(game, seat, card) {
73992
73992
  if (card.type === 'offering')
73993
73993
  return !player.offeringPlayedThisTurn;
73994
73994
  if (card.type === 'character') {
73995
- 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);
73996
74003
  }
73997
74004
  if (card.type === 'place')
73998
74005
  return true;
@@ -74028,7 +74035,7 @@ function playCardFromHand(game, seat, card, options = {}) {
74028
74035
  player.offeringPlayedThisTurn = true;
74029
74036
  }
74030
74037
  else if (card.type === 'character') {
74031
- 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));
74032
74039
  const targetSlot = options.slotIndex ?? [0, 1, 2, 3].find(index => !occupiedSlots.has(index));
74033
74040
  if (targetSlot === undefined || occupiedSlots.has(targetSlot)) {
74034
74041
  return { game, played: false, playedOfferingValue: 0, spentOfferings: 0, log: [] };
@@ -74050,6 +74057,16 @@ function playCardFromHand(game, seat, card, options = {}) {
74050
74057
  });
74051
74058
  player.battleZone.sort((left, right) => (left.slot ?? 0) - (right.slot ?? 0));
74052
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
+ }
74053
74070
  else if (card.type === 'place') {
74054
74071
  const previousPlace = next.placeZone[0];
74055
74072
  if (previousPlace) {
@@ -74086,9 +74103,7 @@ function resolveOfferingAbility(game, seat, offeringInstanceId, messages) {
74086
74103
  }
74087
74104
  const reserveGain = offeringAbilityReserveGain(liveOffering.abilityText, liveOffering.value);
74088
74105
  player.temporaryOfferingReserve = (player.temporaryOfferingReserve ?? 0) + reserveGain;
74089
- player.offerings = player.offerings.map(card => (card.instanceId === offeringInstanceId
74090
- ? { ...card, abilityUsedThisTurn: true }
74091
- : card));
74106
+ player.offerings = player.offerings.map(card => card.instanceId === offeringInstanceId ? { ...card, abilityUsedThisTurn: true } : card);
74092
74107
  player.leaderHp = Math.max(0, player.leaderHp - 1);
74093
74108
  next.revision++;
74094
74109
  const log = [`${liveOffering.name}: ${liveOffering.abilityName}.`];
@@ -74103,6 +74118,12 @@ function playCardLog(card, displayName) {
74103
74118
  return `${displayName} queda lista como ofrenda.`;
74104
74119
  if (card.type === 'place')
74105
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.`;
74106
74127
  return `${displayName} entra en batalla.`;
74107
74128
  }
74108
74129
  function resolveAttackLeader(game, attackerSeat, attackerInstanceId, messages) {
@@ -74117,7 +74138,9 @@ function resolveAttackLeader(game, attackerSeat, attackerInstanceId, messages) {
74117
74138
  const damage = attacker.attack;
74118
74139
  next.players[defenderSeat].leaderHp = Math.max(0, next.players[defenderSeat].leaderHp - damage);
74119
74140
  attacker.canAttack = false;
74120
- const log = [`${attacker.name} ${attackerSeat === 'P1' ? messages.attacksOpponentLeader : messages.attacksYourLeader}`];
74141
+ const log = [
74142
+ `${attacker.name} ${attackerSeat === 'P1' ? messages.attacksOpponentLeader : messages.attacksYourLeader}`,
74143
+ ];
74121
74144
  evaluateVictory(next, log, messages);
74122
74145
  next.revision++;
74123
74146
  next.log = [...log, ...next.log].slice(0, 4);
@@ -74142,7 +74165,13 @@ function resolveAttackCharacter(game, attackerSeat, attackerInstanceId, targetIn
74142
74165
  evaluateVictory(next, log, messages);
74143
74166
  next.revision++;
74144
74167
  next.log = [...log, ...next.log].slice(0, 4);
74145
- 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
+ };
74146
74175
  }
74147
74176
  function advanceTurn(game, messages) {
74148
74177
  game.players[game.turnOwner].temporaryOfferingReserve = 0;
@@ -74150,7 +74179,11 @@ function advanceTurn(game, messages) {
74150
74179
  game.turnCount++;
74151
74180
  const current = game.players[game.turnOwner];
74152
74181
  current.offeringPlayedThisTurn = false;
74153
- 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
+ }));
74154
74187
  current.battleZone = current.battleZone.map(card => ({ ...card, canAttack: true }));
74155
74188
  const drawnCard = game.sharedDeck.shift();
74156
74189
  const log = [];
@@ -74172,16 +74205,16 @@ function advanceTurn(game, messages) {
74172
74205
  ];
74173
74206
  }
74174
74207
  function evaluateVictory(game, log, messages) {
74175
- if (game.players.P1.leaderHp > 0
74176
- && game.players.P2.leaderHp > 0
74177
- && game.sharedDeck.length > 0)
74208
+ if (game.players.P1.leaderHp > 0 && game.players.P2.leaderHp > 0 && game.sharedDeck.length > 0)
74178
74209
  return;
74179
74210
  game.status = 'finished';
74180
74211
  const winner = game.players.P1.leaderHp <= 0
74181
74212
  ? 'P2'
74182
74213
  : game.players.P2.leaderHp <= 0
74183
74214
  ? 'P1'
74184
- : game.players.P1.leaderHp >= game.players.P2.leaderHp ? 'P1' : 'P2';
74215
+ : game.players.P1.leaderHp >= game.players.P2.leaderHp
74216
+ ? 'P1'
74217
+ : 'P2';
74185
74218
  game.matchScore[winner] += 1;
74186
74219
  log.push(winner === 'P1' ? messages.youWin : messages.opponentWins);
74187
74220
  }
@@ -74573,10 +74606,58 @@ const STARTER_CARDS = {
74573
74606
  author: 'MVP',
74574
74607
  rarity: 'normal',
74575
74608
  },
74576
- p_guard: { id: 'p_guard', name: 'Guard', type: 'character', cost: 2, attack: 1, life: 3, edition: 'GEN', collectorNumber: '003/020', year: '2026', author: 'MVP', rarity: 'normal' },
74577
- p_warrior: { id: 'p_warrior', name: 'Warrior', type: 'character', cost: 2, attack: 2, life: 2, edition: 'GEN', collectorNumber: '004/020', year: '2026', author: 'MVP', rarity: 'normal' },
74578
- p_elder: { id: 'p_elder', name: 'Elder', type: 'character', cost: 3, attack: 2, life: 4, edition: 'GEN', collectorNumber: '005/020', year: '2026', author: 'MVP', rarity: 'uncommon' },
74579
- p_champion: { id: 'p_champion', name: 'Champion', type: 'character', cost: 4, attack: 4, life: 4, edition: 'GEN', collectorNumber: '006/020', year: '2026', author: 'MVP', rarity: 'rare' },
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
+ },
74580
74661
  };
74581
74662
  const STARTING_SHARED_DECK = [
74582
74663
  'o_simple',
@@ -74620,7 +74701,14 @@ const STARTING_GAME = {
74620
74701
  leaderHp: 15,
74621
74702
  leaderMaxHp: 15,
74622
74703
  offerings: [
74623
- { 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
+ },
74624
74712
  {
74625
74713
  instanceId: 'p1_offering_002',
74626
74714
  cardId: 'o_forbidden',
@@ -74645,7 +74733,16 @@ const STARTING_GAME = {
74645
74733
  leaderName: 'Noah',
74646
74734
  leaderHp: 15,
74647
74735
  leaderMaxHp: 15,
74648
- 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
+ ],
74649
74746
  offeringPlayedThisTurn: false,
74650
74747
  handCount: 4,
74651
74748
  battleZone: [
@@ -74680,7 +74777,9 @@ class ChristianityGameComponent {
74680
74777
  const nextIndex = (currentIndex + direction + items.length) % items.length;
74681
74778
  const item = items[nextIndex];
74682
74779
  if (item.kind === 'history') {
74683
- 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
+ });
74684
74783
  }
74685
74784
  if (item.kind === 'hand') {
74686
74785
  return this.definitionToRenderCard(this.cardDefinition(item.cardId));
@@ -74787,7 +74886,9 @@ class ChristianityGameComponent {
74787
74886
  if (this.tutorialPhase() === 'coin') {
74788
74887
  return setup.coinWinner === 'P1' ? this.t('christianityCoinYouWon') : this.t('christianityCoinRivalWon');
74789
74888
  }
74790
- return setup.leaderPicker === 'P1' ? this.t('christianityChooseLeaderTitle') : this.t('christianityRivalChoosesLeaderTitle');
74889
+ return setup.leaderPicker === 'P1'
74890
+ ? this.t('christianityChooseLeaderTitle')
74891
+ : this.t('christianityRivalChoosesLeaderTitle');
74791
74892
  });
74792
74893
  this.setupBody = computed(() => {
74793
74894
  const setup = this.tutorialSetup();
@@ -74814,7 +74915,11 @@ class ChristianityGameComponent {
74814
74915
  this.hasOpponentGuards = computed(() => this.opponent().battleZone.length > 0);
74815
74916
  this.opponentHandCount = computed(() => this.opponent().handCount ?? this.opponent().hand?.length ?? 0);
74816
74917
  this.galleryCards = computed(() => {
74817
- 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, '');
74818
74923
  const type = this.galleryType();
74819
74924
  return this.allCatalogCards()
74820
74925
  .filter(card => this.isClassicGalleryCard(card))
@@ -74849,12 +74954,12 @@ class ChristianityGameComponent {
74849
74954
  { value: 'place', label: this.t('christianityPlaceCard') },
74850
74955
  { value: 'event', label: this.t('christianityEventCard') },
74851
74956
  ]);
74852
- this.hasModalOverlay = computed(() => !!this.selectedPreview()
74853
- || !!this.selectedInfo()
74854
- || this.showHistory()
74855
- || this.showPlace()
74856
- || this.showGallery()
74857
- || !!this.offeringsSeat());
74957
+ this.hasModalOverlay = computed(() => !!this.selectedPreview() ||
74958
+ !!this.selectedInfo() ||
74959
+ this.showHistory() ||
74960
+ this.showPlace() ||
74961
+ this.showGallery() ||
74962
+ !!this.offeringsSeat());
74858
74963
  this.previewCarouselItems = computed(() => this.currentPreviewCarouselItems());
74859
74964
  this.canNavigatePreview = computed(() => this.previewCarouselItems().length > 1);
74860
74965
  this.previewPositionLabel = computed(() => {
@@ -74880,25 +74985,28 @@ class ChristianityGameComponent {
74880
74985
  canUse: this.canUsePreviewAbility(index),
74881
74986
  }))
74882
74987
  : text.abilityName || text.abilityText
74883
- ? [{
74988
+ ? [
74989
+ {
74884
74990
  index: 0,
74885
74991
  title: text.abilityName ?? this.t('christianityAbility'),
74886
74992
  body: text.abilityText ?? '',
74887
74993
  canUse: this.canUsePreviewAbility(0),
74888
- }]
74889
- : [{
74994
+ },
74995
+ ]
74996
+ : [
74997
+ {
74890
74998
  index: 0,
74891
74999
  title: this.t('christianityNoAbilityTitle'),
74892
75000
  body: this.t('christianityNoAbilityBody'),
74893
75001
  canUse: false,
74894
- }];
75002
+ },
75003
+ ];
74895
75004
  return abilities;
74896
75005
  });
74897
75006
  this.showPreviewAbilityTabs = computed(() => {
74898
75007
  const abilities = this.previewAbilities();
74899
- return abilities.length > 1
74900
- || !this.isPreviewAbilityPlaceholder(abilities[0])
74901
- && (abilities[0]?.title ?? '') !== this.t('christianityAbility');
75008
+ return (abilities.length > 1 ||
75009
+ (!this.isPreviewAbilityPlaceholder(abilities[0]) && (abilities[0]?.title ?? '') !== this.t('christianityAbility')));
74902
75010
  });
74903
75011
  this.showPreviewAbilities = computed(() => {
74904
75012
  const preview = this.selectedPreview();
@@ -75071,7 +75179,9 @@ class ChristianityGameComponent {
75071
75179
  return this.t('christianityWaitTurn');
75072
75180
  }
75073
75181
  if (attacker) {
75074
- return this.hasOpponentGuards() ? this.t('christianityChooseCharacterTarget') : this.t('christianityChooseTarget');
75182
+ return this.hasOpponentGuards()
75183
+ ? this.t('christianityChooseCharacterTarget')
75184
+ : this.t('christianityChooseTarget');
75075
75185
  }
75076
75186
  if (handCard) {
75077
75187
  if (!this.canPlay(handCard)) {
@@ -75080,12 +75190,18 @@ class ChristianityGameComponent {
75080
75190
  if (handCard.type === 'place') {
75081
75191
  return this.t('christianityChoosePlaceSlot');
75082
75192
  }
75193
+ if (handCard.type === 'action' ||
75194
+ handCard.type === 'wisdom' ||
75195
+ handCard.type === 'event' ||
75196
+ handCard.type === 'tool') {
75197
+ return this.t('christianityPlayCard');
75198
+ }
75083
75199
  return handCard.type === 'offering' ? this.t('christianityPlaceOffering') : this.t('christianityChooseSlot');
75084
75200
  }
75085
75201
  return this.t('christianityChooseCard');
75086
75202
  });
75087
75203
  this.i18n.registerDefaults('MiniGames', MINI_GAMES_I18N);
75088
- effect((onCleanup) => {
75204
+ effect(onCleanup => {
75089
75205
  if (!this.hasModalOverlay())
75090
75206
  return;
75091
75207
  const previousOverflow = this.document.body.style.overflow;
@@ -75141,7 +75257,7 @@ class ChristianityGameComponent {
75141
75257
  try {
75142
75258
  const response = await fetch(GENESIS_CATALOG_URL);
75143
75259
  if (response.ok) {
75144
- const catalog = await response.json();
75260
+ const catalog = (await response.json());
75145
75261
  const cards = (catalog.cards ?? []).map(card => this.normalizeCatalogCard(card, catalog));
75146
75262
  if (cards.length) {
75147
75263
  this.cardCatalog.set(Object.fromEntries(cards.map(card => [card.id, card])));
@@ -75171,19 +75287,19 @@ class ChristianityGameComponent {
75171
75287
  const editionIcon = card.editionIcon ?? catalog.edition?.icon ?? GENESIS_ICON_URL;
75172
75288
  const verseText = card.verseText ?? card.bible?.text;
75173
75289
  const scripture = card.scripture ?? this.formatScripture(card.bible);
75174
- 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;
75175
75291
  const life = card.type === 'leader'
75176
- ? card.leaderLife ?? card.life ?? 10
75292
+ ? (card.leaderLife ?? card.life ?? 10)
75177
75293
  : card.type === 'offering'
75178
75294
  ? offeringValue
75179
- : card.life ?? 0;
75295
+ : (card.life ?? 0);
75180
75296
  return {
75181
75297
  ...card,
75182
- cost: card.type === 'offering' ? 0 : card.cost ?? 0,
75298
+ cost: card.type === 'offering' ? 0 : (card.cost ?? 0),
75183
75299
  offeringValue,
75184
75300
  life,
75185
- leaderLife: card.type === 'leader' ? card.leaderLife ?? life : card.leaderLife,
75186
- 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,
75187
75303
  abilityName: card.abilityName ?? card.abilities?.[0]?.name,
75188
75304
  abilityText: card.abilityText ?? card.abilities?.[0]?.text,
75189
75305
  verseText,
@@ -75205,21 +75321,27 @@ class ChristianityGameComponent {
75205
75321
  createTutorialGame(cards, options = {}) {
75206
75322
  const leaders = this.tutorialLeaderCards(cards);
75207
75323
  const playerLeader = this.cardById(options.playerLeaderId) ?? this.findCard(leaders, 'Adán') ?? leaders[0];
75208
- const opponentLeader = this.cardById(options.opponentLeaderId) ?? this.findCard(leaders, 'Noé') ?? leaders.find(card => card.id !== playerLeader?.id) ?? leaders[0];
75209
- const offerings = cards.filter(card => card.type === 'offering' && this.isClassicGalleryCard(card)).slice(0, TUTORIAL_OFFERING_LIMIT);
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);
75210
75331
  const places = cards.filter(card => card.type === 'place');
75211
75332
  const characters = cards
75212
- .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)
75213
75338
  .slice(0, TUTORIAL_CHARACTER_LIMIT);
75214
- const tutorialDeck = [
75215
- ...offerings,
75216
- ...characters,
75217
- ...offerings,
75218
- ...characters.slice(0, 6),
75219
- ].slice(0, 28);
75339
+ const tutorialDeck = [...offerings, ...characters, ...offerings, ...characters.slice(0, 6)].slice(0, 28);
75220
75340
  const shuffledDeck = this.prepareTutorialOpeningDeck(this.shuffleCards(tutorialDeck, options.seed ?? TUTORIAL_SEED));
75221
75341
  const playerHand = shuffledDeck.slice(0, TUTORIAL_OPENING_HAND_SIZE).map(card => card.id);
75222
- const opponentHand = shuffledDeck.slice(TUTORIAL_OPENING_HAND_SIZE, TUTORIAL_OPENING_HAND_SIZE * 2).map(card => card.id);
75342
+ const opponentHand = shuffledDeck
75343
+ .slice(TUTORIAL_OPENING_HAND_SIZE, TUTORIAL_OPENING_HAND_SIZE * 2)
75344
+ .map(card => card.id);
75223
75345
  const sharedDeck = shuffledDeck.slice(TUTORIAL_OPENING_HAND_SIZE * 2).map(card => card.id);
75224
75346
  return {
75225
75347
  ...structuredClone(STARTING_GAME),
@@ -75289,7 +75411,7 @@ class ChristianityGameComponent {
75289
75411
  return cards.find(card => this.translatedCardText(card).name.toLowerCase().includes(namePart.toLowerCase()));
75290
75412
  }
75291
75413
  cardById(cardId) {
75292
- return cardId ? this.cardCatalog()[cardId] ?? STARTER_CARDS[cardId] : undefined;
75414
+ return cardId ? (this.cardCatalog()[cardId] ?? STARTER_CARDS[cardId]) : undefined;
75293
75415
  }
75294
75416
  tutorialLeaderCards(cards) {
75295
75417
  const leaders = cards
@@ -75391,7 +75513,7 @@ class ChristianityGameComponent {
75391
75513
  type: card.type,
75392
75514
  typeLabel: this.cardTypeLabel(card),
75393
75515
  cost: card.cost,
75394
- offeringValue: card.type === 'offering' ? card.offeringValue ?? card.life : undefined,
75516
+ offeringValue: card.type === 'offering' ? (card.offeringValue ?? card.life) : undefined,
75395
75517
  attack: card.type === 'character' ? card.attack : undefined,
75396
75518
  life: card.life,
75397
75519
  currentHp: card.life,
@@ -75529,8 +75651,17 @@ class ChristianityGameComponent {
75529
75651
  return this.cardTypeIconUrlForType('character');
75530
75652
  }
75531
75653
  cardTypeIconUrlForType(type) {
75532
- const normalized = type.toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '');
75533
- const icon = normalized === 'lider' ? 'leader' : normalized === 'personaje' ? 'character' : normalized === 'ofrenda' ? 'offering' : normalized;
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;
75534
75665
  return `assets/games/christianity/icons/${icon}.png`;
75535
75666
  }
75536
75667
  offeringPips(value) {
@@ -75543,7 +75674,7 @@ class ChristianityGameComponent {
75543
75674
  const cards = this.game().players[seat].battleZone;
75544
75675
  const positionedCards = new Map();
75545
75676
  cards.forEach((card, index) => {
75546
- const slot = Number.isInteger(card.slot) ? card.slot ?? index : index;
75677
+ const slot = Number.isInteger(card.slot) ? (card.slot ?? index) : index;
75547
75678
  if (slot >= 0 && slot < 4 && !positionedCards.has(slot)) {
75548
75679
  positionedCards.set(slot, card);
75549
75680
  }
@@ -75557,13 +75688,20 @@ class ChristianityGameComponent {
75557
75688
  return `christianity-battle-slot-${index}`;
75558
75689
  }
75559
75690
  dropTargetIds() {
75560
- 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
+ ];
75561
75696
  }
75562
75697
  ownAttackerDropSourceIds() {
75563
75698
  return ['christianity-player-battle-source'];
75564
75699
  }
75565
75700
  attackTargetDropIds() {
75566
- 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
+ ];
75567
75705
  }
75568
75706
  opponentTargetDropId(card) {
75569
75707
  return `christianity-opponent-target-${card.instanceId}`;
@@ -75699,7 +75837,11 @@ class ChristianityGameComponent {
75699
75837
  return `christianity-use-offering-${offering.owner}-${this.testIdPart(offering.instanceId)}`;
75700
75838
  }
75701
75839
  testIdPart(value) {
75702
- 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');
75703
75845
  }
75704
75846
  canPlaySelected() {
75705
75847
  const selected = this.selectedHandCard();
@@ -75815,10 +75957,10 @@ class ChristianityGameComponent {
75815
75957
  this.attackLeader();
75816
75958
  }
75817
75959
  isValidAttackerInstance(instanceId) {
75818
- return this.game().status === 'in_progress'
75819
- && this.isOwnTurn()
75820
- && !this.combatAnimation()
75821
- && this.player().battleZone.some(card => card.instanceId === instanceId && card.canAttack);
75960
+ return (this.game().status === 'in_progress' &&
75961
+ this.isOwnTurn() &&
75962
+ !this.combatAnimation() &&
75963
+ this.player().battleZone.some(card => card.instanceId === instanceId && card.canAttack));
75822
75964
  }
75823
75965
  selectHandCard(cardId) {
75824
75966
  const wasSelected = this.selectedHandCard() === cardId;
@@ -75831,9 +75973,7 @@ class ChristianityGameComponent {
75831
75973
  if (!cardId)
75832
75974
  return;
75833
75975
  const card = this.cardDefinition(cardId);
75834
- const resolvedSlotIndex = card.type === 'character' && slotIndex === undefined
75835
- ? this.firstAvailableBattleSlotIndex()
75836
- : slotIndex;
75976
+ const resolvedSlotIndex = card.type === 'character' && slotIndex === undefined ? this.firstAvailableBattleSlotIndex() : slotIndex;
75837
75977
  this.playCardFromHand(cardId, resolvedSlotIndex);
75838
75978
  }
75839
75979
  firstAvailableBattleSlotIndex() {
@@ -75914,7 +76054,12 @@ class ChristianityGameComponent {
75914
76054
  });
75915
76055
  return;
75916
76056
  }
75917
- 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
+ });
75918
76063
  this.selectedAttacker.set(null);
75919
76064
  this.combatResolutionTimer = window.setTimeout(() => {
75920
76065
  let damage = 0;
@@ -76009,7 +76154,7 @@ class ChristianityGameComponent {
76009
76154
  this.resetTransientTableState();
76010
76155
  const setup = this.createTutorialSetup();
76011
76156
  this.tutorialSetup.set(setup);
76012
- this.selectedTutorialLeaderId.set(setup.leaderPicker === 'P1' ? setup.availableLeaders[0] ?? null : null);
76157
+ this.selectedTutorialLeaderId.set(setup.leaderPicker === 'P1' ? (setup.availableLeaders[0] ?? null) : null);
76013
76158
  this.leaderChoiceSeconds.set(TUTORIAL_LEADER_CHOICE_SECONDS);
76014
76159
  this.tutorialPhase.set('coin');
76015
76160
  this.screen.set('table');
@@ -76071,7 +76216,7 @@ class ChristianityGameComponent {
76071
76216
  return;
76072
76217
  this.selectTutorialLeader(leaderId);
76073
76218
  this.inspectHandCard(leaderId);
76074
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'leader-choice' } : preview);
76219
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'leader-choice' } : preview));
76075
76220
  }
76076
76221
  toggleOptions() {
76077
76222
  this.showOptions.update(value => !value);
@@ -76428,7 +76573,7 @@ class ChristianityGameComponent {
76428
76573
  }
76429
76574
  }
76430
76575
  combatAnimationAttacker(animation) {
76431
- 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);
76432
76577
  }
76433
76578
  combatAnimationTargetLabel(animation) {
76434
76579
  if (animation.target === 'leader') {
@@ -76467,7 +76612,7 @@ class ChristianityGameComponent {
76467
76612
  const dx = to.x - from.x;
76468
76613
  const dy = to.y - from.y;
76469
76614
  const length = Math.max(12, Math.hypot(dx, dy));
76470
- const angle = Math.atan2(dy, dx) * 180 / Math.PI;
76615
+ const angle = (Math.atan2(dy, dx) * 180) / Math.PI;
76471
76616
  return {
76472
76617
  fromX: `${from.x}%`,
76473
76618
  fromY: `${from.y}%`,
@@ -76557,7 +76702,9 @@ class ChristianityGameComponent {
76557
76702
  const offeringId = hand.find(cardId => this.cardDefinition(cardId).type === 'offering' && canPlayCard(game, 'P2', this.cardDefinition(cardId)));
76558
76703
  if (offeringId) {
76559
76704
  const card = this.cardDefinition(offeringId);
76560
- 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
+ });
76561
76708
  if (result.played) {
76562
76709
  Object.assign(game, result.game);
76563
76710
  log.push(...result.log);
@@ -76628,7 +76775,9 @@ class ChristianityGameComponent {
76628
76775
  title: leader ? this.translatedCardText(leader).name : player.leaderName,
76629
76776
  eyebrow: this.t('christianityLeaderCard'),
76630
76777
  meta: `${player.leaderHp}/${player.leaderMaxHp}`,
76631
- 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'),
76632
76781
  cardId: player.leaderCardId,
76633
76782
  source: 'board',
76634
76783
  });
@@ -76703,7 +76852,7 @@ class ChristianityGameComponent {
76703
76852
  const preview = this.selectedPreview();
76704
76853
  if (!preview?.seat || !preview.offeringInstanceId)
76705
76854
  return null;
76706
- 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);
76707
76856
  }
76708
76857
  canUsePreviewAbility(index) {
76709
76858
  const offering = this.selectedPreviewOffering();
@@ -76724,7 +76873,9 @@ class ChristianityGameComponent {
76724
76873
  this.t('christianityDeckRuleShared'),
76725
76874
  this.t('christianityDeckRuleDraw'),
76726
76875
  this.t('christianityDeckRuleEmpty'),
76727
- nextCard ? `${this.t('christianityDeckNextCard')}: ${this.cardDisplayName(nextCard)}` : this.t('christianityDeckNoNextCard'),
76876
+ nextCard
76877
+ ? `${this.t('christianityDeckNextCard')}: ${this.cardDisplayName(nextCard)}`
76878
+ : this.t('christianityDeckNoNextCard'),
76728
76879
  ],
76729
76880
  });
76730
76881
  }
@@ -76739,15 +76890,12 @@ class ChristianityGameComponent {
76739
76890
  eyebrow: this.t('christianitySharedZone'),
76740
76891
  meta: this.t('christianityEmpty'),
76741
76892
  body: this.t('christianityPlaceRuleBody'),
76742
- facts: [
76743
- this.t('christianityPlaceSlotPlayable'),
76744
- this.t('christianityPlaceOnlyOne'),
76745
- ],
76893
+ facts: [this.t('christianityPlaceSlotPlayable'), this.t('christianityPlaceOnlyOne')],
76746
76894
  });
76747
76895
  }
76748
76896
  inspectPlaceCard(cardId) {
76749
76897
  this.inspectHandCard(cardId);
76750
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'place' } : preview);
76898
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'place' } : preview));
76751
76899
  }
76752
76900
  inspectHowToPlay() {
76753
76901
  this.selectedPreview.set(null);
@@ -76784,11 +76932,11 @@ class ChristianityGameComponent {
76784
76932
  }
76785
76933
  inspectGalleryCard(cardId) {
76786
76934
  this.inspectHandCard(cardId);
76787
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'gallery' } : preview);
76935
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'gallery' } : preview));
76788
76936
  }
76789
76937
  inspectHistoryCard(cardId) {
76790
76938
  this.inspectHandCard(cardId);
76791
- this.selectedPreview.update(preview => preview ? { ...preview, source: 'history' } : preview);
76939
+ this.selectedPreview.update(preview => (preview ? { ...preview, source: 'history' } : preview));
76792
76940
  }
76793
76941
  inspectCharacter(card) {
76794
76942
  this.selectedInfo.set(null);
@@ -76892,418 +77040,276 @@ class ChristianityGameComponent {
76892
77040
  </section>
76893
77041
  </section>
76894
77042
  } @else {
76895
- <section class="christianity-match-strip" data-testid="christianity-match-strip" [attr.aria-label]="t('christianityMatchScore')">
76896
- <article class="christianity-match-player">
76897
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
76898
- <div>
76899
- <strong>{{ player().displayName }}</strong>
76900
- <small>{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small>
76901
- </div>
76902
- </article>
76903
- <div class="christianity-scoreboard">
76904
- <span>{{ t('christianityMatch') }}</span>
76905
- <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
76906
- </div>
76907
- <article class="christianity-match-player christianity-match-player--opponent">
76908
- <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
76909
- <div>
76910
- <strong>{{ opponent().displayName }}</strong>
76911
- <small>{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{ opponent().leaderMaxHp }}</small>
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>
76912
77060
  </div>
76913
- </article>
76914
- </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>
76915
77073
 
76916
- <section
76917
- class="christianity-turn-banner"
76918
- data-testid="christianity-turn-banner"
76919
- [class.is-own-turn]="isOwnTurn()"
76920
- [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
76921
- [class.is-finished]="game().status === 'finished'"
76922
- aria-live="polite"
76923
- >
76924
- <div>
76925
- <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
76926
- <strong>{{ turnBannerTitle() }}</strong>
76927
- <p>{{ turnBannerBody() }}</p>
76928
- </div>
76929
- <div class="christianity-turn-actions">
76930
- <button
76931
- type="button"
76932
- class="christianity-end-turn"
76933
- data-testid="christianity-end-turn"
76934
- [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
76935
- (click)="endTurn()"
76936
- >
76937
- {{ t('christianityEndTurn') }}
76938
- </button>
76939
- <button type="button" class="christianity-options-button" data-testid="christianity-options-button" valLightRipple (click)="toggleOptions()">
76940
- {{ t('christianityOptions') }}
76941
- </button>
76942
- </div>
76943
- @if (showOptions()) {
76944
- <div class="christianity-options-menu" data-testid="christianity-options-menu">
76945
- <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
76946
- <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">{{ t('christianityResign') }}</button>
76947
- <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
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>
76948
77086
  </div>
76949
- }
76950
- </section>
76951
-
76952
- <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
76953
- <div class="christianity-hand-meter christianity-hand-meter--opponent">
76954
- <span>{{ t('christianityHand') }}</span>
76955
- <strong>{{ opponentHandCount() }}</strong>
76956
- </div>
76957
- <section class="christianity-hand-fan christianity-hand-fan--opponent" data-testid="christianity-opponent-hand" [attr.aria-label]="t('christianityOpponentHand')">
76958
- @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
77087
+ <div class="christianity-turn-actions">
77088
+ <button
77089
+ type="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>
76959
77097
  <button
76960
77098
  type="button"
76961
- class="christianity-hidden-card-button christianity-render-card-button"
77099
+ class="christianity-options-button"
77100
+ data-testid="christianity-options-button"
76962
77101
  valLightRipple
76963
- [style.transform]="fanTransform($index, opponentHandCount(), true)"
76964
- [attr.aria-label]="t('christianityHiddenCard')"
77102
+ (click)="toggleOptions()"
76965
77103
  >
76966
- <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
77104
+ {{ t('christianityOptions') }}
76967
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>
76968
77115
  }
76969
77116
  </section>
76970
- </section>
76971
77117
 
76972
- <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
76973
- @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>
76974
77123
  <section
76975
- class="christianity-setup-panel"
76976
- [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
76977
- [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
76978
- data-testid="christianity-setup-panel"
76979
- aria-live="polite"
77124
+ class="christianity-hand-fan christianity-hand-fan--opponent"
77125
+ data-testid="christianity-opponent-hand"
77126
+ [attr.aria-label]="t('christianityOpponentHand')"
76980
77127
  >
76981
- @if (tutorialPhase() === 'coin') {
76982
- <div
76983
- class="christianity-setup-coin"
76984
- [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
76985
- aria-hidden="true"
76986
- >
76987
- <span></span>
76988
- <span></span>
76989
- </div>
76990
- }
76991
- <div>
76992
- <span>{{ t('christianityMatchSetup') }}</span>
76993
- <h2>{{ setupTitle() }}</h2>
76994
- <p>{{ setupBody() }}</p>
76995
- </div>
76996
- @if (tutorialPhase() === 'coin') {
76997
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
76998
- {{ t('christianityContinue') }}
76999
- </button>
77000
- }
77001
- @if (tutorialPhase() === 'leader_pick') {
77002
- @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
77003
- <div class="christianity-leader-choice-meter" aria-live="polite">
77004
- <span>{{ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString()) }}</span>
77005
- <i [style.transform]="'scaleX(' + (leaderChoiceSeconds() / leaderChoiceTotalSeconds) + ')'"></i>
77006
- </div>
77007
- <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
77008
- @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
77009
- @let leader = cardDefinition(leaderId);
77010
- <article class="christianity-leader-choice-option" [class.is-selected]="selectedTutorialLeaderId() === leaderId">
77011
- <button
77012
- type="button"
77013
- class="christianity-leader-choice-card christianity-render-card-button"
77014
- valLightRipple
77015
- [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
77016
- (click)="selectTutorialLeader(leaderId)"
77017
- >
77018
- <val-christianity-card [card]="definitionToRenderCard(leader)" variant="baby"></val-christianity-card>
77019
- </button>
77020
- <div>
77021
- <span>{{ selectedTutorialLeaderId() === leaderId ? t('christianityLeaderSelected') : t('christianitySelectLeader') }}</span>
77022
- <button type="button" class="christianity-leader-choice-detail" valLightRipple (click)="inspectLeaderChoice(leaderId)">
77023
- {{ t('christianityViewDetails') }}
77024
- </button>
77025
- </div>
77026
- </article>
77027
- }
77028
- </div>
77029
- <button type="button" class="christianity-primary-action" data-testid="christianity-confirm-leader" valLightRipple (click)="confirmTutorialLeader()">
77030
- {{ t('christianityChooseThisLeader') }}
77031
- </button>
77032
- } @else {
77033
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77034
- {{ t('christianityContinue') }}
77035
- </button>
77036
- }
77037
- }
77038
- </section>
77039
- }
77040
- @if (game().status === 'finished') {
77041
- <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
77042
- <span>{{ t('christianityGameOver') }}</span>
77043
- <h2>{{ resultTitle() }}</h2>
77044
- <p>{{ resultBody() }}</p>
77045
- <div>
77046
- <button type="button" class="christianity-primary-action" data-testid="christianity-play-again" valLightRipple (click)="restartTutorial()">
77047
- {{ t('christianityPlayAgain') }}
77048
- </button>
77049
- <button type="button" class="christianity-secondary-action" valLightRipple (click)="returnToChristianityHome()">
77050
- {{ t('christianityBackToMenu') }}
77051
- </button>
77052
- </div>
77053
- </section>
77054
- }
77055
- <div class="christianity-impact-layer">
77056
- @if (combatTrail(); as trail) {
77057
- <span
77058
- class="christianity-combat-trail"
77059
- aria-hidden="true"
77060
- [class.is-leader-target]="trail.tone === 'leader'"
77061
- [class.is-character-target]="trail.tone === 'character'"
77062
- [style.--christianity-combat-trail-x]="trail.fromX"
77063
- [style.--christianity-combat-trail-y]="trail.fromY"
77064
- [style.--christianity-combat-trail-mid-x]="trail.midX"
77065
- [style.--christianity-combat-trail-mid-y]="trail.midY"
77066
- [style.--christianity-combat-trail-length]="trail.length"
77067
- [style.--christianity-combat-trail-angle]="trail.angle"
77068
- >
77069
- <span></span>
77070
- </span>
77071
- }
77072
- @for (impact of impactEvents(); track impact.id) {
77073
- <val-mini-game-score-pop [props]="impactPopProps(impact)" />
77074
- }
77075
- @if (combatCallout(); as callout) {
77076
- <val-mini-game-callout [props]="callout" />
77077
- }
77078
- </div>
77079
- <section class="christianity-player-side christianity-player-side--opponent">
77080
- <section class="christianity-player-rack christianity-player-rack--opponent">
77081
- <div class="christianity-offerings-zone">
77082
- <button type="button" class="christianity-offering-pile" data-testid="christianity-offerings-P2" [class.is-empty-zone]="!topOffering('P2')" valLightRipple (click)="openOfferings('P2')">
77083
- @if (offeringStack('P2').length) {
77084
- <span class="christianity-zone-card-stack" aria-hidden="true">
77085
- @for (offering of offeringStack('P2'); track offering.instanceId) {
77086
- <val-christianity-card
77087
- [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77088
- [variant]="offering.ready ? 'baby' : 'back'"
77089
- [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77090
- ></val-christianity-card>
77091
- }
77092
- </span>
77093
- } @else {
77094
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77095
- }
77096
- <span>{{ t('christianityOfferingReserve') }}</span>
77097
- <strong>{{ readyOfferings(opponent()) }}</strong>
77098
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
77099
- </button>
77100
- </div>
77101
- <div
77102
- class="christianity-attack-drop-target"
77103
- cdkDropList
77104
- id="christianity-opponent-leader-drop"
77105
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77106
- [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
77107
- [cdkDropListSortingDisabled]="true"
77108
- (cdkDropListDropped)="dropOnOpponentLeader($event)"
77109
- >
77128
+ @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
77110
77129
  <button
77111
77130
  type="button"
77112
- class="christianity-leader-card christianity-render-card-button"
77113
- data-testid="christianity-leader-P2"
77131
+ class="christianity-hidden-card-button christianity-render-card-button"
77114
77132
  valLightRipple
77115
- [class.is-target]="isOpponentLeaderTarget()"
77116
- [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
77117
- [class.is-combat-target]="isCombatLeaderTarget('P2')"
77118
- (click)="handleOpponentLeader()"
77133
+ [style.transform]="fanTransform($index, opponentHandCount(), true)"
77134
+ [attr.aria-label]="t('christianityHiddenCard')"
77119
77135
  >
77120
- <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
77136
+ <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
77121
77137
  </button>
77122
- </div>
77123
- <span class="christianity-rack-spacer" aria-hidden="true"></span>
77138
+ }
77124
77139
  </section>
77140
+ </section>
77125
77141
 
77126
- <section class="christianity-battle-zone christianity-battle-zone--opponent" data-testid="christianity-battle-P2" [attr.aria-label]="t('christianityOpponentBattle')">
77127
- @for (slot of battleSlots('P2'); track slot.trackId) {
77128
- @if (slot.card) {
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') {
77129
77167
  <button
77130
77168
  type="button"
77131
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77132
- [attr.data-testid]="battleCardTestId('P2', slot.card)"
77133
- cdkDropList
77134
- [id]="opponentTargetDropId(slot.card)"
77135
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
77136
- [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
77137
- [cdkDropListSortingDisabled]="true"
77169
+ class="christianity-primary-action"
77170
+ data-testid="christianity-continue-setup"
77138
77171
  valLightRipple
77139
- [class.is-target]="isOpponentCharacterTarget(slot.card)"
77140
- [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
77141
- (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
77142
- (click)="handleOpponentCharacter(slot.card)"
77172
+ (click)="continueAfterCoin()"
77143
77173
  >
77144
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77174
+ {{ t('christianityContinue') }}
77145
77175
  </button>
77146
- } @else {
77147
- <span class="christianity-slot"></span>
77148
77176
  }
77149
- }
77150
- </section>
77151
- </section>
77152
-
77153
- <section class="christianity-shared-zone" data-testid="christianity-shared-zone" [attr.aria-label]="t('christianitySharedZone')">
77154
- <button type="button" class="christianity-zone-pile christianity-zone-pile--history" data-testid="christianity-history-zone" [class.is-empty-zone]="!topHistoryCard()" [class.has-new-card]="historyPulse()" valLightRipple (click)="openHistory()">
77155
- @if (historyStack().length) {
77156
- <span class="christianity-zone-card-stack" aria-hidden="true">
77157
- @for (cardId of historyStack(); track cardId + $index) {
77158
- <val-christianity-card
77159
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
77160
- variant="baby"
77161
- [style.transform]="zoneStackTransform($index, historyStack().length)"
77162
- ></val-christianity-card>
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>
77163
77241
  }
77164
- </span>
77165
- } @else {
77166
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77167
- }
77168
- <span>{{ t('christianityHistory') }}</span>
77169
- <strong>{{ game().historyZone.length }}</strong>
77170
- </button>
77171
- <button type="button" class="christianity-zone-pile christianity-zone-pile--deck" data-testid="christianity-deck-zone" valLightRipple aria-live="polite" (click)="inspectDeck()">
77172
- <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
77173
- <i></i>
77174
- </span>
77175
- <span>{{ t('christianityDeck') }}</span>
77176
- <strong>{{ game().sharedDeck.length }}</strong>
77177
- </button>
77178
- <button
77179
- type="button"
77180
- class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
77181
- data-testid="christianity-place-zone"
77182
- cdkDropList
77183
- id="christianity-place-drop"
77184
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77185
- [cdkDropListEnterPredicate]="canDropOnPlace"
77186
- [cdkDropListSortingDisabled]="true"
77187
- [class.is-empty-zone]="!activePlaceCard()"
77188
- [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
77189
- valLightRipple
77190
- (cdkDropListDropped)="dropOnPlace($event)"
77191
- (click)="playSelectedPlaceOrInspect()"
77192
- >
77193
- @if (placeStack().length) {
77194
- @if (activePlaceCard(); as placeCard) {
77195
- <div class="christianity-place-backdrop" [style.background-image]="placeBackdropImage(placeCard)" aria-hidden="true"></div>
77196
77242
  }
77197
- <span class="christianity-zone-card-stack" aria-hidden="true">
77198
- @for (cardId of placeStack(); track cardId + $index) {
77199
- <val-christianity-card
77200
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
77201
- variant="baby"
77202
- [style.transform]="zoneStackTransform($index, placeStack().length)"
77203
- ></val-christianity-card>
77204
- }
77205
- </span>
77206
- } @else {
77207
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77208
- }
77209
- <span>{{ t('christianityPlace') }}</span>
77210
- <strong>{{ game().placeZone.length }}</strong>
77211
- @if (canPlaceSelectedPlace() || isDraggingPlace()) {
77212
- <em>{{ t('christianityPlayHere') }}</em>
77213
- }
77214
- </button>
77215
- </section>
77216
-
77217
- <section class="christianity-player-side">
77218
- <section
77219
- class="christianity-battle-zone"
77220
- data-testid="christianity-battle-P1"
77221
- cdkDropList
77222
- id="christianity-player-battle-source"
77223
- [cdkDropListConnectedTo]="attackTargetDropIds()"
77224
- [cdkDropListSortingDisabled]="true"
77225
- [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77226
- [attr.aria-label]="t('christianityYourBattle')"
77227
- >
77228
- @for (slot of battleSlots('P1'); track slot.trackId) {
77229
- @if (slot.card) {
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>
77230
77251
  <button
77231
77252
  type="button"
77232
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
77233
- [attr.data-testid]="battleCardTestId('P1', slot.card)"
77234
- cdkDrag
77235
- [cdkDragData]="slot.card.instanceId"
77236
- [cdkDragDisabled]="!canDragAttacker(slot.card)"
77253
+ class="christianity-primary-action"
77254
+ data-testid="christianity-play-again"
77237
77255
  valLightRipple
77238
- [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
77239
- [class.is-exhausted]="!slot.card.canAttack"
77240
- [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
77241
- [class.is-combat-attacker]="isCombatAttacker(slot.card)"
77242
- (cdkDragStarted)="startDraggingAttacker(slot.card)"
77243
- (cdkDragEnded)="endDraggingAttacker()"
77244
- (click)="selectAttacker(slot.card)"
77256
+ (click)="restartTutorial()"
77245
77257
  >
77246
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77247
- <ng-template cdkDragPreview>
77248
- <div class="christianity-card-drag-preview">
77249
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
77250
- </div>
77251
- </ng-template>
77252
- <ng-template cdkDragPlaceholder>
77253
- <div class="christianity-card-drag-placeholder"></div>
77254
- </ng-template>
77258
+ {{ t('christianityPlayAgain') }}
77255
77259
  </button>
77256
- } @else {
77257
77260
  <button
77258
77261
  type="button"
77259
- class="christianity-slot"
77260
- [attr.data-testid]="battleSlotTestId('P1', $index)"
77261
- cdkDropList
77262
- [id]="battleSlotDropId($index)"
77263
- [cdkDropListData]="$index"
77264
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77265
- [cdkDropListEnterPredicate]="canDropOnBattleSlot"
77266
- [cdkDropListSortingDisabled]="true"
77267
- [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
77262
+ class="christianity-secondary-action"
77268
77263
  valLightRipple
77269
- (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
77270
- (click)="playSelectedCard($index)"
77264
+ (click)="returnToChristianityHome()"
77271
77265
  >
77272
- @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
77273
- <span>{{ t('christianityPlayHere') }}</span>
77274
- }
77266
+ {{ t('christianityBackToMenu') }}
77275
77267
  </button>
77276
- }
77277
- }
77278
- </section>
77279
-
77280
- <section class="christianity-player-rack">
77281
- <div class="christianity-offerings-zone">
77282
- <div
77283
- class="christianity-offering-drop-target"
77284
- cdkDropList
77285
- id="christianity-offerings-drop"
77286
- [cdkDropListConnectedTo]="['christianity-hand-source']"
77287
- [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
77288
- [cdkDropListEnterPredicate]="canDropOnOfferings"
77289
- [cdkDropListSortingDisabled]="true"
77290
- (cdkDropListDropped)="dropOnOfferings($event)"
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"
77291
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">
77292
77298
  <button
77293
77299
  type="button"
77294
77300
  class="christianity-offering-pile"
77295
- data-testid="christianity-offerings-P1"
77296
- [class.is-empty-zone]="!topOffering('P1')"
77301
+ data-testid="christianity-offerings-P2"
77302
+ [class.is-empty-zone]="!topOffering('P2')"
77297
77303
  valLightRipple
77298
- (click)="handleOfferingPile('P1')"
77304
+ (click)="openOfferings('P2')"
77299
77305
  >
77300
- @if (offeringStack('P1').length) {
77306
+ @if (offeringStack('P2').length) {
77301
77307
  <span class="christianity-zone-card-stack" aria-hidden="true">
77302
- @for (offering of offeringStack('P1'); track offering.instanceId) {
77308
+ @for (offering of offeringStack('P2'); track offering.instanceId) {
77303
77309
  <val-christianity-card
77304
77310
  [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77305
77311
  [variant]="offering.ready ? 'baby' : 'back'"
77306
- [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
77312
+ [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77307
77313
  ></val-christianity-card>
77308
77314
  }
77309
77315
  </span>
@@ -77311,89 +77317,358 @@ class ChristianityGameComponent {
77311
77317
  <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77312
77318
  }
77313
77319
  <span>{{ t('christianityOfferingReserve') }}</span>
77314
- <strong>{{ readyOfferings(player()) }}</strong>
77315
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
77320
+ <strong>{{ readyOfferings(opponent()) }}</strong>
77321
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
77316
77322
  </button>
77317
77323
  </div>
77318
- </div>
77319
- <button type="button" class="christianity-leader-card christianity-render-card-button" data-testid="christianity-leader-P1" valLightRipple (click)="inspectLeader('P1')">
77320
- <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
77321
- </button>
77322
- <div class="christianity-player-command">
77323
- <div class="christianity-player-badge">
77324
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
77325
- <strong>{{ player().displayName }}</strong>
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>
77326
77345
  </div>
77327
- </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>
77328
77381
  </section>
77329
- </section>
77330
- </section>
77331
77382
 
77332
- <section class="christianity-hand-shelf">
77333
- <div class="christianity-hand-meter">
77334
- <span>{{ t('christianityHand') }}</span>
77335
- <strong>{{ player().hand?.length ?? 0 }}</strong>
77336
- </div>
77337
- <section
77338
- class="christianity-hand-fan"
77339
- data-testid="christianity-hand-P1"
77340
- cdkDropList
77341
- id="christianity-hand-source"
77342
- [cdkDropListConnectedTo]="dropTargetIds()"
77343
- [attr.aria-label]="t('christianityYourHand')"
77344
- (pointerleave)="clearHoveredHandIndex()"
77345
- >
77346
- @for (cardId of player().hand ?? []; track cardId + $index) {
77347
- @let card = cardDefinition(cardId);
77348
- <div
77349
- class="christianity-hand-card-wrap"
77350
- [class.is-selected]="selectedHandCard() === cardId"
77351
- [class.is-dragging]="draggingCardId() === cardId"
77352
- [style.z-index]="handCardZIndex($index)"
77353
- [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
77354
- (pointerenter)="setHoveredHandIndex($index)"
77355
- (pointerdown)="setHoveredHandIndex($index)"
77356
- (pointerup)="releaseHoveredHand($event)"
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()"
77357
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')"
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>
77358
77578
  <button
77359
77579
  type="button"
77360
- class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
77361
- [attr.data-testid]="handCardTestId(cardId, $index)"
77362
- cdkDrag
77580
+ class="christianity-leader-card christianity-render-card-button"
77581
+ data-testid="christianity-leader-P1"
77363
77582
  valLightRipple
77364
- [cdkDragData]="cardId"
77365
- [cdkDragDisabled]="!canPlay(card)"
77366
- [attr.aria-label]="handCardActionLabel(card)"
77367
- [class.is-selected]="selectedHandCard() === cardId"
77368
- [class.is-disabled]="!canPlay(card)"
77369
- (cdkDragStarted)="startDraggingCard(cardId)"
77370
- (cdkDragEnded)="endDraggingCard()"
77371
- (click)="selectHandCard(cardId)"
77583
+ (click)="inspectLeader('P1')"
77372
77584
  >
77373
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77374
- <ng-template cdkDragPreview>
77375
- <div class="christianity-card-drag-preview">
77376
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77377
- </div>
77378
- </ng-template>
77379
- <ng-template cdkDragPlaceholder>
77380
- <div class="christianity-card-drag-placeholder"></div>
77381
- </ng-template>
77585
+ <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
77382
77586
  </button>
77383
- <button
77384
- type="button"
77385
- class="christianity-card-eye"
77386
- [attr.aria-label]="t('christianityPreviewCard')"
77387
- (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
77388
- ></button>
77389
- </div>
77390
- }
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>
77391
77662
  </section>
77392
- </section>
77393
77663
  }
77394
77664
 
77395
77665
  @if (selectedPreview()) {
77396
- <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>
77397
77672
  <aside
77398
77673
  class="christianity-floating-card-shell"
77399
77674
  [class.is-history-preview]="canNavigatePreview()"
@@ -77433,7 +77708,11 @@ class ChristianityGameComponent {
77433
77708
  </button>
77434
77709
  }
77435
77710
  @if (selectedPreviewNeighborRenderCard(-1); as previousCard) {
77436
- <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>
77437
77716
  }
77438
77717
  <div
77439
77718
  class="christianity-floating-card"
@@ -77452,7 +77731,11 @@ class ChristianityGameComponent {
77452
77731
  }
77453
77732
  </div>
77454
77733
  @if (selectedPreviewNeighborRenderCard(1); as nextCard) {
77455
- <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>
77456
77739
  }
77457
77740
  </div>
77458
77741
  <div class="christianity-preview-side">
@@ -77536,7 +77819,12 @@ class ChristianityGameComponent {
77536
77819
  }
77537
77820
 
77538
77821
  @if (selectedInfo(); as info) {
77539
- <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>
77540
77828
  <aside class="christianity-info-panel" aria-live="polite">
77541
77829
  <button type="button" class="christianity-dialog-close christianity-info-close" (click)="closeInfo()">
77542
77830
  {{ t('close') }}
@@ -77561,14 +77849,24 @@ class ChristianityGameComponent {
77561
77849
  @if (screen() === 'table') {
77562
77850
  <section class="christianity-actions" data-testid="christianity-actions" aria-live="polite">
77563
77851
  <p class="christianity-sr-only" data-testid="christianity-status">{{ statusText() }}</p>
77564
- <button type="button" data-testid="christianity-primary-action" [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()" (click)="playSelectedCard()">
77852
+ <button
77853
+ type="button"
77854
+ data-testid="christianity-primary-action"
77855
+ [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()"
77856
+ (click)="playSelectedCard()"
77857
+ >
77565
77858
  {{ selectedActionLabel() }}
77566
77859
  </button>
77567
77860
  </section>
77568
77861
  }
77569
77862
 
77570
77863
  @if (showHistory()) {
77571
- <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>
77572
77870
  <section class="christianity-history-drawer" data-testid="christianity-history-drawer">
77573
77871
  <header>
77574
77872
  <h2>{{ t('christianityHistory') }}</h2>
@@ -77583,7 +77881,13 @@ class ChristianityGameComponent {
77583
77881
  <div class="christianity-history-grid" data-testid="christianity-history-grid">
77584
77882
  @for (cardId of game().historyZone; track cardId + $index) {
77585
77883
  @let card = cardDefinition(cardId);
77586
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="historyCardTestId(cardId, $index)" valLightRipple (click)="inspectHistoryCard(cardId)">
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
+ >
77587
77891
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77588
77892
  </button>
77589
77893
  }
@@ -77595,7 +77899,12 @@ class ChristianityGameComponent {
77595
77899
  }
77596
77900
 
77597
77901
  @if (showPlace()) {
77598
- <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>
77599
77908
  <section class="christianity-history-drawer christianity-place-drawer" data-testid="christianity-place-drawer">
77600
77909
  <header>
77601
77910
  <h2>{{ t('christianityPlace') }}</h2>
@@ -77608,8 +77917,17 @@ class ChristianityGameComponent {
77608
77917
  </header>
77609
77918
  <div class="christianity-history-grid" data-testid="christianity-place-grid">
77610
77919
  @for (cardId of game().placeZone; track cardId + $index) {
77611
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="'christianity-place-card-' + $index" valLightRipple (click)="inspectPlaceCard(cardId)">
77612
- <val-christianity-card [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })" variant="baby"></val-christianity-card>
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>
77613
77931
  </button>
77614
77932
  }
77615
77933
  </div>
@@ -77617,8 +77935,16 @@ class ChristianityGameComponent {
77617
77935
  }
77618
77936
 
77619
77937
  @if (showGallery()) {
77620
- <button type="button" class="christianity-history-backdrop christianity-gallery-backdrop" [attr.aria-label]="t('christianityCloseGallery')" (click)="closeGallery()"></button>
77621
- <section class="christianity-history-drawer christianity-gallery-drawer" data-testid="christianity-gallery-drawer">
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
+ >
77622
77948
  <header>
77623
77949
  <div>
77624
77950
  <h2>{{ t('christianityGallery') }}</h2>
@@ -77634,7 +77960,12 @@ class ChristianityGameComponent {
77634
77960
  <div class="christianity-gallery-controls">
77635
77961
  <label>
77636
77962
  <span>{{ t('christianityGallerySearch') }}</span>
77637
- <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
+ />
77638
77969
  </label>
77639
77970
  <label>
77640
77971
  <span>{{ t('christianityGalleryFilter') }}</span>
@@ -77653,7 +77984,12 @@ class ChristianityGameComponent {
77653
77984
  </div>
77654
77985
  <div class="christianity-gallery-grid">
77655
77986
  @for (card of galleryCards(); track card.id) {
77656
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" valLightRipple (click)="inspectGalleryCard(card.id)">
77987
+ <button
77988
+ type="button"
77989
+ class="christianity-gallery-card-button christianity-render-card-button"
77990
+ valLightRipple
77991
+ (click)="inspectGalleryCard(card.id)"
77992
+ >
77657
77993
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
77658
77994
  </button>
77659
77995
  } @empty {
@@ -77665,12 +78001,23 @@ class ChristianityGameComponent {
77665
78001
 
77666
78002
  @if (offeringsSeat()) {
77667
78003
  @let offeringPlayer = game().players[offeringsSeat()!];
77668
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseOfferings')" (click)="closeOfferings()"></button>
77669
- <section class="christianity-history-drawer christianity-offerings-drawer" [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)">
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
+ >
77670
78014
  <header>
77671
78015
  <h2>{{ t('christianityOfferings') }}</h2>
77672
78016
  <div class="christianity-dialog-actions">
77673
- <span>{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} · {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span>
78017
+ <span
78018
+ >{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} ·
78019
+ {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span
78020
+ >
77674
78021
  <button type="button" class="christianity-dialog-close" (click)="closeOfferings()">
77675
78022
  {{ t('close') }}
77676
78023
  </button>
@@ -77680,14 +78027,25 @@ class ChristianityGameComponent {
77680
78027
  <span class="christianity-rule-note-icon" aria-hidden="true">i</span>
77681
78028
  <p>{{ t('christianityOfferingsRuleBody') }}</p>
77682
78029
  </div>
77683
- <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
77684
- @for (offering of offeringPlayer.offerings; track offering.instanceId) {
77685
- <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
77686
- <button type="button" class="christianity-offering-card christianity-render-card-button" [attr.data-testid]="offeringCardTestId(offering)" valLightRipple (click)="inspectOffering(offering)">
77687
- <val-christianity-card [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()" [variant]="offering.ready ? 'baby' : 'back'"></val-christianity-card>
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>
77688
78044
  </button>
77689
78045
  <div>
77690
- <strong>{{ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted') }}</strong>
78046
+ <strong>{{
78047
+ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted')
78048
+ }}</strong>
77691
78049
  <p>{{ offeringAbilityText(offering) }}</p>
77692
78050
  @if (offering.abilityName) {
77693
78051
  <button
@@ -77796,418 +78154,276 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
77796
78154
  </section>
77797
78155
  </section>
77798
78156
  } @else {
77799
- <section class="christianity-match-strip" data-testid="christianity-match-strip" [attr.aria-label]="t('christianityMatchScore')">
77800
- <article class="christianity-match-player">
77801
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
77802
- <div>
77803
- <strong>{{ player().displayName }}</strong>
77804
- <small>{{ playerLeaderDisplayName(player()) }} · {{ player().leaderHp }}/{{ player().leaderMaxHp }}</small>
77805
- </div>
77806
- </article>
77807
- <div class="christianity-scoreboard">
77808
- <span>{{ t('christianityMatch') }}</span>
77809
- <strong>{{ game().matchScore.P1 }}-{{ game().matchScore.P2 }}</strong>
77810
- </div>
77811
- <article class="christianity-match-player christianity-match-player--opponent">
77812
- <span class="christianity-avatar" [style.background]="opponent().avatarColor">{{ opponent().avatar }}</span>
77813
- <div>
77814
- <strong>{{ opponent().displayName }}</strong>
77815
- <small>{{ playerLeaderDisplayName(opponent()) }} · {{ opponent().leaderHp }}/{{ opponent().leaderMaxHp }}</small>
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>
77816
78174
  </div>
77817
- </article>
77818
- </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>
77819
78187
 
77820
- <section
77821
- class="christianity-turn-banner"
77822
- data-testid="christianity-turn-banner"
77823
- [class.is-own-turn]="isOwnTurn()"
77824
- [class.is-rival-turn]="!isOwnTurn() && game().status === 'in_progress'"
77825
- [class.is-finished]="game().status === 'finished'"
77826
- aria-live="polite"
77827
- >
77828
- <div>
77829
- <span>{{ t('christianityTurn') }} {{ game().turnCount }}</span>
77830
- <strong>{{ turnBannerTitle() }}</strong>
77831
- <p>{{ turnBannerBody() }}</p>
77832
- </div>
77833
- <div class="christianity-turn-actions">
77834
- <button
77835
- type="button"
77836
- class="christianity-end-turn"
77837
- data-testid="christianity-end-turn"
77838
- [disabled]="!isOwnTurn() || tutorialPhase() !== 'in_progress' || !!combatAnimation()"
77839
- (click)="endTurn()"
77840
- >
77841
- {{ t('christianityEndTurn') }}
77842
- </button>
77843
- <button type="button" class="christianity-options-button" data-testid="christianity-options-button" valLightRipple (click)="toggleOptions()">
77844
- {{ t('christianityOptions') }}
77845
- </button>
77846
- </div>
77847
- @if (showOptions()) {
77848
- <div class="christianity-options-menu" data-testid="christianity-options-menu">
77849
- <button type="button" (click)="restartTutorial()">{{ t('christianityRestart') }}</button>
77850
- <button type="button" [disabled]="game().status === 'finished'" (click)="resignTutorial()">{{ t('christianityResign') }}</button>
77851
- <button type="button" (click)="returnToChristianityHome()">{{ t('christianityBackToMenu') }}</button>
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>
77852
78200
  </div>
77853
- }
77854
- </section>
77855
-
77856
- <section class="christianity-hand-shelf christianity-hand-shelf--opponent">
77857
- <div class="christianity-hand-meter christianity-hand-meter--opponent">
77858
- <span>{{ t('christianityHand') }}</span>
77859
- <strong>{{ opponentHandCount() }}</strong>
77860
- </div>
77861
- <section class="christianity-hand-fan christianity-hand-fan--opponent" data-testid="christianity-opponent-hand" [attr.aria-label]="t('christianityOpponentHand')">
77862
- @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
78201
+ <div class="christianity-turn-actions">
77863
78202
  <button
77864
78203
  type="button"
77865
- 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"
77866
78215
  valLightRipple
77867
- [style.transform]="fanTransform($index, opponentHandCount(), true)"
77868
- [attr.aria-label]="t('christianityHiddenCard')"
78216
+ (click)="toggleOptions()"
77869
78217
  >
77870
- <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
78218
+ {{ t('christianityOptions') }}
77871
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>
77872
78229
  }
77873
78230
  </section>
77874
- </section>
77875
78231
 
77876
- <section class="christianity-table" data-testid="christianity-table" [attr.aria-label]="t('christianityTable')">
77877
- @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>
77878
78237
  <section
77879
- class="christianity-setup-panel"
77880
- [class.christianity-setup-panel--coin]="tutorialPhase() === 'coin'"
77881
- [class.christianity-setup-panel--leader-pick]="tutorialPhase() === 'leader_pick'"
77882
- data-testid="christianity-setup-panel"
77883
- aria-live="polite"
78238
+ class="christianity-hand-fan christianity-hand-fan--opponent"
78239
+ data-testid="christianity-opponent-hand"
78240
+ [attr.aria-label]="t('christianityOpponentHand')"
77884
78241
  >
77885
- @if (tutorialPhase() === 'coin') {
77886
- <div
77887
- class="christianity-setup-coin"
77888
- [class.is-rival-won]="tutorialSetup()?.coinWinner === 'P2'"
77889
- aria-hidden="true"
77890
- >
77891
- <span></span>
77892
- <span></span>
77893
- </div>
77894
- }
77895
- <div>
77896
- <span>{{ t('christianityMatchSetup') }}</span>
77897
- <h2>{{ setupTitle() }}</h2>
77898
- <p>{{ setupBody() }}</p>
77899
- </div>
77900
- @if (tutorialPhase() === 'coin') {
77901
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77902
- {{ t('christianityContinue') }}
77903
- </button>
77904
- }
77905
- @if (tutorialPhase() === 'leader_pick') {
77906
- @if (tutorialSetup()?.leaderPicker === 'P1' && (tutorialSetup()?.availableLeaders?.length ?? 0) > 1) {
77907
- <div class="christianity-leader-choice-meter" aria-live="polite">
77908
- <span>{{ t('christianityLeaderChoiceTimer').replace('{seconds}', leaderChoiceSeconds().toString()) }}</span>
77909
- <i [style.transform]="'scaleX(' + (leaderChoiceSeconds() / leaderChoiceTotalSeconds) + ')'"></i>
77910
- </div>
77911
- <div class="christianity-leader-choice-grid" data-testid="christianity-leader-choice-grid">
77912
- @for (leaderId of tutorialSetup()?.availableLeaders ?? []; track leaderId) {
77913
- @let leader = cardDefinition(leaderId);
77914
- <article class="christianity-leader-choice-option" [class.is-selected]="selectedTutorialLeaderId() === leaderId">
77915
- <button
77916
- type="button"
77917
- class="christianity-leader-choice-card christianity-render-card-button"
77918
- valLightRipple
77919
- [attr.aria-pressed]="selectedTutorialLeaderId() === leaderId"
77920
- (click)="selectTutorialLeader(leaderId)"
77921
- >
77922
- <val-christianity-card [card]="definitionToRenderCard(leader)" variant="baby"></val-christianity-card>
77923
- </button>
77924
- <div>
77925
- <span>{{ selectedTutorialLeaderId() === leaderId ? t('christianityLeaderSelected') : t('christianitySelectLeader') }}</span>
77926
- <button type="button" class="christianity-leader-choice-detail" valLightRipple (click)="inspectLeaderChoice(leaderId)">
77927
- {{ t('christianityViewDetails') }}
77928
- </button>
77929
- </div>
77930
- </article>
77931
- }
77932
- </div>
77933
- <button type="button" class="christianity-primary-action" data-testid="christianity-confirm-leader" valLightRipple (click)="confirmTutorialLeader()">
77934
- {{ t('christianityChooseThisLeader') }}
77935
- </button>
77936
- } @else {
77937
- <button type="button" class="christianity-primary-action" data-testid="christianity-continue-setup" valLightRipple (click)="continueAfterCoin()">
77938
- {{ t('christianityContinue') }}
77939
- </button>
77940
- }
77941
- }
77942
- </section>
77943
- }
77944
- @if (game().status === 'finished') {
77945
- <section class="christianity-result-panel" data-testid="christianity-result-panel" aria-live="polite">
77946
- <span>{{ t('christianityGameOver') }}</span>
77947
- <h2>{{ resultTitle() }}</h2>
77948
- <p>{{ resultBody() }}</p>
77949
- <div>
77950
- <button type="button" class="christianity-primary-action" data-testid="christianity-play-again" valLightRipple (click)="restartTutorial()">
77951
- {{ t('christianityPlayAgain') }}
77952
- </button>
77953
- <button type="button" class="christianity-secondary-action" valLightRipple (click)="returnToChristianityHome()">
77954
- {{ t('christianityBackToMenu') }}
77955
- </button>
77956
- </div>
77957
- </section>
77958
- }
77959
- <div class="christianity-impact-layer">
77960
- @if (combatTrail(); as trail) {
77961
- <span
77962
- class="christianity-combat-trail"
77963
- aria-hidden="true"
77964
- [class.is-leader-target]="trail.tone === 'leader'"
77965
- [class.is-character-target]="trail.tone === 'character'"
77966
- [style.--christianity-combat-trail-x]="trail.fromX"
77967
- [style.--christianity-combat-trail-y]="trail.fromY"
77968
- [style.--christianity-combat-trail-mid-x]="trail.midX"
77969
- [style.--christianity-combat-trail-mid-y]="trail.midY"
77970
- [style.--christianity-combat-trail-length]="trail.length"
77971
- [style.--christianity-combat-trail-angle]="trail.angle"
77972
- >
77973
- <span></span>
77974
- </span>
77975
- }
77976
- @for (impact of impactEvents(); track impact.id) {
77977
- <val-mini-game-score-pop [props]="impactPopProps(impact)" />
77978
- }
77979
- @if (combatCallout(); as callout) {
77980
- <val-mini-game-callout [props]="callout" />
77981
- }
77982
- </div>
77983
- <section class="christianity-player-side christianity-player-side--opponent">
77984
- <section class="christianity-player-rack christianity-player-rack--opponent">
77985
- <div class="christianity-offerings-zone">
77986
- <button type="button" class="christianity-offering-pile" data-testid="christianity-offerings-P2" [class.is-empty-zone]="!topOffering('P2')" valLightRipple (click)="openOfferings('P2')">
77987
- @if (offeringStack('P2').length) {
77988
- <span class="christianity-zone-card-stack" aria-hidden="true">
77989
- @for (offering of offeringStack('P2'); track offering.instanceId) {
77990
- <val-christianity-card
77991
- [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
77992
- [variant]="offering.ready ? 'baby' : 'back'"
77993
- [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
77994
- ></val-christianity-card>
77995
- }
77996
- </span>
77997
- } @else {
77998
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
77999
- }
78000
- <span>{{ t('christianityOfferingReserve') }}</span>
78001
- <strong>{{ readyOfferings(opponent()) }}</strong>
78002
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
78003
- </button>
78004
- </div>
78005
- <div
78006
- class="christianity-attack-drop-target"
78007
- cdkDropList
78008
- id="christianity-opponent-leader-drop"
78009
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78010
- [cdkDropListEnterPredicate]="canDropOnOpponentLeader"
78011
- [cdkDropListSortingDisabled]="true"
78012
- (cdkDropListDropped)="dropOnOpponentLeader($event)"
78013
- >
78242
+ @for (_ of hiddenHandCards(opponentHandCount()); track $index) {
78014
78243
  <button
78015
78244
  type="button"
78016
- class="christianity-leader-card christianity-render-card-button"
78017
- data-testid="christianity-leader-P2"
78245
+ class="christianity-hidden-card-button christianity-render-card-button"
78018
78246
  valLightRipple
78019
- [class.is-target]="isOpponentLeaderTarget()"
78020
- [class.is-protected-target]="selectedAttacker() && hasOpponentGuards()"
78021
- [class.is-combat-target]="isCombatLeaderTarget('P2')"
78022
- (click)="handleOpponentLeader()"
78247
+ [style.transform]="fanTransform($index, opponentHandCount(), true)"
78248
+ [attr.aria-label]="t('christianityHiddenCard')"
78023
78249
  >
78024
- <val-christianity-card [card]="leaderRenderCard(opponent())" variant="board"></val-christianity-card>
78250
+ <val-christianity-card [card]="hiddenCardRenderCard()" variant="back"></val-christianity-card>
78025
78251
  </button>
78026
- </div>
78027
- <span class="christianity-rack-spacer" aria-hidden="true"></span>
78252
+ }
78028
78253
  </section>
78254
+ </section>
78029
78255
 
78030
- <section class="christianity-battle-zone christianity-battle-zone--opponent" data-testid="christianity-battle-P2" [attr.aria-label]="t('christianityOpponentBattle')">
78031
- @for (slot of battleSlots('P2'); track slot.trackId) {
78032
- @if (slot.card) {
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') {
78033
78281
  <button
78034
78282
  type="button"
78035
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78036
- [attr.data-testid]="battleCardTestId('P2', slot.card)"
78037
- cdkDropList
78038
- [id]="opponentTargetDropId(slot.card)"
78039
- [cdkDropListConnectedTo]="ownAttackerDropSourceIds()"
78040
- [cdkDropListEnterPredicate]="canDropOnOpponentCharacter"
78041
- [cdkDropListSortingDisabled]="true"
78283
+ class="christianity-primary-action"
78284
+ data-testid="christianity-continue-setup"
78042
78285
  valLightRipple
78043
- [class.is-target]="isOpponentCharacterTarget(slot.card)"
78044
- [class.is-combat-target]="isCombatCharacterTarget(slot.card)"
78045
- (cdkDropListDropped)="dropOnOpponentCharacter($event, slot.card)"
78046
- (click)="handleOpponentCharacter(slot.card)"
78286
+ (click)="continueAfterCoin()"
78047
78287
  >
78048
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78288
+ {{ t('christianityContinue') }}
78049
78289
  </button>
78050
- } @else {
78051
- <span class="christianity-slot"></span>
78052
78290
  }
78053
- }
78054
- </section>
78055
- </section>
78056
-
78057
- <section class="christianity-shared-zone" data-testid="christianity-shared-zone" [attr.aria-label]="t('christianitySharedZone')">
78058
- <button type="button" class="christianity-zone-pile christianity-zone-pile--history" data-testid="christianity-history-zone" [class.is-empty-zone]="!topHistoryCard()" [class.has-new-card]="historyPulse()" valLightRipple (click)="openHistory()">
78059
- @if (historyStack().length) {
78060
- <span class="christianity-zone-card-stack" aria-hidden="true">
78061
- @for (cardId of historyStack(); track cardId + $index) {
78062
- <val-christianity-card
78063
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityHistory') })"
78064
- variant="baby"
78065
- [style.transform]="zoneStackTransform($index, historyStack().length)"
78066
- ></val-christianity-card>
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>
78067
78355
  }
78068
- </span>
78069
- } @else {
78070
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78071
- }
78072
- <span>{{ t('christianityHistory') }}</span>
78073
- <strong>{{ game().historyZone.length }}</strong>
78074
- </button>
78075
- <button type="button" class="christianity-zone-pile christianity-zone-pile--deck" data-testid="christianity-deck-zone" valLightRipple aria-live="polite" (click)="inspectDeck()">
78076
- <span class="christianity-pile-card-hint christianity-pile-card-hint--deck" aria-hidden="true">
78077
- <i></i>
78078
- </span>
78079
- <span>{{ t('christianityDeck') }}</span>
78080
- <strong>{{ game().sharedDeck.length }}</strong>
78081
- </button>
78082
- <button
78083
- type="button"
78084
- class="christianity-zone-card christianity-zone-card--place christianity-render-card-button"
78085
- data-testid="christianity-place-zone"
78086
- cdkDropList
78087
- id="christianity-place-drop"
78088
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78089
- [cdkDropListEnterPredicate]="canDropOnPlace"
78090
- [cdkDropListSortingDisabled]="true"
78091
- [class.is-empty-zone]="!activePlaceCard()"
78092
- [class.is-play-target]="canPlaceSelectedPlace() || isDraggingPlace()"
78093
- valLightRipple
78094
- (cdkDropListDropped)="dropOnPlace($event)"
78095
- (click)="playSelectedPlaceOrInspect()"
78096
- >
78097
- @if (placeStack().length) {
78098
- @if (activePlaceCard(); as placeCard) {
78099
- <div class="christianity-place-backdrop" [style.background-image]="placeBackdropImage(placeCard)" aria-hidden="true"></div>
78100
78356
  }
78101
- <span class="christianity-zone-card-stack" aria-hidden="true">
78102
- @for (cardId of placeStack(); track cardId + $index) {
78103
- <val-christianity-card
78104
- [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })"
78105
- variant="baby"
78106
- [style.transform]="zoneStackTransform($index, placeStack().length)"
78107
- ></val-christianity-card>
78108
- }
78109
- </span>
78110
- } @else {
78111
- <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78112
- }
78113
- <span>{{ t('christianityPlace') }}</span>
78114
- <strong>{{ game().placeZone.length }}</strong>
78115
- @if (canPlaceSelectedPlace() || isDraggingPlace()) {
78116
- <em>{{ t('christianityPlayHere') }}</em>
78117
- }
78118
- </button>
78119
- </section>
78120
-
78121
- <section class="christianity-player-side">
78122
- <section
78123
- class="christianity-battle-zone"
78124
- data-testid="christianity-battle-P1"
78125
- cdkDropList
78126
- id="christianity-player-battle-source"
78127
- [cdkDropListConnectedTo]="attackTargetDropIds()"
78128
- [cdkDropListSortingDisabled]="true"
78129
- [class.is-placing-card]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78130
- [attr.aria-label]="t('christianityYourBattle')"
78131
- >
78132
- @for (slot of battleSlots('P1'); track slot.trackId) {
78133
- @if (slot.card) {
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>
78134
78365
  <button
78135
78366
  type="button"
78136
- class="christianity-card-preview-card christianity-card-preview-card--battle christianity-render-card-button"
78137
- [attr.data-testid]="battleCardTestId('P1', slot.card)"
78138
- cdkDrag
78139
- [cdkDragData]="slot.card.instanceId"
78140
- [cdkDragDisabled]="!canDragAttacker(slot.card)"
78367
+ class="christianity-primary-action"
78368
+ data-testid="christianity-play-again"
78141
78369
  valLightRipple
78142
- [class.is-selected]="selectedAttacker()?.instanceId === slot.card.instanceId"
78143
- [class.is-exhausted]="!slot.card.canAttack"
78144
- [class.is-ready-attacker]="isOwnTurn() && slot.card.canAttack"
78145
- [class.is-combat-attacker]="isCombatAttacker(slot.card)"
78146
- (cdkDragStarted)="startDraggingAttacker(slot.card)"
78147
- (cdkDragEnded)="endDraggingAttacker()"
78148
- (click)="selectAttacker(slot.card)"
78370
+ (click)="restartTutorial()"
78149
78371
  >
78150
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78151
- <ng-template cdkDragPreview>
78152
- <div class="christianity-card-drag-preview">
78153
- <val-christianity-card [card]="characterRenderCard(slot.card)" variant="board"></val-christianity-card>
78154
- </div>
78155
- </ng-template>
78156
- <ng-template cdkDragPlaceholder>
78157
- <div class="christianity-card-drag-placeholder"></div>
78158
- </ng-template>
78372
+ {{ t('christianityPlayAgain') }}
78159
78373
  </button>
78160
- } @else {
78161
78374
  <button
78162
78375
  type="button"
78163
- class="christianity-slot"
78164
- [attr.data-testid]="battleSlotTestId('P1', $index)"
78165
- cdkDropList
78166
- [id]="battleSlotDropId($index)"
78167
- [cdkDropListData]="$index"
78168
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78169
- [cdkDropListEnterPredicate]="canDropOnBattleSlot"
78170
- [cdkDropListSortingDisabled]="true"
78171
- [class.christianity-slot--play-target]="canPlaceSelectedCharacter() || isDraggingCharacter()"
78376
+ class="christianity-secondary-action"
78172
78377
  valLightRipple
78173
- (cdkDropListDropped)="dropOnBattleSlot($event, $index)"
78174
- (click)="playSelectedCard($index)"
78378
+ (click)="returnToChristianityHome()"
78175
78379
  >
78176
- @if (canPlaceSelectedCharacter() || isDraggingCharacter()) {
78177
- <span>{{ t('christianityPlayHere') }}</span>
78178
- }
78380
+ {{ t('christianityBackToMenu') }}
78179
78381
  </button>
78180
- }
78181
- }
78182
- </section>
78183
-
78184
- <section class="christianity-player-rack">
78185
- <div class="christianity-offerings-zone">
78186
- <div
78187
- class="christianity-offering-drop-target"
78188
- cdkDropList
78189
- id="christianity-offerings-drop"
78190
- [cdkDropListConnectedTo]="['christianity-hand-source']"
78191
- [class.is-play-target]="canPlaceSelectedOffering() || isDraggingOffering()"
78192
- [cdkDropListEnterPredicate]="canDropOnOfferings"
78193
- [cdkDropListSortingDisabled]="true"
78194
- (cdkDropListDropped)="dropOnOfferings($event)"
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"
78195
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">
78196
78412
  <button
78197
78413
  type="button"
78198
78414
  class="christianity-offering-pile"
78199
- data-testid="christianity-offerings-P1"
78200
- [class.is-empty-zone]="!topOffering('P1')"
78415
+ data-testid="christianity-offerings-P2"
78416
+ [class.is-empty-zone]="!topOffering('P2')"
78201
78417
  valLightRipple
78202
- (click)="handleOfferingPile('P1')"
78418
+ (click)="openOfferings('P2')"
78203
78419
  >
78204
- @if (offeringStack('P1').length) {
78420
+ @if (offeringStack('P2').length) {
78205
78421
  <span class="christianity-zone-card-stack" aria-hidden="true">
78206
- @for (offering of offeringStack('P1'); track offering.instanceId) {
78422
+ @for (offering of offeringStack('P2'); track offering.instanceId) {
78207
78423
  <val-christianity-card
78208
78424
  [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()"
78209
78425
  [variant]="offering.ready ? 'baby' : 'back'"
78210
- [style.transform]="zoneStackTransform($index, offeringStack('P1').length)"
78426
+ [style.transform]="zoneStackTransform($index, offeringStack('P2').length)"
78211
78427
  ></val-christianity-card>
78212
78428
  }
78213
78429
  </span>
@@ -78215,89 +78431,358 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78215
78431
  <span class="christianity-empty-card-slot" aria-hidden="true"></span>
78216
78432
  }
78217
78433
  <span>{{ t('christianityOfferingReserve') }}</span>
78218
- <strong>{{ readyOfferings(player()) }}</strong>
78219
- <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(player()) }}</small>
78434
+ <strong>{{ readyOfferings(opponent()) }}</strong>
78435
+ <small>{{ t('christianityOfferingBase') }} {{ totalOfferings(opponent()) }}</small>
78220
78436
  </button>
78221
78437
  </div>
78222
- </div>
78223
- <button type="button" class="christianity-leader-card christianity-render-card-button" data-testid="christianity-leader-P1" valLightRipple (click)="inspectLeader('P1')">
78224
- <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
78225
- </button>
78226
- <div class="christianity-player-command">
78227
- <div class="christianity-player-badge">
78228
- <span class="christianity-avatar" [style.background]="player().avatarColor">{{ player().avatar }}</span>
78229
- <strong>{{ player().displayName }}</strong>
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>
78230
78459
  </div>
78231
- </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>
78232
78495
  </section>
78233
- </section>
78234
- </section>
78235
78496
 
78236
- <section class="christianity-hand-shelf">
78237
- <div class="christianity-hand-meter">
78238
- <span>{{ t('christianityHand') }}</span>
78239
- <strong>{{ player().hand?.length ?? 0 }}</strong>
78240
- </div>
78241
- <section
78242
- class="christianity-hand-fan"
78243
- data-testid="christianity-hand-P1"
78244
- cdkDropList
78245
- id="christianity-hand-source"
78246
- [cdkDropListConnectedTo]="dropTargetIds()"
78247
- [attr.aria-label]="t('christianityYourHand')"
78248
- (pointerleave)="clearHoveredHandIndex()"
78249
- >
78250
- @for (cardId of player().hand ?? []; track cardId + $index) {
78251
- @let card = cardDefinition(cardId);
78252
- <div
78253
- class="christianity-hand-card-wrap"
78254
- [class.is-selected]="selectedHandCard() === cardId"
78255
- [class.is-dragging]="draggingCardId() === cardId"
78256
- [style.z-index]="handCardZIndex($index)"
78257
- [style.transform]="fanTransform($index, player().hand?.length ?? 0, false)"
78258
- (pointerenter)="setHoveredHandIndex($index)"
78259
- (pointerdown)="setHoveredHandIndex($index)"
78260
- (pointerup)="releaseHoveredHand($event)"
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()"
78261
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>
78262
78692
  <button
78263
78693
  type="button"
78264
- class="christianity-card-preview-card christianity-card-preview-card--hand christianity-render-card-button"
78265
- [attr.data-testid]="handCardTestId(cardId, $index)"
78266
- cdkDrag
78694
+ class="christianity-leader-card christianity-render-card-button"
78695
+ data-testid="christianity-leader-P1"
78267
78696
  valLightRipple
78268
- [cdkDragData]="cardId"
78269
- [cdkDragDisabled]="!canPlay(card)"
78270
- [attr.aria-label]="handCardActionLabel(card)"
78271
- [class.is-selected]="selectedHandCard() === cardId"
78272
- [class.is-disabled]="!canPlay(card)"
78273
- (cdkDragStarted)="startDraggingCard(cardId)"
78274
- (cdkDragEnded)="endDraggingCard()"
78275
- (click)="selectHandCard(cardId)"
78697
+ (click)="inspectLeader('P1')"
78276
78698
  >
78277
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78278
- <ng-template cdkDragPreview>
78279
- <div class="christianity-card-drag-preview">
78280
- <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78281
- </div>
78282
- </ng-template>
78283
- <ng-template cdkDragPlaceholder>
78284
- <div class="christianity-card-drag-placeholder"></div>
78285
- </ng-template>
78699
+ <val-christianity-card [card]="leaderRenderCard(player())" variant="board"></val-christianity-card>
78286
78700
  </button>
78287
- <button
78288
- type="button"
78289
- class="christianity-card-eye"
78290
- [attr.aria-label]="t('christianityPreviewCard')"
78291
- (click)="inspectHandCard(cardId, true); $event.stopPropagation()"
78292
- ></button>
78293
- </div>
78294
- }
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>
78295
78776
  </section>
78296
- </section>
78297
78777
  }
78298
78778
 
78299
78779
  @if (selectedPreview()) {
78300
- <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>
78301
78786
  <aside
78302
78787
  class="christianity-floating-card-shell"
78303
78788
  [class.is-history-preview]="canNavigatePreview()"
@@ -78337,7 +78822,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78337
78822
  </button>
78338
78823
  }
78339
78824
  @if (selectedPreviewNeighborRenderCard(-1); as previousCard) {
78340
- <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>
78341
78830
  }
78342
78831
  <div
78343
78832
  class="christianity-floating-card"
@@ -78356,7 +78845,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78356
78845
  }
78357
78846
  </div>
78358
78847
  @if (selectedPreviewNeighborRenderCard(1); as nextCard) {
78359
- <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>
78360
78853
  }
78361
78854
  </div>
78362
78855
  <div class="christianity-preview-side">
@@ -78440,7 +78933,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78440
78933
  }
78441
78934
 
78442
78935
  @if (selectedInfo(); as info) {
78443
- <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>
78444
78942
  <aside class="christianity-info-panel" aria-live="polite">
78445
78943
  <button type="button" class="christianity-dialog-close christianity-info-close" (click)="closeInfo()">
78446
78944
  {{ t('close') }}
@@ -78465,14 +78963,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78465
78963
  @if (screen() === 'table') {
78466
78964
  <section class="christianity-actions" data-testid="christianity-actions" aria-live="polite">
78467
78965
  <p class="christianity-sr-only" data-testid="christianity-status">{{ statusText() }}</p>
78468
- <button type="button" data-testid="christianity-primary-action" [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()" (click)="playSelectedCard()">
78966
+ <button
78967
+ type="button"
78968
+ data-testid="christianity-primary-action"
78969
+ [disabled]="tutorialPhase() !== 'in_progress' || selectedActionDisabled()"
78970
+ (click)="playSelectedCard()"
78971
+ >
78469
78972
  {{ selectedActionLabel() }}
78470
78973
  </button>
78471
78974
  </section>
78472
78975
  }
78473
78976
 
78474
78977
  @if (showHistory()) {
78475
- <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>
78476
78984
  <section class="christianity-history-drawer" data-testid="christianity-history-drawer">
78477
78985
  <header>
78478
78986
  <h2>{{ t('christianityHistory') }}</h2>
@@ -78487,7 +78995,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78487
78995
  <div class="christianity-history-grid" data-testid="christianity-history-grid">
78488
78996
  @for (cardId of game().historyZone; track cardId + $index) {
78489
78997
  @let card = cardDefinition(cardId);
78490
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="historyCardTestId(cardId, $index)" valLightRipple (click)="inspectHistoryCard(cardId)">
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
+ >
78491
79005
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78492
79006
  </button>
78493
79007
  }
@@ -78499,7 +79013,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78499
79013
  }
78500
79014
 
78501
79015
  @if (showPlace()) {
78502
- <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>
78503
79022
  <section class="christianity-history-drawer christianity-place-drawer" data-testid="christianity-place-drawer">
78504
79023
  <header>
78505
79024
  <h2>{{ t('christianityPlace') }}</h2>
@@ -78512,8 +79031,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78512
79031
  </header>
78513
79032
  <div class="christianity-history-grid" data-testid="christianity-place-grid">
78514
79033
  @for (cardId of game().placeZone; track cardId + $index) {
78515
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" [attr.data-testid]="'christianity-place-card-' + $index" valLightRipple (click)="inspectPlaceCard(cardId)">
78516
- <val-christianity-card [card]="definitionToRenderCard(cardDefinition(cardId), { statusText: t('christianityPlace') })" variant="baby"></val-christianity-card>
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>
78517
79045
  </button>
78518
79046
  }
78519
79047
  </div>
@@ -78521,8 +79049,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78521
79049
  }
78522
79050
 
78523
79051
  @if (showGallery()) {
78524
- <button type="button" class="christianity-history-backdrop christianity-gallery-backdrop" [attr.aria-label]="t('christianityCloseGallery')" (click)="closeGallery()"></button>
78525
- <section class="christianity-history-drawer christianity-gallery-drawer" data-testid="christianity-gallery-drawer">
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
+ >
78526
79062
  <header>
78527
79063
  <div>
78528
79064
  <h2>{{ t('christianityGallery') }}</h2>
@@ -78538,7 +79074,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78538
79074
  <div class="christianity-gallery-controls">
78539
79075
  <label>
78540
79076
  <span>{{ t('christianityGallerySearch') }}</span>
78541
- <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
+ />
78542
79083
  </label>
78543
79084
  <label>
78544
79085
  <span>{{ t('christianityGalleryFilter') }}</span>
@@ -78557,7 +79098,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78557
79098
  </div>
78558
79099
  <div class="christianity-gallery-grid">
78559
79100
  @for (card of galleryCards(); track card.id) {
78560
- <button type="button" class="christianity-gallery-card-button christianity-render-card-button" valLightRipple (click)="inspectGalleryCard(card.id)">
79101
+ <button
79102
+ type="button"
79103
+ class="christianity-gallery-card-button christianity-render-card-button"
79104
+ valLightRipple
79105
+ (click)="inspectGalleryCard(card.id)"
79106
+ >
78561
79107
  <val-christianity-card [card]="definitionToRenderCard(card)" variant="baby"></val-christianity-card>
78562
79108
  </button>
78563
79109
  } @empty {
@@ -78569,12 +79115,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78569
79115
 
78570
79116
  @if (offeringsSeat()) {
78571
79117
  @let offeringPlayer = game().players[offeringsSeat()!];
78572
- <button type="button" class="christianity-history-backdrop" [attr.aria-label]="t('christianityCloseOfferings')" (click)="closeOfferings()"></button>
78573
- <section class="christianity-history-drawer christianity-offerings-drawer" [attr.data-testid]="offeringsDrawerTestId(offeringsSeat()!)">
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
+ >
78574
79128
  <header>
78575
79129
  <h2>{{ t('christianityOfferings') }}</h2>
78576
79130
  <div class="christianity-dialog-actions">
78577
- <span>{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} · {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span>
79131
+ <span
79132
+ >{{ t('christianityOfferingReserve') }} {{ readyOfferings(offeringPlayer) }} ·
79133
+ {{ t('christianityOfferingBase') }} {{ totalOfferings(offeringPlayer) }}</span
79134
+ >
78578
79135
  <button type="button" class="christianity-dialog-close" (click)="closeOfferings()">
78579
79136
  {{ t('close') }}
78580
79137
  </button>
@@ -78584,14 +79141,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
78584
79141
  <span class="christianity-rule-note-icon" aria-hidden="true">i</span>
78585
79142
  <p>{{ t('christianityOfferingsRuleBody') }}</p>
78586
79143
  </div>
78587
- <div class="christianity-offering-list" [attr.data-testid]="offeringListTestId(offeringsSeat()!)">
78588
- @for (offering of offeringPlayer.offerings; track offering.instanceId) {
78589
- <article class="christianity-offering-row" [class.is-exhausted]="!offering.ready">
78590
- <button type="button" class="christianity-offering-card christianity-render-card-button" [attr.data-testid]="offeringCardTestId(offering)" valLightRipple (click)="inspectOffering(offering)">
78591
- <val-christianity-card [card]="offering.ready ? offeringRenderCard(offering) : hiddenCardRenderCard()" [variant]="offering.ready ? 'baby' : 'back'"></val-christianity-card>
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>
78592
79158
  </button>
78593
79159
  <div>
78594
- <strong>{{ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted') }}</strong>
79160
+ <strong>{{
79161
+ offering.ready ? t('christianityOfferingReady') : t('christianityOfferingExhausted')
79162
+ }}</strong>
78595
79163
  <p>{{ offeringAbilityText(offering) }}</p>
78596
79164
  @if (offering.abilityName) {
78597
79165
  <button