@domainellipticlanguage/mtg-crucible 0.2.0 → 0.2.4

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.
Files changed (62) hide show
  1. package/README.md +137 -386
  2. package/dist/constants/constants/index.d.ts +3 -0
  3. package/dist/constants/constants/index.d.ts.map +1 -0
  4. package/dist/constants/constants/index.js +14 -0
  5. package/dist/constants/constants/index.js.map +1 -0
  6. package/dist/constants/index.d.ts +3 -0
  7. package/dist/constants/index.d.ts.map +1 -0
  8. package/dist/constants/index.js +14 -0
  9. package/dist/constants/index.js.map +1 -0
  10. package/dist/constants/types.d.ts +166 -0
  11. package/dist/constants/types.d.ts.map +1 -0
  12. package/dist/constants/types.js +19 -0
  13. package/dist/constants/types.js.map +1 -0
  14. package/dist/helpers.d.ts +0 -6
  15. package/dist/helpers.d.ts.map +1 -1
  16. package/dist/helpers.js +0 -54
  17. package/dist/helpers.js.map +1 -1
  18. package/dist/index.d.ts +5 -3
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +23 -10
  21. package/dist/index.js.map +1 -1
  22. package/dist/parser/index.d.ts +4 -0
  23. package/dist/parser/index.d.ts.map +1 -0
  24. package/dist/parser/index.js +28 -0
  25. package/dist/parser/index.js.map +1 -0
  26. package/dist/parser/layout.d.ts +1355 -0
  27. package/dist/parser/layout.d.ts.map +1 -0
  28. package/dist/parser/layout.js +339 -0
  29. package/dist/parser/layout.js.map +1 -0
  30. package/dist/parser/parser/index.d.ts +4 -0
  31. package/dist/parser/parser/index.d.ts.map +1 -0
  32. package/dist/parser/parser/index.js +28 -0
  33. package/dist/parser/parser/index.js.map +1 -0
  34. package/dist/parser/parser.d.ts +30 -0
  35. package/dist/parser/parser.d.ts.map +1 -0
  36. package/dist/parser/parser.js +1321 -0
  37. package/dist/parser/parser.js.map +1 -0
  38. package/dist/parser/types.d.ts +166 -0
  39. package/dist/parser/types.d.ts.map +1 -0
  40. package/dist/parser/types.js +19 -0
  41. package/dist/parser/types.js.map +1 -0
  42. package/dist/parser.d.ts.map +1 -1
  43. package/dist/parser.js +64 -66
  44. package/dist/parser.js.map +1 -1
  45. package/dist/react/react/MtgCard.d.ts +2 -2
  46. package/dist/react/react/MtgCard.d.ts.map +1 -1
  47. package/dist/react/react/MtgCard.js +15 -15
  48. package/dist/react/react/MtgCard.js.map +1 -1
  49. package/dist/react/react/index.d.ts +1 -1
  50. package/dist/react/react/index.d.ts.map +1 -1
  51. package/dist/react/types.d.ts +21 -15
  52. package/dist/react/types.d.ts.map +1 -1
  53. package/dist/react/types.js +16 -0
  54. package/dist/react/types.js.map +1 -1
  55. package/dist/renderers/render.d.ts.map +1 -1
  56. package/dist/renderers/render.js +4 -19
  57. package/dist/renderers/render.js.map +1 -1
  58. package/dist/types.d.ts +21 -15
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/types.js +16 -0
  61. package/dist/types.js.map +1 -1
  62. package/package.json +12 -2
package/README.md CHANGED
@@ -5,470 +5,221 @@ A TypeScript library for rendering Magic: The Gathering card images as PNGs.
5
5
  ## Installation
6
6
 
7
7
  ```bash
8
- npm install mtg-crucible
8
+ npm install @domainellipticlanguage/mtg-crucible
9
9
  ```
10
10
 
11
11
  ## Quick Start
12
12
 
13
+ ### From text
14
+
13
15
  ```typescript
14
- import { renderFromText } from 'mtg-crucible';
16
+ import { renderCard } from '@domainellipticlanguage/mtg-crucible';
15
17
  import { writeFileSync } from 'fs';
16
18
 
17
- const png = await renderFromText(`
18
- Crucible of Legends {3}
19
- Art: https://raw.githubusercontent.com/nathanfdunn/mtg-crucible/refs/heads/main/logo/banner-image.png
20
- Rarity: Mythic Rare
21
- Legendary Artifact
22
- Whenever a legendary creature you control dies, return it to your hand at the beginning of the next end step.
23
- *Every great story begins with fire.*
19
+ const result = await renderCard(`
20
+ Crucible of Legends {3}
21
+ Art URL: https://example.com/art.png
22
+ Rarity: Mythic Rare
23
+ Legendary Artifact
24
+ Whenever a legendary creature you control dies, return it to your hand at the beginning of the next end step.
25
+ Flavor Text: Every great story begins with fire.
24
26
  `);
