From ed12a60bf1299568a1d07934fe2cbeb0d7ecac96 Mon Sep 17 00:00:00 2001 From: Peter Lord Date: Thu, 30 Jul 2026 17:35:47 -0700 Subject: [PATCH] Keep filter dropdowns labeled after a value is picked --- frontend/app/cards/CardsClient.tsx | 6 ++ .../app/cards/browse/[slug]/BrowseDetail.tsx | 4 + frontend/app/components/SearchFilter.tsx | 92 +++++++++++-------- .../app/enchantments/EnchantmentsClient.tsx | 1 + frontend/app/encounters/EncountersClient.tsx | 2 + frontend/app/events/EventsClient.tsx | 2 + frontend/app/guides/GuidesClient.tsx | 2 + frontend/app/keywords/[id]/KeywordDetail.tsx | 1 + frontend/app/monsters/MonstersClient.tsx | 2 + frontend/app/potions/PotionsClient.tsx | 2 + frontend/app/powers/PowersClient.tsx | 2 + frontend/app/relics/RelicsClient.tsx | 3 + frontend/app/timeline/TimelineClient.tsx | 1 + frontend/lib/i18n-x4.ts | 9 ++ 14 files changed, 91 insertions(+), 38 deletions(-) diff --git a/frontend/app/cards/CardsClient.tsx b/frontend/app/cards/CardsClient.tsx index 0e866072..1dfb75f2 100644 --- a/frontend/app/cards/CardsClient.tsx +++ b/frontend/app/cards/CardsClient.tsx @@ -230,6 +230,7 @@ function CardsClientInner({ initialCards }: { initialCards: Card[] }) { filters={[ { label: "View", + name: "View", value: view, options: [ { value: "card", label: "Card View" }, @@ -240,30 +241,35 @@ function CardsClientInner({ initialCards }: { initialCards: Card[] }) { }, { label: "Any Color", + name: "Color", value: color, options: colorOptions, onChange: (v) => setFilterAndUrl("color", v, setColor), }, { label: "Any Type", + name: "Type", value: type, options: typeOptions, onChange: (v) => setFilterAndUrl("type", v, setType), }, { label: "Any Rarity", + name: "Rarity", value: rarity, options: rarityOptions, onChange: (v) => setFilterAndUrl("rarity", v, setRarity), }, { label: "Any Cost", + name: "Cost", value: cost, options: costOptions, onChange: (v) => setFilterAndUrl("cost", v, setCost), }, { label: "Any Keyword", + name: "Keyword", value: keyword, options: keywordOptions, onChange: (v) => setFilterAndUrl("keyword", v, setKeyword), diff --git a/frontend/app/cards/browse/[slug]/BrowseDetail.tsx b/frontend/app/cards/browse/[slug]/BrowseDetail.tsx index 1c686252..af8761b0 100644 --- a/frontend/app/cards/browse/[slug]/BrowseDetail.tsx +++ b/frontend/app/cards/browse/[slug]/BrowseDetail.tsx @@ -101,6 +101,7 @@ export default function BrowseDetail({ initialCards, fixedParams }: BrowseDetail if (showColorFilter) { filters.push({ label: "Any Color", + name: "Color", value: color, options: colorOptions, onChange: setColor, @@ -109,6 +110,7 @@ export default function BrowseDetail({ initialCards, fixedParams }: BrowseDetail if (showTypeFilter) { filters.push({ label: "Any Type", + name: "Type", value: type, options: typeOptions, onChange: setType, @@ -117,6 +119,7 @@ export default function BrowseDetail({ initialCards, fixedParams }: BrowseDetail if (showRarityFilter) { filters.push({ label: "Any Rarity", + name: "Rarity", value: rarity, options: rarityOptions, onChange: setRarity, @@ -125,6 +128,7 @@ export default function BrowseDetail({ initialCards, fixedParams }: BrowseDetail if (showKeywordFilter) { filters.push({ label: "Any Keyword", + name: "Keyword", value: keyword, options: keywordOptions, onChange: setKeyword, diff --git a/frontend/app/components/SearchFilter.tsx b/frontend/app/components/SearchFilter.tsx index 601b5f00..58c5fa33 100644 --- a/frontend/app/components/SearchFilter.tsx +++ b/frontend/app/components/SearchFilter.tsx @@ -33,6 +33,9 @@ interface SearchFilterProps { onSearchChange: (value: string) => void; filters?: { label: string; + // Short caption rendered above the select so the filter stays identifiable + // after a value is picked. Passed through t(); falls back to the raw text. + name?: string; value: string; options: FilterOption[]; onChange: (value: string) => void; @@ -81,8 +84,14 @@ export default function SearchFilter({ return () => clearTimeout(timer); }, [draft, search]); + const caption = (text: string) => ( + + {t(text, lang)} + + ); + return ( -
+
{filters?.map((filter) => ( - filter.onChange(e.target.value)} + aria-label={t(filter.name ?? filter.label, lang)} + className={selectClass} + > + {!filter.noEmptyOption && ( + + )} + {groupOptions(filter.options).map((seg, i) => + seg.group ? ( + + {seg.opts.map((opt) => ( + + ))} + + ) : ( + seg.opts.map((opt) => ( - ))} - - ) : ( - seg.opts.map((opt) => ( - - )) - ), - )} - + )) + ), + )} + + ))} {sortOptions && onSortChange && ( - + )} {resultCount !== undefined && ( diff --git a/frontend/app/enchantments/EnchantmentsClient.tsx b/frontend/app/enchantments/EnchantmentsClient.tsx index e65c3c78..fe54cece 100644 --- a/frontend/app/enchantments/EnchantmentsClient.tsx +++ b/frontend/app/enchantments/EnchantmentsClient.tsx @@ -78,6 +78,7 @@ function EnchantmentsClientInner({ initialEnchantments }: { initialEnchantments: filters={[ { label: "Any Card Type", + name: "Card Type", value: cardType, options: cardTypeOptions, onChange: (v) => setFilterAndUrl("cardType", v, setCardType), diff --git a/frontend/app/encounters/EncountersClient.tsx b/frontend/app/encounters/EncountersClient.tsx index fce85300..9b09e289 100644 --- a/frontend/app/encounters/EncountersClient.tsx +++ b/frontend/app/encounters/EncountersClient.tsx @@ -115,12 +115,14 @@ function EncountersClientInner({ initialEncounters }: { initialEncounters: Encou filters={[ { label: "Any Type", + name: "Type", value: roomType, options: roomTypeOptions, onChange: (v) => setFilterAndUrl("roomType", v, setRoomType), }, { label: "Any Act", + name: "Act", value: act, options: actOptions, onChange: (v) => setFilterAndUrl("act", v, setAct), diff --git a/frontend/app/events/EventsClient.tsx b/frontend/app/events/EventsClient.tsx index d4f4c712..e4b2d8cc 100644 --- a/frontend/app/events/EventsClient.tsx +++ b/frontend/app/events/EventsClient.tsx @@ -188,12 +188,14 @@ function EventsClientInner({ initialEvents }: { initialEvents: GameEvent[] }) { filters={[ { label: "Any Type", + name: "Type", value: type, options: typeOptions, onChange: (v) => setFilterAndUrl("type", v, setType), }, { label: "Any Act", + name: "Act", value: act, options: actOptions, onChange: (v) => setFilterAndUrl("act", v, setAct), diff --git a/frontend/app/guides/GuidesClient.tsx b/frontend/app/guides/GuidesClient.tsx index 6739632b..b03457af 100644 --- a/frontend/app/guides/GuidesClient.tsx +++ b/frontend/app/guides/GuidesClient.tsx @@ -108,12 +108,14 @@ function GuidesClientInner({ initialGuides }: { initialGuides: GuideSummary[] }) filters={[ { label: "Any Category", + name: "Category", value: category, options: categoryOptions, onChange: (v) => setFilterAndUrl("category", v, setCategory), }, { label: "Any Difficulty", + name: "Difficulty", value: difficulty, options: difficultyOptions, onChange: (v) => setFilterAndUrl("difficulty", v, setDifficulty), diff --git a/frontend/app/keywords/[id]/KeywordDetail.tsx b/frontend/app/keywords/[id]/KeywordDetail.tsx index 816403d1..a89f3d82 100644 --- a/frontend/app/keywords/[id]/KeywordDetail.tsx +++ b/frontend/app/keywords/[id]/KeywordDetail.tsx @@ -181,6 +181,7 @@ export default function KeywordDetail({ initialResult }: { initialResult?: Initi filters={[ { label: "Any Character", + name: "Character", value: color, options: colorOptions, onChange: setColor, diff --git a/frontend/app/monsters/MonstersClient.tsx b/frontend/app/monsters/MonstersClient.tsx index 4ee14d3f..9a38ae8a 100644 --- a/frontend/app/monsters/MonstersClient.tsx +++ b/frontend/app/monsters/MonstersClient.tsx @@ -118,12 +118,14 @@ function MonstersClientInner({ initialMonsters }: { initialMonsters: Monster[] } filters={[ { label: "Any Type", + name: "Type", value: type, options: typeOptions, onChange: (v) => setFilterAndUrl("type", v, setType), }, { label: "Any Act", + name: "Act", value: act, options: actOptions, onChange: (v) => setFilterAndUrl("act", v, setAct), diff --git a/frontend/app/potions/PotionsClient.tsx b/frontend/app/potions/PotionsClient.tsx index b7cba493..1a4fc84d 100644 --- a/frontend/app/potions/PotionsClient.tsx +++ b/frontend/app/potions/PotionsClient.tsx @@ -122,12 +122,14 @@ function PotionsClientInner({ initialPotions }: { initialPotions: Potion[] }) { filters={[ { label: "Any Rarity", + name: "Rarity", value: rarity, options: rarityOptions, onChange: (v) => setFilterAndUrl("rarity", v, setRarity), }, { label: "Any Character", + name: "Character", value: pool, options: poolOptions, onChange: (v) => setFilterAndUrl("pool", v, setPool), diff --git a/frontend/app/powers/PowersClient.tsx b/frontend/app/powers/PowersClient.tsx index 359d983b..9e1ea47c 100644 --- a/frontend/app/powers/PowersClient.tsx +++ b/frontend/app/powers/PowersClient.tsx @@ -83,12 +83,14 @@ const [powers, setPowers] = useState(initialPowers); filters={[ { label: "Any Type", + name: "Type", value: type, options: typeOptions, onChange: setType, }, { label: "Any Stack Type", + name: "Stack Type", value: stackType, options: stackOptions, onChange: setStackType, diff --git a/frontend/app/relics/RelicsClient.tsx b/frontend/app/relics/RelicsClient.tsx index 7df1583c..9a83eaa7 100644 --- a/frontend/app/relics/RelicsClient.tsx +++ b/frontend/app/relics/RelicsClient.tsx @@ -164,18 +164,21 @@ function RelicsClientInner({ initialRelics }: { initialRelics: Relic[] }) { filters={[ { label: "Any Rarity", + name: "Rarity", value: rarity, options: rarityOptions, onChange: (v) => setFilterAndUrl("rarity", v, setRarity), }, { label: "Any Pool", + name: "Pool", value: pool, options: poolOptions, onChange: (v) => setFilterAndUrl("pool", v, setPool), }, { label: "Any Ancient", + name: "Ancient", value: ancient, options: ancientOptions, onChange: (v) => setFilterAndUrl("ancient", v, setAncient), diff --git a/frontend/app/timeline/TimelineClient.tsx b/frontend/app/timeline/TimelineClient.tsx index 4e664d5e..ca6dec35 100644 --- a/frontend/app/timeline/TimelineClient.tsx +++ b/frontend/app/timeline/TimelineClient.tsx @@ -257,6 +257,7 @@ export default function TimelineClient({ filters={[ { label: "Any Story", + name: "Story", value: storyFilter, options: storyOptions, onChange: setStoryFilter, diff --git a/frontend/lib/i18n-x4.ts b/frontend/lib/i18n-x4.ts index f3b9f284..4bc1de42 100644 --- a/frontend/lib/i18n-x4.ts +++ b/frontend/lib/i18n-x4.ts @@ -149,6 +149,15 @@ export const X4: Record> = { "Subscribe on the Steam Workshop and Steam downloads the mod on its own": { eng: "Subscribe on the Steam Workshop and Steam downloads the mod on its own", deu: "Abonniere im Steam Workshop und Steam lädt den Mod von selbst herunter", esp: "Suscríbete en el Steam Workshop y Steam descarga el mod por sí solo", fra: "Abonnez-vous sur le Steam Workshop et Steam télécharge le mod tout seul", ita: "Iscriviti sullo Steam Workshop e Steam scarica la mod da solo", jpn: "Steam ワークショップで登録すると、Steam が自動でモッドをダウンロードします", kor: "Steam 창작마당에서 구독하면 Steam이 알아서 모드를 내려받습니다", pol: "Subskrybuj w Steam Workshop, a Steam sam pobierze mod", ptb: "Inscreva-se na Steam Workshop e o Steam baixa o mod sozinho", rus: "Подпишитесь в Мастерской Steam, и Steam сам скачает мод", spa: "Suscríbete en el Steam Workshop y Steam descarga el mod por sí solo", tha: "กดติดตามใน Steam Workshop แล้ว Steam จะดาวน์โหลดม็อดให้เอง", tur: "Steam Atölyesi'nde abone olun, Steam modu kendiliğinden indirir", zhs: "在 Steam 创意工坊订阅后,Steam 会自动下载模组", zht: "在 Steam 工作坊訂閱後,Steam 會自動下載模組" }, "Enable the mod from the game's mods menu and restart": { eng: "Enable the mod from the game's mods menu and restart", deu: "Aktiviere den Mod im Mods-Menü des Spiels und starte neu", esp: "Activa el mod desde el menú de mods del juego y reinicia", fra: "Activez le mod depuis le menu des mods du jeu et redémarrez", ita: "Attiva la mod dal menu mod del gioco e riavvia", jpn: "ゲームのモッドメニューでモッドを有効にして再起動", kor: "게임의 모드 메뉴에서 모드를 활성화하고 재시작", pol: "Włącz mod w menu modów gry i uruchom ponownie", ptb: "Ative o mod no menu de mods do jogo e reinicie", rus: "Включите мод в меню модов игры и перезапустите её", spa: "Activa el mod desde el menú de mods del juego y reinicia", tha: "เปิดใช้ม็อดจากเมนูม็อดในเกมแล้วรีสตาร์ต", tur: "Modu oyunun mod menüsünden etkinleştirin ve yeniden başlatın", zhs: "在游戏的模组菜单中启用模组并重启", zht: "在遊戲的模組選單中啟用模組並重啟" }, "Play. Runs upload automatically, and signing in with Steam on the site links them to your profile": { eng: "Play. Runs upload automatically, and signing in with Steam on the site links them to your profile", deu: "Spiele. Runs werden automatisch hochgeladen, und die Steam-Anmeldung auf der Seite verknüpft sie mit deinem Profil", esp: "Juega. Las partidas se suben automáticamente, e iniciar sesión con Steam en el sitio las vincula a tu perfil", fra: "Jouez. Les parties se téléversent automatiquement, et la connexion Steam sur le site les relie à votre profil", ita: "Gioca. Le partite si caricano automaticamente e l'accesso con Steam sul sito le collega al tuo profilo", jpn: "プレイするだけ。ランは自動でアップロードされ、サイトで Steam サインインするとプロフィールに紐付きます", kor: "플레이하세요. 런이 자동 업로드되며, 사이트에서 Steam 로그인하면 프로필에 연결됩니다", pol: "Graj. Przebiegi przesyłają się automatycznie, a logowanie przez Steam na stronie łączy je z twoim profilem", ptb: "Jogue. As partidas são enviadas automaticamente, e entrar com a Steam no site as vincula ao seu perfil", rus: "Играйте. Забеги загружаются автоматически, а вход через Steam на сайте привязывает их к вашему профилю", spa: "Juega. Las partidas se suben automáticamente, e iniciar sesión con Steam en el sitio las vincula a tu perfil", tha: "เล่นได้เลย การเล่นจะอัปโหลดอัตโนมัติ และการลงชื่อเข้าใช้ด้วย Steam บนเว็บไซต์จะเชื่อมกับโปรไฟล์ของคุณ", tur: "Oynayın. Oyunlar otomatik yüklenir ve sitede Steam ile giriş yapmak onları profilinize bağlar", zhs: "开始游戏。对局会自动上传,在网站上用 Steam 登录即可关联到你的个人资料", zht: "開始遊戲。對局會自動上傳,在網站上用 Steam 登入即可關聯到你的個人資料" }, + "Cost": { eng: "Cost", deu: "Kosten", esp: "Coste", fra: "Coût", ita: "Costo", jpn: "コスト", kor: "비용", pol: "Koszt", ptb: "Custo", rus: "Стоимость", spa: "Costo", tha: "คอสต์", tur: "Maliyet", zhs: "费用", zht: "費用" }, + "Keyword": { eng: "Keyword", deu: "Schlüsselwort", esp: "Palabra clave", fra: "Mot-clé", ita: "Parola chiave", jpn: "キーワード", kor: "키워드", pol: "Słowo kluczowe", ptb: "Palavra-chave", rus: "Ключевое слово", spa: "Palabra clave", tha: "คีย์เวิร์ด", tur: "Anahtar kelime", zhs: "关键词", zht: "關鍵詞" }, + "Act": { eng: "Act", deu: "Akt", esp: "Acto", fra: "Acte", ita: "Atto", jpn: "幕", kor: "막", pol: "Akt", ptb: "Ato", rus: "Акт", spa: "Acto", tha: "องก์", tur: "Perde", zhs: "章节", zht: "章節" }, + "Category": { eng: "Category", deu: "Kategorie", esp: "Categoría", fra: "Catégorie", ita: "Categoria", jpn: "カテゴリ", kor: "카테고리", pol: "Kategoria", ptb: "Categoria", rus: "Категория", spa: "Categoría", tha: "หมวดหมู่", tur: "Kategori", zhs: "分类", zht: "分類" }, + "Difficulty": { eng: "Difficulty", deu: "Schwierigkeit", esp: "Dificultad", fra: "Difficulté", ita: "Difficoltà", jpn: "難易度", kor: "난이도", pol: "Trudność", ptb: "Dificuldade", rus: "Сложность", spa: "Dificultad", tha: "ความยาก", tur: "Zorluk", zhs: "难度", zht: "難度" }, + "Story": { eng: "Story", deu: "Geschichte", esp: "Historia", fra: "Histoire", ita: "Storia", jpn: "ストーリー", kor: "스토리", pol: "Historia", ptb: "História", rus: "История", spa: "Historia", tha: "เรื่องราว", tur: "Hikaye", zhs: "故事", zht: "故事" }, + "Card Type": { eng: "Card Type", deu: "Kartentyp", esp: "Tipo de carta", fra: "Type de carte", ita: "Tipo di carta", jpn: "カードタイプ", kor: "카드 유형", pol: "Typ karty", ptb: "Tipo de carta", rus: "Тип карты", spa: "Tipo de carta", tha: "ประเภทการ์ด", tur: "Kart Türü", zhs: "卡牌类型", zht: "卡牌型別" }, + "Stack Type": { eng: "Stack Type", deu: "Stapeltyp", esp: "Tipo de acumulación", fra: "Type de cumul", ita: "Tipo di accumulo", jpn: "スタックタイプ", kor: "중첩 유형", pol: "Typ kumulacji", ptb: "Tipo de acúmulo", rus: "Тип стака", spa: "Tipo de acumulación", tha: "ประเภทการซ้อน", tur: "Yığın Türü", zhs: "叠加类型", zht: "疊加型別" }, + "Sort by": { eng: "Sort by", deu: "Sortieren nach", esp: "Ordenar por", fra: "Trier par", ita: "Ordina per", jpn: "並び替え", kor: "정렬 기준", pol: "Sortuj według", ptb: "Ordenar por", rus: "Сортировка", spa: "Ordenar por", tha: "เรียงตาม", tur: "Sırala", zhs: "排序方式", zht: "排序方式" }, "How to install": { eng: "How to install", deu: "Installation", esp: "Cómo instalarlo", fra: "Comment l'installer", ita: "Come installarla", jpn: "インストール方法", kor: "설치 방법", pol: "Jak zainstalować", ptb: "Como instalar", rus: "Как установить", spa: "Cómo instalarlo", tha: "วิธีติดตั้ง", tur: "Nasıl kurulur", zhs: "安装方法", zht: "安裝方法" }, "Mod or overlay?": { eng: "Mod or overlay?", deu: "Mod oder Overlay?", esp: "¿Mod u overlay?", fra: "Mod ou overlay ?", ita: "Mod o overlay?", jpn: "モッドとオーバーレイの違いは?", kor: "모드와 오버레이의 차이는?", pol: "Mod czy nakładka?", ptb: "Mod ou overlay?", rus: "Мод или оверлей?", spa: "¿Mod u overlay?", tha: "ม็อดหรือโอเวอร์เลย์?", tur: "Mod mu overlay mi?", zhs: "模组还是悬浮层?", zht: "模組還是懸浮層?" }, "The mod runs inside the game itself, no external apps, and is the easiest way to contribute runs and see community insights while playing. The Overwolf overlay is a separate companion for card and relic lookups on top of the game. They work fine together, and both are free.": { eng: "The mod runs inside the game itself, no external apps, and is the easiest way to contribute runs and see community insights while playing. The Overwolf overlay is a separate companion for card and relic lookups on top of the game. They work fine together, and both are free.", deu: "Der Mod läuft direkt im Spiel, ohne externe Apps, und ist der einfachste Weg, Runs beizutragen und Community-Einblicke beim Spielen zu sehen. Das Overwolf-Overlay ist ein separater Begleiter für Karten- und Relikt-Nachschlagen über dem Spiel. Beide funktionieren problemlos zusammen und sind kostenlos.", esp: "El mod funciona dentro del propio juego, sin aplicaciones externas, y es la forma más fácil de contribuir partidas y ver información de la comunidad mientras juegas. El overlay de Overwolf es un compañero aparte para consultar cartas y reliquias sobre el juego. Funcionan bien juntos y ambos son gratuitos.", fra: "Le mod fonctionne dans le jeu lui-même, sans application externe, et c'est le moyen le plus simple de contribuer des parties et de voir les analyses communautaires en jouant. L'overlay Overwolf est un compagnon séparé pour consulter cartes et reliques par-dessus le jeu. Les deux fonctionnent bien ensemble et sont gratuits.", ita: "La mod gira dentro il gioco stesso, senza app esterne, ed è il modo più semplice per contribuire partite e vedere le analisi della community mentre giochi. L'overlay Overwolf è un compagno separato per consultare carte e reliquie sopra il gioco. Funzionano bene insieme ed entrambi sono gratuiti.", jpn: "モッドはゲーム内で直接動作し、外部アプリは不要。プレイしながらランを提供しコミュニティのインサイトを見る最も簡単な方法です。Overwolf オーバーレイはカードやレリックを参照する別のコンパニオンです。併用でき、どちらも無料です。", kor: "모드는 외부 앱 없이 게임 안에서 직접 실행되며, 플레이하면서 런을 기여하고 커뮤니티 인사이트를 보는 가장 쉬운 방법입니다. Overwolf 오버레이는 게임 위에서 카드와 유물을 찾아보는 별도의 컴패니언입니다. 함께 사용해도 잘 작동하며 둘 다 무료입니다.", pol: "Mod działa w samej grze, bez zewnętrznych aplikacji, i jest najprostszym sposobem na przekazywanie przebiegów i podgląd analiz społeczności podczas gry. Nakładka Overwolf to osobny towarzysz do sprawdzania kart i reliktów nad grą. Działają razem bez problemu i oba są darmowe.", ptb: "O mod roda dentro do próprio jogo, sem aplicativos externos, e é a forma mais fácil de contribuir partidas e ver insights da comunidade enquanto joga. O overlay da Overwolf é um companheiro separado para consultar cartas e relíquias sobre o jogo. Funcionam bem juntos e ambos são gratuitos.", rus: "Мод работает прямо внутри игры, без внешних приложений, и это самый простой способ отправлять забеги и видеть аналитику сообщества во время игры. Оверлей Overwolf — отдельный компаньон для справки по картам и реликвиям поверх игры. Они прекрасно работают вместе, и оба бесплатны.", spa: "El mod funciona dentro del propio juego, sin aplicaciones externas, y es la forma más fácil de contribuir partidas y ver información de la comunidad mientras juegas. El overlay de Overwolf es un compañero aparte para consultar cartas y reliquias sobre el juego. Funcionan bien juntos y ambos son gratuitos.", tha: "ม็อดทำงานภายในตัวเกมเอง ไม่ต้องใช้แอปภายนอก และเป็นวิธีที่ง่ายที่สุดในการส่งการเล่นและดูข้อมูลเชิงลึกของชุมชนระหว่างเล่น ส่วนโอเวอร์เลย์ Overwolf เป็นตัวช่วยแยกต่างหากสำหรับค้นหาการ์ดและเรลิกบนหน้าจอเกม ใช้ร่วมกันได้และฟรีทั้งคู่", tur: "Mod, harici uygulama olmadan oyunun kendi içinde çalışır ve oynarken oyun katkısı yapmanın ve topluluk içgörülerini görmenin en kolay yoludur. Overwolf overlay'i ise oyunun üzerinde kart ve kalıntı aramak için ayrı bir yardımcıdır. Birlikte sorunsuz çalışırlar ve ikisi de ücretsizdir.", zhs: "模组直接在游戏内运行,无需外部应用,是边玩边贡献对局、查看社区洞察的最简单方式。Overwolf 悬浮层是叠加在游戏上查询卡牌和遗物的独立伴侣工具。两者可以同时使用,都是免费的。", zht: "模組直接在遊戲內運行,無需外部應用,是邊玩邊貢獻對局、查看社群洞察的最簡單方式。Overwolf 懸浮層是疊加在遊戲上查詢卡牌和遺物的獨立夥伴工具。兩者可以同時使用,都是免費的。" },