···176176177177This matches Scryfall's behavior: `id:rg` finds cards playable in a Gruul commander deck.
178178179179-### Identity Count Queries
179179+### Color Count Queries
180180181181-The `identity` field also supports numeric comparisons on the *number* of colors:
182182-- `id>1` - Cards with more than 1 identity color (multicolor+)
183183-- `id=2` - Cards with exactly 2 identity colors
184184-- `id<=1` - Mono-color or colorless cards
185185-- `id=0` - Colorless cards only
181181+Both `color` and `identity` fields support numeric comparisons on the *number* of colors:
182182+- `c>1` or `id>1` - Cards with more than 1 color (multicolor+)
183183+- `c=2` or `id=2` - Cards with exactly 2 colors
184184+- `c<=1` or `id<=1` - Mono-color or colorless cards
185185+- `c=0` or `id=0` - Colorless cards only
186186187187This is useful for finding mono-color commanders, multicolor cards, etc.
188188
+14
src/lib/search/__tests__/describe.test.ts
···114114 });
115115116116 it.each([
117117+ ["c=0", "cards with exactly 0 colors"],
118118+ ["c=1", "cards with exactly 1 color"],
119119+ ["c=2", "cards with exactly 2 colors"],
120120+ ["c>1", "cards with more than 1 color"],
121121+ ["c>0", "cards with more than 0 colors"],
122122+ ["c<3", "cards with fewer than 3 colors"],
123123+ ["c>=2", "cards with 2 or more colors"],
124124+ ["c<=1", "cards with 1 or fewer colors"],
125125+ ["c!=1", "cards without exactly 1 color"],
126126+ ])("color count: `%s` → %s", (query, expected) => {
127127+ expect(desc(query)).toBe(expected);
128128+ });
129129+130130+ it.each([
117131 [
118132 "fire id>=g",
119133 'name includes "fire" AND color identity includes at least {G}',
···212212function describeField(node: FieldNode): string {
213213 const fieldLabel = FIELD_LABELS[node.field];
214214215215- // Special handling for identity count queries (id>1, id=2, etc.)
216216- if (node.field === "identity" && node.value.kind === "number") {
215215+ // Special handling for color/identity count queries (c>1, id>1, c=2, id=2, etc.)
216216+ if (
217217+ (node.field === "color" || node.field === "identity") &&
218218+ node.value.kind === "number"
219219+ ) {
217220 const n = node.value.value;
221221+ const label = node.field === "identity" ? "identity " : "";
218222 // Grammatically: "1 color" but "0/2/3 colors", "fewer than 3 colors", "2 or more colors"
219223 const colorWordExact = n === 1 ? "color" : "colors";
220224 switch (node.operator) {
221225 case ":":
222226 case "=":
223223- return `cards with exactly ${n} identity ${colorWordExact}`;
227227+ return `cards with exactly ${n} ${label}${colorWordExact}`;
224228 case "!=":
225225- return `cards without exactly ${n} identity ${colorWordExact}`;
229229+ return `cards without exactly ${n} ${label}${colorWordExact}`;
226230 case "<":
227227- return `cards with fewer than ${n} identity colors`;
231231+ return `cards with fewer than ${n} ${label}colors`;
228232 case ">":
229229- return `cards with more than ${n} identity ${colorWordExact}`;
233233+ return `cards with more than ${n} ${label}${colorWordExact}`;
230234 case "<=":
231231- return `cards with ${n} or fewer identity colors`;
235235+ return `cards with ${n} or fewer ${label}colors`;
232236 case ">=":
233233- return `cards with ${n} or more identity colors`;
237237+ return `cards with ${n} or more ${label}colors`;
234238 }
235239 }
236240
+12-1
src/lib/search/fields.ts
···8888 return ok(compileOracleText(operator, value));
89899090 // Color fields
9191- case "color":
9191+ case "color": {
9292+ // Numeric comparison: c>1 means "more than 1 color"
9393+ if (value.kind === "number") {
9494+ return ok(
9595+ createOrderedMatcher(
9696+ (card) => card.colors?.length ?? 0,
9797+ value.value,
9898+ operator,
9999+ ),
100100+ );
101101+ }
92102 return ok(compileColorField((c) => c.colors, operator, value));
103103+ }
9310494105 case "identity": {
95106 // Numeric comparison: id>1 means "more than 1 color in identity"
+4-4
src/lib/search/parser.ts
···355355 if (this.match("WORD")) {
356356 const value = this.previous().value;
357357358358- // For color fields, parse as colors - but identity can also take numeric values
359359- // for counting colors (id>1 = "more than 1 color in identity")
358358+ // For color fields, parse as colors - but color/identity can also take numeric values
359359+ // for counting colors (c>1 or id>1 = "more than 1 color")
360360 if (field === "color" || field === "identity") {
361361- // Check if value is purely numeric (for identity count queries like id>1)
362362- if (field === "identity" && /^\d+$/.test(value)) {
361361+ // Check if value is purely numeric (for count queries like c>1, id>1)
362362+ if (/^\d+$/.test(value)) {
363363 const num = parseInt(value, 10);
364364 return ok({ kind: "number", value: num });
365365 }