25
27
 
26
- writeFileSync('crucible-of-legends.png', png);
28
+ writeFileSync('crucible-of-legends.png', result.frontFace);
29
+ ```
30
+
31
+ ### From structured data
32
+
33
+ ```typescript
34
+ import { renderCard } from '@domainellipticlanguage/mtg-crucible';
35
+
36
+ const result = await renderCard({
37
+ name: 'Crucible of Legends',
38
+ manaCost: '{3}',
39
+ supertypes: ['legendary'],
40
+ types: ['artifact'],
41
+ rarity: 'mythic',
42
+ abilities: 'Whenever a legendary creature you control dies, return it to your hand at the beginning of the next end step.',
43
+ flavorText: 'Every great story begins with fire.',
44
+ });
27
45
  ```
28
46
 
29
47
  <img src="logo/crucible-of-legends.png" alt="Crucible of Legends" width="300">
30
48
 
31
49
  ## API
32
50
 
33
- ### `renderFromText(text: string): Promise<Buffer>`
51
+ ### `renderCard(input: CardData | string): Promise<RenderedCard>`
34
52
 
35
- Parse a text-format card definition and render it to a PNG buffer in one step.
53
+ Parse and render a card. Accepts either a text-format string or a `CardData` object. Returns a `RenderedCard` with `frontFace` (PNG buffer), optional `backFace`, orientation info, and rotation data for multi-face cards.
36
54
 
37
- ### `parseCard(text: string): CardInput`
55
+ ### `parseCard(text: string): CardData`
38
56
 
39
- Parse a text-format card definition into a `CardInput` object. Useful when you want to inspect or modify the parsed data before rendering.
57
+ Parse a text-format card definition into a `CardData` object.
40
58
 
41
- ### `renderCard(card: CardInput): Promise<Buffer>`
59
+ ### `formatCard(card: CardData): string`
42
60
 
43
- Render a `CardInput` object to a PNG buffer. Automatically dispatches to the correct renderer based on the card type (standard, planeswalker, saga, or battle).
61
+ Convert a `CardData` object back to text format (round-trips with `parseCard`).
44
62
 
45
- ### Individual renderers
63
+ ### `normalizeCard(card: CardData): NormalizedCardData`
46
64
 
47
- For direct control, each renderer is also exported:
65
+ Normalize a `CardData` into `NormalizedCardData` with all fields resolved (frame colors derived, abilities parsed, defaults filled in).
48
66
 
49
- - `renderStandard(card: CardData): Promise<Buffer>`
50
- - `renderPlaneswalker(card: PlaneswalkerData): Promise<Buffer>`
51
- - `renderSaga(card: SagaData): Promise<Buffer>`
52
- - `renderBattle(card: BattleData): Promise<Buffer>`
67
+ ### `getArtDimensions(card: CardData, template?: TemplateName, linked?: boolean): { width: number; height: number }`
53
68
 
54
- ## Text Format
69
+ Get the expected art image dimensions for a given card and template. Useful for generating or resizing art to fit correctly.
55
70
 
56
- Cards are defined in a plain text format inspired by official text spoilers.
57
- For the full grammar, metadata reference, and corner cases, see [docs/text-format.md](docs/text-format.md).
71
+ ### `renderCardImage(card: NormalizedCardData, templateOverride?: string): Promise<Buffer>`
58
72
 
59
- ### Standard cards
73
+ Low-level renderer. Renders a single face to PNG. Requires pre-normalized card data.
60
74
 
61
- ```
62
- Name {mana cost}
63
- Art: <art url> (Optional)
64
- Rarity: <rarity> (Optional)
65
- Type Line
66
- Rules text line 1
67
- Rules text line 2
68
- Power/Toughness
69
- *Flavor text*
70
- ```
71
-
72
- Each line of rules text becomes a separate paragraph on the rendered card. Mana symbols use curly brace notation: `{W}`, `{U}`, `{B}`, `{R}`, `{G}`, `{C}`, `{T}`, `{1}`, `{2}`, etc. Hybrid and phyrexian mana are supported: `{G/U}`, `{G/P}`.
75
+ ## Text Format
73
76
 
74
- Power/toughness is only parsed for creatures and vehicles. Wildcards like `*/1+*` are supported.
77
+ Cards are defined in a plain text format. For the full grammar see [docs/text-format.md](docs/text-format.md).
75
78
 
76
- Flavor text is wrapped in `*asterisks*` and must come after P/T (at the very end). Multiple flavor lines are joined with newlines:
79
+ ### Standard cards
77
80
 
78
81
  ```
