Card filter: show only Universes Beyond cards

The Universes Beyond toggle now cycles off → hide → only, backed by a new
only_universes_beyond library filter. Choosing "only" counts as a real
query, so Card Search runs it on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 22:03:45 -05:00
parent f1f6591319
commit 04dc4f6e7b
4 changed files with 48 additions and 7 deletions

View File

@@ -201,6 +201,8 @@ export interface LibraryFilter {
* Warhammer 40,000, Marvel, …). Ordinary cards reprinted into a UB set are
* kept. Needs card data downloaded after this shipped; inert otherwise. */
exclude_universes_beyond?: boolean;
/** The opposite: only cards that exist only as Universes Beyond. */
only_universes_beyond?: boolean;
/** Hide Arena/MTGO-only cards (Alchemy rebalances) — they can't be owned on
* paper. Needs card data downloaded after this shipped; inert otherwise. */
exclude_digital_only?: boolean;

View File

@@ -2391,6 +2391,8 @@ const ICON_PACKAGE = icon(
*/
export type CardCriteria = {
excludeUb: boolean;
/** Never set together with `excludeUb`: the toggle cycles off → hide → only. */
onlyUb: boolean;
excludeDigital: boolean;
excludeFunny: boolean;
priceMin: string;
@@ -2410,6 +2412,7 @@ export type CardCriteria = {
export const EMPTY_CRITERIA: CardCriteria = {
excludeUb: false,
onlyUb: false,
excludeDigital: false,
excludeFunny: false,
priceMin: "",
@@ -2451,6 +2454,7 @@ const num = (s: string): number | null => (s.trim() === "" ? null : Number(s));
export function applyCardCriteria(c: CardCriteria): Partial<LibraryFilter> {
return {
exclude_universes_beyond: c.excludeUb,
only_universes_beyond: c.onlyUb,
exclude_digital_only: c.excludeDigital,
exclude_funny: c.excludeFunny,
price_min: num(c.priceMin),
@@ -2474,10 +2478,12 @@ export function applyCardCriteria(c: CardCriteria): Partial<LibraryFilter> {
* The three "exclude" toggles deliberately don't count: they refine a result
* set rather than select one, so ticking "hide Un-sets" on its own shouldn't
* make Card Search pull every card in the database. Choosing a format, a
* power range, or "not owned" is a real query, and does.
* power range, "not owned" or "only Universes Beyond" is a real query, and
* does.
*/
export function criteriaActive(c: CardCriteria): boolean {
return (
c.onlyUb ||
c.canBeCommander ||
c.vanillaOnly ||
c.notOwned ||
@@ -2611,13 +2617,29 @@ export function CardCriteriaFields({
<div className="icon-toggles">
{/* Exclusions read as "no X" — tinted with the danger colour when
active, so removing things looks different from selecting them. */}
{/* Three states: off → hide → only → off. "Only" looks like the
other "only" toggles rather than an exclusion. */}
<IconToggle
icon={ICON_CROSSOVER}
label="Hide Universes Beyond"
hint="Cards that only exist as another property — Lord of the Rings, Warhammer 40,000, Marvel. Normal cards reprinted in those sets stay."
exclude
on={c.excludeUb}
onToggle={() => onChange({ excludeUb: !c.excludeUb })}
label={
c.onlyUb
? "Only Universes Beyond (click to show all)"
: c.excludeUb
? "Hiding Universes Beyond (click for only them)"
: "Hide Universes Beyond"
}
hint="Cards that only exist as another property — Lord of the Rings, Warhammer 40,000, Marvel. Normal cards reprinted in those sets count as normal cards. Click again to show only these cards, and once more to show everything."
exclude={!c.onlyUb}
on={c.excludeUb || c.onlyUb}
onToggle={() =>
onChange(
c.onlyUb
? { excludeUb: false, onlyUb: false }
: c.excludeUb
? { excludeUb: false, onlyUb: true }
: { excludeUb: true, onlyUb: false },
)
}
/>
<IconToggle
icon={ICON_SCREEN}