79
- Wrath of God {2}{W}{W}
80
- Sorcery
81
- Destroy all creatures. They can't be regenerated.
82
- *Legend speaks of the Creators' rage at their most prized creation.*
82
+ Lightning Bolt {R}
83
+ Instant
84
+ Lightning Bolt deals 3 damage to any target.
83
85
  ```
84
86
 
85
- Reminder text `(like this)` in the middle of rules text is preserved as rules text, not treated as flavor.
86
-
87
- ### Extended Text Spoiler Format
88
-
89
- An art image URL can be specified between the name and type line, amont other things
87
+ ### Creatures
90
88
 
91
89
  ```
92
- Archangel Avacyn {3}{W}{W}
93
- Art: https://cards.scryfall.io/art_crop/front/7/f/7f4893ef.jpg
94
- Rarity: Rare
95
- Legendary Creature — Angel
96
- Flash
97
- Flying, vigilance
98
- 4/4
99
- *Some flavor text*
90
+ Tarmogoyf {1}{G}
91
+ Creature -- Lhurgoyf
92
+ Tarmogoyf's power is equal to the number of card types among cards in all graveyards and its toughness is equal to that number plus 1.
93
+ */1+*
100
94
  ```
101
95
 
102
96
  ### Planeswalkers
103
97
 
104
98
  ```
105
99
  Liliana of the Veil {1}{B}{B}
106
- Legendary Planeswalker Liliana
100
+ Legendary Planeswalker -- Liliana
107
101
  +1: Each player discards a card.
108
102
  -2: Target player sacrifices a creature.
109
103
  -6: Separate all permanents target player controls into two piles.
110
104
  Loyalty: 3
111
105
  ```
112
106
 
113
- Abilities prefixed with `+N:`, `-N:`, or `0:` are parsed as loyalty abilities. Lines without a cost prefix are treated as static abilities.
114
-
115
107
  ### Sagas
116
108
 
117
109
  ```
118
110
  The Eldest Reborn {4}{B}
119
- Enchantment Saga
120
- I Each opponent sacrifices a creature or planeswalker.
121
- II Each opponent discards a card.
122
- III Put target creature or planeswalker card from a graveyard onto the battlefield under your control.
123
- ```
124
-
125
- Chapter numerals (I through VI) are parsed automatically. Combined chapters are supported:
126
-
127
- ```
128
- I, II — Create a 1/1 red Goblin creature token.
129
- III — Creatures you control get +2/+0 until end of turn.
111
+ Enchantment -- Saga
112
+ I -- Each opponent sacrifices a creature or planeswalker.
113
+ II -- Each opponent discards a card.
114
+ III -- Put target creature or planeswalker card from a graveyard onto the battlefield under your control.
130
115
  ```
131
116
 
132
117
  ### Battles
133
118
 
134
119
  ```
135
120
  Invasion of Gobakhan {1}{W}
136
- Battle Siege
137
- When Invasion of Gobakhan enters the battlefield, look at target opponent's hand.
121
+ Battle -- Siege
122
+ When Invasion of Gobakhan enters, look at target opponent's hand.
138
123
  Defense: 3
139
124
  ```
140
125
 
141
- ### Lands (no mana cost)
142
-
143
- ```
144
- Command Tower
145
- Land
146
- {T}: Add one mana of any color in your commander's color identity.
147
- ```
148
-
149
- ## Frame Color
150
-
151
- The frame color is automatically derived:
152
-
153
- | Condition | Frame |
154
- |---|---|
155
- | Type includes "Vehicle" | `v` (vehicle) |
156
- | Type includes "Land" + no mana cost | `l` (land) |
157
- | No colored mana symbols | `a` (artifact/colorless) |
158
- | One color in mana cost | That color (`w`, `u`, `b`, `r`, `g`) |
159
- | Two or more colors | `m` (multicolor/gold) |
160
-
161
- Colors are extracted from all mana symbols including hybrid (`{G/U}`) and phyrexian (`{G/P}`).
162
-
163
- ## Card Dimensions
164
-
165
- | Card Type | Width | Height |
166
- |---|---|---|
167
- | Standard | 2010 | 2814 |
168
- | Planeswalker | 1500 | 2100 |
169
- | Saga | 1500 | 2100 |
170
- | Battle | 2814 | 2010 (landscape) |
171
-
172
- ## Development
173
-
174
- ```bash
175
- npm test # run tests (vitest)
176
- npm run build # compile TypeScript
177
- npm run dev # start local dev server on port 3000
178
- npm run spike # render test cards to output/
179
- ```
180
-
181
- ## Deploy to AWS Lambda
182
-
183
- The project uses [SST](https://sst.dev) to deploy a Lambda function that serves the same UI as the dev server.
184
-
185
- **Prerequisites:** AWS credentials configured (`~/.aws/credentials` or environment variables).
186
-
187
- ```bash
188
- npx sst deploy --stage dev # deploy
189
- npx sst remove --stage dev # tear down
190
- ```
191
-
192
- The deploy output will print a function URL you can open in a browser. The Lambda is configured with 2 GB memory, 30s timeout, and x86_64 architecture (required for `@napi-rs/canvas`).
193
-
194
-
195
- ## TODO
196
-
197
- - Improve set symbol generation with logo
198
- - Fix missing rarity on sagas
199
- - Test limits of parser leniency
200
- - Test reminder text without asterisks
201
- - Test multiple lines of flavor text
202
- - Investigate card dimensions
203
- - [X] Add blurb about Extended Text Spoiler format
204
- - Update readme examples to be custom
205
- - Add a carddata example to quickstart
206
- - Add Class enchantment to spike
207
- - Support Level Up https://scryfall.com/card/c13/43/echo-mage
208
- - note how this affect P/T assumptions...
209
- - Support more hybrid mana
210
- - Phyrexian hybrid
211
- - colorless/color hybrid
212
- - 2/color hybrid
213
- - Support multi-cards
214
- - Enchantment Rooms
215
- - https://scryfall.com/card/dsk/43/bottomless-pool-locker-room
216
- - Fuse cards
217
- - Adventures
218
- - MDFC
219
- - Kamigawa flip cards
220
- - Flip cards (Werewolf, etc.)
221
- - Support Varying P/T
222
- - Leveler Cards
223
- - Prototype
224
- - Support Mutate
225
- - Test harness
226
- - Optimize asset size
227
- - Downsample everything - it's excessive right now
228
- - 744 × 1039 and jpeg to match mtg.design
229
- - 672 × 936 to match scryfall
230
- - Alternatively, procedurally generate textures + frames
231
- - Can we get away with a single frame/format to serve Class, Saga, Case?
232
- - Think we just need an AccentColor enum
233
- - Ponder if card template should be more coarse grained
234
- - Finalize the schema
235
- - Support color indicator
236
- - Support saga creature
237
- - Support the MDFC / Transform triangle indicator.
238
- - Figure out what default set/sequence/collection should be
239
- - Support {11} to {20}
240
- - Support untap symbol {Q}
241
- - Support two color accents & crowns - does CC have these?
242
- - does not look like it...perhaps can use them as masks applied to other renders
243
- - Support the wedge for MDFC or transform cards
244
-
245
- ## Bugs
246
- - Fix planeswalker ability spacing
247
- - Four ability planeswalkers seem to have a different template?
248
- - Fix planeswalker templates to have transparency
249
- - pretty sure our current setup can handle this
250
- - Revamp parser - Flavor Text: or Flavor: X
251
- - More lenient parsing - we can ...
252
- - Fix planeswalker art render positioning X
253
- - Fix common set logo
254
- - Fix colored artifacts using wrong border (do we support accents though?)
255
- - Fix land accents - why is command tower gold?
256
- - Ok I think if it produces multiple colors, the accent changes. Colorless lands have no accent
257
- - This complicates our enums...
258
- - Archway of Innovation - example of other. Same with basics...
259
- - Oh and dryad arbor
260
- - Reminder text does not get rendered in italics. (Anything in parens can be assumed to be reminder text)
261
- - Legendary crown is missing a shadow on the side
262
- - Asterisked text in flavor text should be normal faced, not italic
263
- - Nit: Saga reminder text could be formatted a little better
264
- - For gold and hybrid frames, still use the default for the name and typeline. Same for P/T box
265
- - Lands have distinctive text box backgrounds?
266
- - Make hybrid mana parsing more lenient -
267
- - but normalize it to the correct order. Similar for phyrexian mana
268
- - Update Flavortext parsing
269
- - Multicolored artifact
270
- - Urza's Saga - just straight up broken
271
- - Shrewed Hatchling - P/T box wrong color
272
- - Drayad Arbor - no P/T box. name and type box are wrong color
273
-
274
- - An Unearthly Child - where does that little golden bit come from?
275
- - Ability to override text size??
276
-
277
- - Parsing - should the unstructured PW ability use the PW ability template with '' cost?
278
-
279
- ## Design Decisions
280
- - should we support multicolored as an alias for gold?
281
- - '' single letters as aliases?
282
- - should normalize sort mana values?
283
- - do we support styling in the text format? too complicated and people should just use the JSON format
284
- - Should we infer missing hyphens in type line?
285
- - anything unrecognized is assumed to be a subtype?
286
-
287
- # TODO
288
- - test with LTS
289
- - Add {S} symbol
290
- - Should we expose the name and type line colors? Yeah might as well.
291
-
292
- - Expose function to Derive the art boundaries.
293
-
294
- ## Future Features And Blockers
295
- - Pass in FrameModifier / FrameVariant / FrameStyle
296
- - can be a list or single value. List will round robin??
297
- - Support devoid borders
298
- - Support nyx borders
299
- - Support Snow borders
300
- - Support miracle borders
301
-
302
- - Support various borders
303
- - Support hybrid mana borders
304
- - Support composite cards
305
- - mdfc is fine
306
- - transform - double the crowns?
307
- - flip - another 5 templates. no pinlines?
308
- - Tokens?
309
- - Flavor Name (nickname)
310
-
311
-
312
- # bugs
313
- Arni Slays the Troll - saga drawable areay is rectangle...
314
-
315
- - fix reminder text italics
316
-
317
- https://cran.r-project.org/web/packages/scryr/vignettes/frames.html
318
- - Frame Effects
319
-
320
- - Lesson
321
-
322
- Only way to avoid combinatorial explosion - separate layers
323
-
324
- ## Supported
325
- Battles (only the front face)
326
-
327
- ## Maybe
328
- - Keyrune integration https://keyrune.andrewgioia.com/icons.html
329
-
330
-
331
- # API's
332
- ```typescript
333
- renderCard(cardData: CardData): RenderedCard
334
-
335
- renderCard(text: string): RenderedCard
336
-
337
- parseCard(text: string): CardData
338
- formatCard(cardData: CardData): string
339
-
340
- normalizeCard(cardData: CardData): CardData
341
-
342
-
343
- // TODO maybe make a class and there should just be an aspectRatio property that does the division.
344
- getArtDimensions(cardTemplate: CardTemplate): { widthPixels: number; heightPixels: number; aspectRatioWidth: number; aspectRatioHeight: number; }
345
- ```
346
-
347
- # Plan
348
- Note: IF you need to refer to any borders and assets, you cannot do that yet. they are downloading. But at some point you will be able to refer to Card Conjurer
349
-
350
- 1. [X] Fix everything to use the new apis and types. i.e. fix the build
351
- 1. [X (supposedly)] Harden the text parser
352
- 1. Create test framework whereby the AI can query scryfall for the text, json, art crop, and rendered card. Then we render our own card (using the scryfall art crop for art), then we concatenate our card with the scryfall card and the AI can view them side by side in a single image to allow for excruciating detail comparison.
353
- 1. Add support for composite cards. In the text format as well
354
-
355
- # Decisions
356
- Card normalization - do we help them out with boilerplate reminder text for sagas and classes?
357
- Could we support hybrid borders via draw tools? linear gradient to shift between them?
358
-
359
- # Tech Debt
360
- - The Class level one is still messed up - tried changing it and it got messed up. I think we do need to parse it as level one?
361
-
362
- # React Component
363
- - Let's create a react component for displaying a RenderedCard object
364
- - rotations - you can click likee on scryfall
365
- - Card name rendered in an invisible span so that people can ctrl+F
366
- - right click to copy scryfall text, copy crucible text, copy scryfall json, copy crucible json, copy card image (the face you are currently looking at)
367
- - some way to control zoom/scale (maybe that's just regular styling and the component doesn't need to care?)
368
- - This should be structured so that people can independently import the component or the parser or the renderer. The react component will have a peer dependency on react.
126
+ ### Multi-face cards
369
127
 
370
- devoid cards are full art
128
+ Use `--linkType--` delimiters between faces:
371
129
 
372
-
373
-
374
- Normal form - abilities should be converted to an array?
375
- - should have a Null StructuredAbilities new type of Parsed
376
-
377
- # Parser
378
- Explicit legendCrown field (Has Legend Crown: true/false?)
379
- Refactor stuff under flavor or style
380
-
381
- Maybe CardData should have an Art Alt text. For storing art description?
382
- - Or Art Description
383
- - Should Art be Art URL?
384
- Embed linktype in separator
385
- Define normalized form
386
-
387
- Remove support for asterisk-bounded flavor text
388
-
389
- Normal form - I guess we should use caps for everything? titel case? Avoid conversions...
390
-
391
-
392
- # Renderer
393
- gradient for adventure book thing - that's the accent colors
394
- Support split cards - no mask, just clip
395
- Support MDFC
396
- Fix battles?
397
-
398
-
399
- # Optimization
400
- Convert from O(M*N) to O(M+N)
401
- Extract wubrgla textures
402
- Extract pinlines, etc.
403
-
404
- # Scope Down
405
- - Remove colorless frame
406
- - How to deprecate levelers, etc.?
407
- - Deprecate battles
408
-
409
- # Cruscible Text Format
410
130
  ```
411
131
  Huntmaster of the Fells {2}{R}{G}
412
- Creature Human Werewolf
132
+ Creature -- Human Werewolf
413
133
  Whenever this creature enters or transforms into Huntmaster of the Fells, create a 2/2 green Wolf creature token and you gain 2 life.
414
- At the beginning of each upkeep, if no spells were cast last turn, transform this creature.
415
134
  2/2
416
- Flavor Text: He's a pretty cool guy
417
- wraps around to next line
418
- Art URL: ...
419
- Art Description: ...
420
- Artist: Chris Rain
421
- Has Legend Crown: false
422
- Frame Effect: Normal
423
- Frame Color: Gold
424
- Accent Color: Red and Green
425
- Name Line Color: Gold
426
- Type Line Color: Gold
427
- Set Code: INR
428
- Collector Number: 205
429
- Designer: Mark Rosewater?
430
135
  --transform--
431
136
  Ravager of the Fells
432
- Art URL: ...
433
- Art Description: ...
434
137
  Color Indicator: Red and Green
435
- Creature Werewolf
138
+ Creature -- Werewolf
436
139
  Trample
437
- Whenever this creature transforms into Ravager of the Fells, it deals 2 damage to target opponent or planeswalker and 2 damage to up to one target creature that player or that planeswalker's controller controls.
438
- At the beginning of each upkeep, if a player cast two or more spells last turn, transform this creature.
439
140
  4/4
440
- Flavor Text: Another flavor text
441
141
  ```
442
142
 
443
- # Bugs
444
- accents for lands - also look for basic land names like Forest, etc. So fetch lands will work
445
- Let's support keyword abilities vs. ability words. Ability words are like "Landfall" and are italicized. Keyword abilities are like "Exhaust" and are not italicized. They both appear before hyphens (or M dashes). We will infer which is which by the presence of reminder text. if no reminder text, then it must be an ability word, otherwise it must be a keyword ability. Examples:
446
- "Landfall - Whenever you cast a land spell, this creature gets +1/+1 until end of turn."
447
- - since no reminder text, this is an ability word and the "Landfall" should be italicized
448
- "Exhaust - {3}{R}: Put two +1/+1 counters on this creature. (Activate each exhaust ability only once.)"
449
- - since there is reminder text, this is a keyword ability and the "Exhaust" should not be italicized
450
- - fuse cards need to blend in the bottom reminder text
143
+ Supported link types: `--transform--`, `--mdfc--`, `--split--`, `--fuse--`, `--flip--`, `--adventure--`, `--aftermath--`
451
144
 
452
- - spacing for type line of non-creature flip card is sub-optimal
453
- - fix colorless frames
145
+ A bare `----` delimiter will infer the link type from card content (e.g. "Fuse" in text, both sides being instants/sorceries, presence of "transform" keyword).
454
146
 
455
- # improvements
456
- - for split cards with hybrid mana, do the red white and blue thing...
457
- - Fuse needs to use the mask and write the reminder text
147
+ ### Metadata fields
458
148
 
459
- - support nyx crowns
460
- - make parser more lenient (line order, allow and, oxfoard comma,)
461
- - typeline color / name line color are not independent?
462
- - P/T box color should match with frame
149
+ These can appear on any line (order doesn't matter):
463
150
 
464
- - When providing overrides we should infer as much as possible.
465
- - ex: doing a dual frame, the type lines should inherit?
466
- - Support Nyx crowns
467
- - bullets for modal spells
151
+ | Field | Example |
152
+ |---|---|
153
+ | `Art URL:` | `Art URL: https://example.com/art.png` |
154
+ | `Art Description:` | `Art Description: A fiery landscape` |
155
+ | `Rarity:` | `Rarity: Mythic Rare` |
156
+ | `Flavor Text:` | `Flavor Text: Some italic text` |
157
+ | `Frame Color:` | `Frame Color: Red and Blue` |
158
+ | `Accent Color:` | `Accent Color: Green` |
159
+ | `Frame Effect:` | `Frame Effect: Nyx` |
160
+ | `Color Indicator:` | `Color Indicator: Red and Green` |
161
+ | `Has Legend Crown:` | `Has Legend Crown: true` |
162
+ | `Set Code:` | `Set Code: MH3` |
163
+ | `Collector Number:` | `Collector Number: 205` |
164
+ | `Artist:` | `Artist: Chris Rahn` |
165
+ | `Designer:` | `Designer: Mark Rosewater` |
166
+
167
+ ### Mana symbols
168
+
169
+ Use curly brace notation: `{W}`, `{U}`, `{B}`, `{R}`, `{G}`, `{C}`, `{T}`, `{1}`, `{2}`, etc.
170
+
171
+ Hybrid: `{G/U}`, `{W/B}`. Phyrexian: `{G/P}`, `{R/P}`.
172
+
173
+ ## Supported Templates
174
+
175
+ - Standard (including colorless/Eldrazi full-bleed art)
176
+ - Planeswalker (3 and 4 ability variants)
177
+ - Saga
178
+ - Class
179
+ - Battle
180
+ - Adventure
181
+ - Transform (front and back)
182
+ - MDFC / Modal DFC (front and back)
183
+ - Split
184
+ - Fuse
185
+ - Flip (Kamigawa-style)
186
+ - Aftermath
187
+ - Mutate
188
+ - Prototype
189
+ - Leveler
190
+
191
+ ## React Component
192
+
193
+ ```tsx
194
+ import { MtgCard } from '@domainellipticlanguage/mtg-crucible/react';
195
+
196
+ <MtgCard
197
+ card={renderedCardDisplay}
198
+ cardText="searchable text for ctrl+f"
199
+ rotateWidgetStyle={{ display: 'none' }} // optional: hide rotation arrow
200
+ />
201
+ ```
202
+
203
+ The component supports:
204
+ - Click to cycle through rotations (transform, flip, split, etc.)
205
+ - Rotation arrow widget (Scryfall-style) with hover/click animation
206
+ - Right-click context menu: download, copy image, copy text formats
207
+ - Invisible searchable text overlay for Ctrl+F
208
+ - CSS 3D transforms for card flipping
468
209
 
210
+ ## Development
469
211
 
212
+ ```bash
213
+ npm test # run tests (vitest)
214
+ npm run build # compile TypeScript
215
+ npm run dev # start local dev server with hot reload
216
+ ```
470
217
 
471
- type line and name line color logic is impacting flip cards...
218
+ ## Publishing
472
219
 
473
- Transforming sagas don't work...
474
- I guess we need to add more frames..
220
+ ```bash
221
+ npm login
222
+ npm version patch # or minor/major
223
+ npm run build
224
+ npm publish --access public
225
+ ```
@@ -0,0 +1,3 @@
1
+ export { RARITIES, TEMPLATE_NAMES, COLORS, FRAME_COLORS, FRAME_EFFECTS, SUPERTYPES_LIST, CARD_TYPES, LINK_TYPES, } from '../types';
2
+ export type { Rarity, TemplateName, Color, AccentColor, FrameColor, FrameEffect, Supertype, Type, Subtype, LinkType, PlaneswalkerAbilities, SagaAbilities, ClassAbilities, LevelerAbilities, CaseAbilities, PrototypeAbilities, MutateAbilities, NoneAbilities, StructuredAbilities, ParsedAbilities, CardData, NormalizedCardData, Rotation, RenderedCard, MtgCardDisplayData, } from '../types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/constants/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAC7D,eAAe,EAAE,UAAU,EAAE,UAAU,GACxC,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EACjE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAClC,qBAAqB,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EACtE,aAAa,EAAE,kBAAkB,EAAE,eAAe,EAAE,aAAa,EACjE,mBAAmB,EAAE,eAAe,EACpC,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,EACpD,kBAAkB,GACnB,MAAM,UAAU,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LINK_TYPES = exports.CARD_TYPES = exports.SUPERTYPES_LIST = exports.FRAME_EFFECTS = exports.FRAME_COLORS = exports.COLORS = exports.TEMPLATE_NAMES = exports.RARITIES = void 0;
4
+ // Browser-safe constants module — no Node dependencies
5
+ var types_1 = require("../types");
6
+ Object.defineProperty(exports, "RARITIES", { enumerable: true, get: function () { return types_1.RARITIES; } });
7
+ Object.defineProperty(exports, "TEMPLATE_NAMES", { enumerable: true, get: function () { return types_1.TEMPLATE_NAMES; } });
8
+ Object.defineProperty(exports, "COLORS", { enumerable: true, get: function () { return types_1.COLORS; } });
9
+ Object.defineProperty(exports, "FRAME_COLORS", { enumerable: true, get: function () { return types_1.FRAME_COLORS; } });
10
+ Object.defineProperty(exports, "FRAME_EFFECTS", { enumerable: true, get: function () { return types_1.FRAME_EFFECTS; } });
11
+ Object.defineProperty(exports, "SUPERTYPES_LIST", { enumerable: true, get: function () { return types_1.SUPERTYPES_LIST; } });
12
+ Object.defineProperty(exports, "CARD_TYPES", { enumerable: true, get: function () { return types_1.CARD_TYPES; } });
13
+ Object.defineProperty(exports, "LINK_TYPES", { enumerable: true, get: function () { return types_1.LINK_TYPES; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/constants/index.ts"],"names":[],"mappings":";;;AAAA,uDAAuD;AACvD,kCAGkB;AAFhB,iGAAA,QAAQ,OAAA;AAAE,uGAAA,cAAc,OAAA;AAAE,+FAAA,MAAM,OAAA;AAAE,qGAAA,YAAY,OAAA;AAAE,sGAAA,aAAa,OAAA;AAC7D,wGAAA,eAAe,OAAA;AAAE,mGAAA,UAAU,OAAA;AAAE,mGAAA,UAAU,OAAA"}
@@ -0,0 +1,3 @@
1
+ export { RARITIES, TEMPLATE_NAMES, COLORS, FRAME_COLORS, FRAME_EFFECTS, SUPERTYPES_LIST, CARD_TYPES, LINK_TYPES, } from '../types';
2
+ export type { Rarity, TemplateName, Color, AccentColor, FrameColor, FrameEffect, Supertype, Type, Subtype, LinkType, PlaneswalkerAbilities, SagaAbilities, ClassAbilities, LevelerAbilities, CaseAbilities, PrototypeAbilities, MutateAbilities, NoneAbilities, StructuredAbilities, ParsedAbilities, CardData, NormalizedCardData, Rotation, RenderedCard, MtgCardDisplayData, } from '../types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAC7D,eAAe,EAAE,UAAU,EAAE,UAAU,GACxC,MAAM,UAAU,CAAC;AAElB,YAAY,EACV,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EACjE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAClC,qBAAqB,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EACtE,aAAa,EAAE,kBAAkB,EAAE,eAAe,EAAE,aAAa,EACjE,mBAAmB,EAAE,eAAe,EACpC,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,EACpD,kBAAkB,GACnB,MAAM,UAAU,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LINK_TYPES = exports.CARD_TYPES = exports.SUPERTYPES_LIST = exports.FRAME_EFFECTS = exports.FRAME_COLORS = exports.COLORS = exports.TEMPLATE_NAMES = exports.RARITIES = void 0;
4
+ // Browser-safe constants module — no Node dependencies
5
+ var types_1 = require("../types");
6
+ Object.defineProperty(exports, "RARITIES", { enumerable: true, get: function () { return types_1.RARITIES; } });
7
+ Object.defineProperty(exports, "TEMPLATE_NAMES", { enumerable: true, get: function () { return types_1.TEMPLATE_NAMES; } });
8
+ Object.defineProperty(exports, "COLORS", { enumerable: true, get: function () { return types_1.COLORS; } });
9
+ Object.defineProperty(exports, "FRAME_COLORS", { enumerable: true, get: function () { return types_1.FRAME_COLORS; } });
10
+ Object.defineProperty(exports, "FRAME_EFFECTS", { enumerable: true, get: function () { return types_1.FRAME_EFFECTS; } });
11
+ Object.defineProperty(exports, "SUPERTYPES_LIST", { enumerable: true, get: function () { return types_1.SUPERTYPES_LIST; } });
12
+ Object.defineProperty(exports, "CARD_TYPES", { enumerable: true, get: function () { return types_1.CARD_TYPES; } });
13
+ Object.defineProperty(exports, "LINK_TYPES", { enumerable: true, get: function () { return types_1.LINK_TYPES; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/constants/index.ts"],"names":[],"mappings":";;;AAAA,uDAAuD;AACvD,kCAGkB;AAFhB,iGAAA,QAAQ,OAAA;AAAE,uGAAA,cAAc,OAAA;AAAE,+FAAA,MAAM,OAAA;AAAE,qGAAA,YAAY,OAAA;AAAE,sGAAA,aAAa,OAAA;AAC7D,wGAAA,eAAe,OAAA;AAAE,mGAAA,UAAU,OAAA;AAAE,mGAAA,UAAU,OAAA"}