@cardanvil/frame-kit 0.2.0
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.
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/authoring/index.d.ts +2 -0
- package/dist/authoring/index.js +2 -0
- package/dist/authoring/layoutDefaults.d.ts +6 -0
- package/dist/authoring/layoutDefaults.js +80 -0
- package/dist/authoring/omit.d.ts +2 -0
- package/dist/authoring/omit.js +14 -0
- package/dist/cli/api.d.ts +34 -0
- package/dist/cli/api.js +188 -0
- package/dist/cli/build.d.ts +36 -0
- package/dist/cli/build.js +222 -0
- package/dist/cli/bundle.d.ts +23 -0
- package/dist/cli/bundle.js +58 -0
- package/dist/cli/devUrl.d.ts +10 -0
- package/dist/cli/devUrl.js +41 -0
- package/dist/cli/discover.d.ts +25 -0
- package/dist/cli/discover.js +116 -0
- package/dist/cli/emitSchemas.d.ts +2 -0
- package/dist/cli/emitSchemas.js +13 -0
- package/dist/cli/errors.d.ts +8 -0
- package/dist/cli/errors.js +11 -0
- package/dist/cli/frameIndex.d.ts +115 -0
- package/dist/cli/frameIndex.js +104 -0
- package/dist/cli/hash.d.ts +14 -0
- package/dist/cli/hash.js +32 -0
- package/dist/cli/imageSize.d.ts +13 -0
- package/dist/cli/imageSize.js +72 -0
- package/dist/cli/index.d.ts +18 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/loadFrame.d.ts +24 -0
- package/dist/cli/loadFrame.js +90 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +228 -0
- package/dist/cli/report.d.ts +35 -0
- package/dist/cli/report.js +74 -0
- package/dist/cli/schema.d.ts +33 -0
- package/dist/cli/schema.js +51 -0
- package/dist/cli/stableJson.d.ts +11 -0
- package/dist/cli/stableJson.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/manifest/contract.d.ts +10503 -0
- package/dist/manifest/contract.js +87 -0
- package/dist/manifest/deserialize.d.ts +19 -0
- package/dist/manifest/deserialize.js +23 -0
- package/dist/manifest/index.d.ts +4 -0
- package/dist/manifest/index.js +4 -0
- package/dist/manifest/serialize.d.ts +35 -0
- package/dist/manifest/serialize.js +44 -0
- package/dist/manifest/testFrame.d.ts +9 -0
- package/dist/manifest/testFrame.js +53 -0
- package/dist/manifest/walk.d.ts +21 -0
- package/dist/manifest/walk.js +111 -0
- package/dist/schema/assetUrl.d.ts +27 -0
- package/dist/schema/assetUrl.js +28 -0
- package/dist/schema/frame.d.ts +26467 -0
- package/dist/schema/frame.js +312 -0
- package/dist/schema/frameAssets.d.ts +539 -0
- package/dist/schema/frameAssets.js +183 -0
- package/dist/schema/frameMeta.d.ts +24 -0
- package/dist/schema/frameMeta.js +48 -0
- package/dist/schema/index.d.ts +5 -0
- package/dist/schema/index.js +5 -0
- package/dist/schema/layout.d.ts +101 -0
- package/dist/schema/layout.js +59 -0
- package/package.json +62 -0
- package/schema/frame-index.schema.json +233 -0
- package/schema/frame-meta.schema.json +54 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { AssetUrlSchema } from "./assetUrl.js";
|
|
3
|
+
/**
|
|
4
|
+
* Canonical frame color codes: WUBRG, gold (m), artifact (a), colorless (c),
|
|
5
|
+
* land (l), vehicle (v). Land is always the lowercase "l" — some legacy configs
|
|
6
|
+
* also shipped an uppercase "L" alias pointing at the same art, but the nested
|
|
7
|
+
* configs standardize on "l".
|
|
8
|
+
*/
|
|
9
|
+
export const FRAME_COLORS = [
|
|
10
|
+
"w",
|
|
11
|
+
"u",
|
|
12
|
+
"b",
|
|
13
|
+
"r",
|
|
14
|
+
"g",
|
|
15
|
+
"m",
|
|
16
|
+
"a",
|
|
17
|
+
"c",
|
|
18
|
+
"l",
|
|
19
|
+
"v",
|
|
20
|
+
];
|
|
21
|
+
export const FrameColorEnum = z.enum(FRAME_COLORS);
|
|
22
|
+
/** Image URLs keyed by frame color. Every frame family is a (partial) color set. */
|
|
23
|
+
export const ColorSetSchema = z.partialRecord(FrameColorEnum, AssetUrlSchema);
|
|
24
|
+
/**
|
|
25
|
+
* Scryfall `frame_effects` values that name a specific transform-DFC icon
|
|
26
|
+
* variant (mirrors `ScryfallFrameEffect`'s `*Dfc` members, plus `fandfc`).
|
|
27
|
+
*/
|
|
28
|
+
export const DFC_ICON_EFFECTS = [
|
|
29
|
+
"convertdfc",
|
|
30
|
+
"sunmoondfc",
|
|
31
|
+
"compasslanddfc",
|
|
32
|
+
"originpwdfc",
|
|
33
|
+
"mooneldrazidfc",
|
|
34
|
+
"waxingandwaningmoondfc",
|
|
35
|
+
"fandfc",
|
|
36
|
+
"upsidedowndfc",
|
|
37
|
+
];
|
|
38
|
+
export const DfcIconEffectEnum = z.enum(DFC_ICON_EFFECTS);
|
|
39
|
+
/** DFC icon image URLs keyed by which `frame_effects` variant they render. */
|
|
40
|
+
export const DfcIconSetSchema = z.partialRecord(DfcIconEffectEnum, AssetUrlSchema);
|
|
41
|
+
/**
|
|
42
|
+
* Base frames additionally allow "c2" — the hybrid title/type wash used by the
|
|
43
|
+
* two-color hybrid split rendering. "c2" only ever exists in the base family.
|
|
44
|
+
*/
|
|
45
|
+
export const BASE_COLORS = [...FRAME_COLORS, "c2"];
|
|
46
|
+
export const BaseColorEnum = z.enum(BASE_COLORS);
|
|
47
|
+
export const BaseColorSetSchema = z.partialRecord(BaseColorEnum, AssetUrlSchema);
|
|
48
|
+
/** Legendary-crown asset sets, grouped by variant. */
|
|
49
|
+
export const CrownAssetsSchema = z.object({
|
|
50
|
+
base: ColorSetSchema.optional(),
|
|
51
|
+
ub: ColorSetSchema.optional(),
|
|
52
|
+
nickname: ColorSetSchema.optional(),
|
|
53
|
+
nicknameUb: ColorSetSchema.optional(),
|
|
54
|
+
nyxInsert: ColorSetSchema.optional(),
|
|
55
|
+
nyxInsertUb: ColorSetSchema.optional(),
|
|
56
|
+
});
|
|
57
|
+
/** Mask pair keyed by frame size (regular = 3-ability, tall = 4-ability). */
|
|
58
|
+
const PlaneswalkerMasksSchema = z.object({
|
|
59
|
+
regular: AssetUrlSchema.optional(),
|
|
60
|
+
tall: AssetUrlSchema.optional(),
|
|
61
|
+
});
|
|
62
|
+
/** Planeswalker ability-icon assets. */
|
|
63
|
+
export const PlaneswalkerAssetConfigSchema = z.object({
|
|
64
|
+
plus: AssetUrlSchema,
|
|
65
|
+
minus: AssetUrlSchema,
|
|
66
|
+
zero: AssetUrlSchema,
|
|
67
|
+
separator: AssetUrlSchema,
|
|
68
|
+
startingLoyalty: AssetUrlSchema,
|
|
69
|
+
/** Ability box masks. */
|
|
70
|
+
masks: PlaneswalkerMasksSchema.optional(),
|
|
71
|
+
/** Colorless-frame overlay masks (nickname banner cutout). */
|
|
72
|
+
colorlessNicknameMasks: PlaneswalkerMasksSchema.optional(),
|
|
73
|
+
});
|
|
74
|
+
const BannerMasksSchema = z.object({
|
|
75
|
+
right: AssetUrlSchema,
|
|
76
|
+
stripe: AssetUrlSchema,
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Saga ability badge/divider + per-color chapter banners. Read-ahead cards use
|
|
80
|
+
* the `bannerTransform`/`bannerTransformMasks` variants. Banner masks live here
|
|
81
|
+
* (not in `masks`) because they are per-banner-variant art wired straight into
|
|
82
|
+
* the saga node, never resolved through the layout-mask path.
|
|
83
|
+
*/
|
|
84
|
+
export const SagaAssetConfigSchema = z.object({
|
|
85
|
+
badge: AssetUrlSchema,
|
|
86
|
+
divider: AssetUrlSchema,
|
|
87
|
+
banner: ColorSetSchema.optional(),
|
|
88
|
+
bannerTransform: ColorSetSchema.optional(),
|
|
89
|
+
bannerMasks: BannerMasksSchema.optional(),
|
|
90
|
+
bannerTransformMasks: BannerMasksSchema.optional(),
|
|
91
|
+
bannerReadAheadMasks: BannerMasksSchema.optional(),
|
|
92
|
+
});
|
|
93
|
+
/** The full frame-asset tree for a layout. */
|
|
94
|
+
export const FrameAssetsSchema = z.object({
|
|
95
|
+
base: BaseColorSetSchema,
|
|
96
|
+
/** Planeswalker 4+-ability ("tall") variants of the base color frames. */
|
|
97
|
+
tall: ColorSetSchema.optional(),
|
|
98
|
+
land: ColorSetSchema.optional(),
|
|
99
|
+
nyx: ColorSetSchema.optional(),
|
|
100
|
+
/**
|
|
101
|
+
* Textless creature variant of the base frames — has a baked-in PT plate
|
|
102
|
+
* notch, used instead of `base` when the card has power/toughness (see
|
|
103
|
+
* `renderCardToCanvas`).
|
|
104
|
+
*/
|
|
105
|
+
creature: ColorSetSchema.optional(),
|
|
106
|
+
pt: ColorSetSchema.optional(),
|
|
107
|
+
crown: CrownAssetsSchema.optional(),
|
|
108
|
+
nickname: ColorSetSchema.optional(),
|
|
109
|
+
/** Token name-plate banner, drawn over the frame in place of the normal title. */
|
|
110
|
+
tokenTitle: ColorSetSchema.optional(),
|
|
111
|
+
/** Crown black backing strip. */
|
|
112
|
+
black: AssetUrlSchema.optional(),
|
|
113
|
+
planeswalker: PlaneswalkerAssetConfigSchema.optional(),
|
|
114
|
+
saga: SagaAssetConfigSchema.optional(),
|
|
115
|
+
/** Extended-art overlay drawn on top of the masked frame. */
|
|
116
|
+
extendedOverlay: AssetUrlSchema.optional(),
|
|
117
|
+
/**
|
|
118
|
+
* Legacy single DFC icon overlaid on the crown of transform cards. Superseded
|
|
119
|
+
* by `dfcIconSet` (resolved from the card's actual `frame_effects`); kept as
|
|
120
|
+
* a fallback for any layout that hasn't shipped the full icon set.
|
|
121
|
+
*/
|
|
122
|
+
dfcIcon: AssetUrlSchema.optional(),
|
|
123
|
+
/**
|
|
124
|
+
* Transform DFC icons keyed by `frame_effects` variant (e.g. "sunmoondfc",
|
|
125
|
+
* "convertdfc") — the icon actually drawn is whichever variant the card's
|
|
126
|
+
* own `frame_effects` names, so every transform card gets its correct mark
|
|
127
|
+
* (not just legendary ones, and not always the "convertdfc" mark).
|
|
128
|
+
*/
|
|
129
|
+
dfcIconSet: DfcIconSetSchema.optional(),
|
|
130
|
+
/**
|
|
131
|
+
* Per-color DFC icons (modal DFC) — keyed by the *other* face's frame color,
|
|
132
|
+
* so the front face shows the back's indicator and vice versa. Used instead
|
|
133
|
+
* of the single `dfcIcon` image when the layout ships per-color icons.
|
|
134
|
+
*/
|
|
135
|
+
dfcIconColorSet: ColorSetSchema.optional(),
|
|
136
|
+
/**
|
|
137
|
+
* Modal DFC "flipside" badges — small per-color badge art drawn in the
|
|
138
|
+
* bottom corner of the rules area showing the *other* face's frame color.
|
|
139
|
+
* The front face uses the badge for the back face's color, and vice versa.
|
|
140
|
+
* Keyed by frame color (w/u/b/r/g/m/a/c/l).
|
|
141
|
+
*/
|
|
142
|
+
flipside: ColorSetSchema.optional(),
|
|
143
|
+
/**
|
|
144
|
+
* Crown icon drawn on Lesson cards (rendered as a Transform front face —
|
|
145
|
+
* see `getCardLayout`) in place of the usual `dfcIconSet`/`dfcIcon` mark.
|
|
146
|
+
*/
|
|
147
|
+
lessonIcon: AssetUrlSchema.optional(),
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
150
|
+
* Section masks the renderer actually reads. `rightHalf` overrides the global
|
|
151
|
+
* default (`globalMasks.rightHalf`) when a layout ships its own. The legacy
|
|
152
|
+
* `frame`/`border` mask keys are intentionally absent — nothing reads them.
|
|
153
|
+
*/
|
|
154
|
+
export const LayoutMasksSchema = z.object({
|
|
155
|
+
pinlines: AssetUrlSchema.optional(),
|
|
156
|
+
titleAndType: AssetUrlSchema.optional(),
|
|
157
|
+
rules: AssetUrlSchema.optional(),
|
|
158
|
+
noBorder: AssetUrlSchema.optional(),
|
|
159
|
+
legendary: AssetUrlSchema.optional(),
|
|
160
|
+
/** Legendary crown mask variant for transforming MDFC front faces — the
|
|
161
|
+
* composited top-half frame uses a different legendary crown cutout than
|
|
162
|
+
* the regular MDFC legendary mask. See `drawCardToStage.ts`. */
|
|
163
|
+
legendaryTransform: AssetUrlSchema.optional(),
|
|
164
|
+
/** No-border mask variant for transforming MDFC front faces — the
|
|
165
|
+
* composited top-half frame uses a different no-border cutout than the
|
|
166
|
+
* regular MDFC no-border mask. See `drawCardToStage.ts`. */
|
|
167
|
+
noBorderTransform: AssetUrlSchema.optional(),
|
|
168
|
+
rightHalf: AssetUrlSchema.optional(),
|
|
169
|
+
title: AssetUrlSchema.optional(),
|
|
170
|
+
type: AssetUrlSchema.optional(),
|
|
171
|
+
keyword: AssetUrlSchema.optional(),
|
|
172
|
+
/** Extended-art cutout mask applied to the combined frame. */
|
|
173
|
+
extended: AssetUrlSchema.optional(),
|
|
174
|
+
/**
|
|
175
|
+
* Half-frame cutouts for compositing two faces into one frame: the front
|
|
176
|
+
* face's frame is trimmed to its top half and composited with the back
|
|
177
|
+
* face's frame trimmed to its bottom half, so a single-face layout reads
|
|
178
|
+
* as one frame built from both faces. See `getCardLayout` and
|
|
179
|
+
* `drawCardToStage`.
|
|
180
|
+
*/
|
|
181
|
+
topHalf: AssetUrlSchema.optional(),
|
|
182
|
+
bottomHalf: AssetUrlSchema.optional(),
|
|
183
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The marker file that says "a frame lives in this directory".
|
|
4
|
+
*
|
|
5
|
+
* Packaging has to work both in a pnpm workspace of packages (Card Anvil's own
|
|
6
|
+
* frames) and in a flat single-package repository (the author template), so
|
|
7
|
+
* frames are found by looking for this file rather than by guessing at a
|
|
8
|
+
* directory convention.
|
|
9
|
+
*/
|
|
10
|
+
export declare const FRAME_META_FILENAME = "frame.meta.json";
|
|
11
|
+
export declare const FrameMetaSchema: z.ZodObject<{
|
|
12
|
+
$schema: z.ZodOptional<z.ZodString>;
|
|
13
|
+
id: z.ZodString;
|
|
14
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
15
|
+
entry: z.ZodDefault<z.ZodString>;
|
|
16
|
+
export: z.ZodDefault<z.ZodString>;
|
|
17
|
+
author: z.ZodObject<{
|
|
18
|
+
name: z.ZodString;
|
|
19
|
+
url: z.ZodOptional<z.ZodURL>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
license: z.ZodString;
|
|
22
|
+
homepage: z.ZodOptional<z.ZodURL>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export type FrameMeta = z.infer<typeof FrameMetaSchema>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The marker file that says "a frame lives in this directory".
|
|
4
|
+
*
|
|
5
|
+
* Packaging has to work both in a pnpm workspace of packages (Card Anvil's own
|
|
6
|
+
* frames) and in a flat single-package repository (the author template), so
|
|
7
|
+
* frames are found by looking for this file rather than by guessing at a
|
|
8
|
+
* directory convention.
|
|
9
|
+
*/
|
|
10
|
+
export const FRAME_META_FILENAME = "frame.meta.json";
|
|
11
|
+
/** Reverse-DNS, e.g. `com.example.parchment`. */
|
|
12
|
+
const IdSchema = z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^[a-z0-9]+(\.[a-z0-9-]+)+$/, "must be reverse-DNS, lower case, e.g. com.example.parchment");
|
|
15
|
+
/** Lower-case kebab, used for filenames. */
|
|
16
|
+
const SlugSchema = z
|
|
17
|
+
.string()
|
|
18
|
+
.regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, "must be lower-case kebab-case");
|
|
19
|
+
export const FrameMetaSchema = z.object({
|
|
20
|
+
$schema: z.string().optional(),
|
|
21
|
+
/**
|
|
22
|
+
* Stable identity. Never changes across releases — it is what an installed
|
|
23
|
+
* frame is keyed by, and what a marketplace binds to a repository.
|
|
24
|
+
*/
|
|
25
|
+
id: IdSchema,
|
|
26
|
+
/**
|
|
27
|
+
* Names the bundle file. Defaults to the directory basename, so it only
|
|
28
|
+
* needs setting when the directory is named something else (Card Anvil's
|
|
29
|
+
* `packages/frame-m15` ships as `m15`).
|
|
30
|
+
*/
|
|
31
|
+
slug: SlugSchema.optional(),
|
|
32
|
+
/** Module exporting the frame, relative to this file. */
|
|
33
|
+
entry: z.string().default("./index.ts"),
|
|
34
|
+
/** Named export to read from `entry`. */
|
|
35
|
+
export: z.string().default("default"),
|
|
36
|
+
author: z.object({
|
|
37
|
+
name: z.string().min(1),
|
|
38
|
+
url: z.url().optional(),
|
|
39
|
+
}),
|
|
40
|
+
/**
|
|
41
|
+
* How the frame's art is licensed. Required because it travels into the
|
|
42
|
+
* distributable index where anyone installing the frame can read it, and
|
|
43
|
+
* nobody else can answer it for you. SPDX identifiers are preferred;
|
|
44
|
+
* `NOASSERTION` is the SPDX way to say no claim is being made.
|
|
45
|
+
*/
|
|
46
|
+
license: z.string().min(1),
|
|
47
|
+
homepage: z.url().optional(),
|
|
48
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors `ScryfallLayout` from `@scryfall/api-types`.
|
|
4
|
+
*
|
|
5
|
+
* Inlined as a literal on purpose: that package ships TypeScript source rather
|
|
6
|
+
* than compiled JavaScript (`"main": "index.ts"`) and exports a runtime enum,
|
|
7
|
+
* so depending on it would make this package unusable outside a bundler that
|
|
8
|
+
* transpiles `node_modules`. Card Anvil's `src/models/layout.sync.test.ts`
|
|
9
|
+
* fails if the two ever drift.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SCRYFALL_LAYOUTS: readonly ["normal", "split", "flip", "transform", "modal_dfc", "meld", "leveler", "class", "saga", "adventure", "mutate", "prototype", "battle", "planar", "scheme", "vanguard", "token", "double_faced_token", "emblem", "augment", "host", "art_series", "reversible_card"];
|
|
12
|
+
export type ScryfallLayoutName = (typeof SCRYFALL_LAYOUTS)[number];
|
|
13
|
+
/**
|
|
14
|
+
* Card shapes that Scryfall does not distinguish but frames must: a
|
|
15
|
+
* planeswalker and a Case are `normal`/`class` to Scryfall, yet need their own
|
|
16
|
+
* box geometry. Crossed with every Scryfall layout to form `<layout>_<special>`.
|
|
17
|
+
*/
|
|
18
|
+
export declare const specialLayouts: readonly ["planeswalker", "case"];
|
|
19
|
+
/**
|
|
20
|
+
* Explicit additional layouts not covered by the `specialLayouts` cross-product.
|
|
21
|
+
* `saga_transform`/`saga_transform_creature` cover a transform DFC where EITHER
|
|
22
|
+
* face is a Saga — which physical side it is on varies per card and is resolved
|
|
23
|
+
* at render time, not by separate keys.
|
|
24
|
+
*/
|
|
25
|
+
export declare const additionalLayouts: readonly ["saga_creature", "saga_transform", "saga_transform_creature"];
|
|
26
|
+
export declare const layouts: readonly ["normal", "split", "flip", "transform", "modal_dfc", "meld", "leveler", "class", "saga", "adventure", "mutate", "prototype", "battle", "planar", "scheme", "vanguard", "token", "double_faced_token", "emblem", "augment", "host", "art_series", "reversible_card", ...("transform_planeswalker" | "transform_case" | "saga_planeswalker" | "saga_case" | "normal_planeswalker" | "normal_case" | "split_planeswalker" | "split_case" | "flip_planeswalker" | "flip_case" | "modal_dfc_planeswalker" | "modal_dfc_case" | "meld_planeswalker" | "meld_case" | "leveler_planeswalker" | "leveler_case" | "class_planeswalker" | "class_case" | "adventure_planeswalker" | "adventure_case" | "mutate_planeswalker" | "mutate_case" | "prototype_planeswalker" | "prototype_case" | "battle_planeswalker" | "battle_case" | "planar_planeswalker" | "planar_case" | "scheme_planeswalker" | "scheme_case" | "vanguard_planeswalker" | "vanguard_case" | "token_planeswalker" | "token_case" | "double_faced_token_planeswalker" | "double_faced_token_case" | "emblem_planeswalker" | "emblem_case" | "augment_planeswalker" | "augment_case" | "host_planeswalker" | "host_case" | "art_series_planeswalker" | "art_series_case" | "reversible_card_planeswalker" | "reversible_card_case")[], "saga_creature", "saga_transform", "saga_transform_creature"];
|
|
27
|
+
export declare const LayoutEnum: z.ZodEnum<{
|
|
28
|
+
transform: "transform";
|
|
29
|
+
saga: "saga";
|
|
30
|
+
normal: "normal";
|
|
31
|
+
split: "split";
|
|
32
|
+
flip: "flip";
|
|
33
|
+
modal_dfc: "modal_dfc";
|
|
34
|
+
meld: "meld";
|
|
35
|
+
leveler: "leveler";
|
|
36
|
+
class: "class";
|
|
37
|
+
adventure: "adventure";
|
|
38
|
+
mutate: "mutate";
|
|
39
|
+
prototype: "prototype";
|
|
40
|
+
battle: "battle";
|
|
41
|
+
planar: "planar";
|
|
42
|
+
scheme: "scheme";
|
|
43
|
+
vanguard: "vanguard";
|
|
44
|
+
token: "token";
|
|
45
|
+
double_faced_token: "double_faced_token";
|
|
46
|
+
emblem: "emblem";
|
|
47
|
+
augment: "augment";
|
|
48
|
+
host: "host";
|
|
49
|
+
art_series: "art_series";
|
|
50
|
+
reversible_card: "reversible_card";
|
|
51
|
+
transform_planeswalker: "transform_planeswalker";
|
|
52
|
+
transform_case: "transform_case";
|
|
53
|
+
saga_planeswalker: "saga_planeswalker";
|
|
54
|
+
saga_case: "saga_case";
|
|
55
|
+
normal_planeswalker: "normal_planeswalker";
|
|
56
|
+
normal_case: "normal_case";
|
|
57
|
+
split_planeswalker: "split_planeswalker";
|
|
58
|
+
split_case: "split_case";
|
|
59
|
+
flip_planeswalker: "flip_planeswalker";
|
|
60
|
+
flip_case: "flip_case";
|
|
61
|
+
modal_dfc_planeswalker: "modal_dfc_planeswalker";
|
|
62
|
+
modal_dfc_case: "modal_dfc_case";
|
|
63
|
+
meld_planeswalker: "meld_planeswalker";
|
|
64
|
+
meld_case: "meld_case";
|
|
65
|
+
leveler_planeswalker: "leveler_planeswalker";
|
|
66
|
+
leveler_case: "leveler_case";
|
|
67
|
+
class_planeswalker: "class_planeswalker";
|
|
68
|
+
class_case: "class_case";
|
|
69
|
+
adventure_planeswalker: "adventure_planeswalker";
|
|
70
|
+
adventure_case: "adventure_case";
|
|
71
|
+
mutate_planeswalker: "mutate_planeswalker";
|
|
72
|
+
mutate_case: "mutate_case";
|
|
73
|
+
prototype_planeswalker: "prototype_planeswalker";
|
|
74
|
+
prototype_case: "prototype_case";
|
|
75
|
+
battle_planeswalker: "battle_planeswalker";
|
|
76
|
+
battle_case: "battle_case";
|
|
77
|
+
planar_planeswalker: "planar_planeswalker";
|
|
78
|
+
planar_case: "planar_case";
|
|
79
|
+
scheme_planeswalker: "scheme_planeswalker";
|
|
80
|
+
scheme_case: "scheme_case";
|
|
81
|
+
vanguard_planeswalker: "vanguard_planeswalker";
|
|
82
|
+
vanguard_case: "vanguard_case";
|
|
83
|
+
token_planeswalker: "token_planeswalker";
|
|
84
|
+
token_case: "token_case";
|
|
85
|
+
double_faced_token_planeswalker: "double_faced_token_planeswalker";
|
|
86
|
+
double_faced_token_case: "double_faced_token_case";
|
|
87
|
+
emblem_planeswalker: "emblem_planeswalker";
|
|
88
|
+
emblem_case: "emblem_case";
|
|
89
|
+
augment_planeswalker: "augment_planeswalker";
|
|
90
|
+
augment_case: "augment_case";
|
|
91
|
+
host_planeswalker: "host_planeswalker";
|
|
92
|
+
host_case: "host_case";
|
|
93
|
+
art_series_planeswalker: "art_series_planeswalker";
|
|
94
|
+
art_series_case: "art_series_case";
|
|
95
|
+
reversible_card_planeswalker: "reversible_card_planeswalker";
|
|
96
|
+
reversible_card_case: "reversible_card_case";
|
|
97
|
+
saga_creature: "saga_creature";
|
|
98
|
+
saga_transform: "saga_transform";
|
|
99
|
+
saga_transform_creature: "saga_transform_creature";
|
|
100
|
+
}>;
|
|
101
|
+
export type Layout = z.infer<typeof LayoutEnum>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Mirrors `ScryfallLayout` from `@scryfall/api-types`.
|
|
4
|
+
*
|
|
5
|
+
* Inlined as a literal on purpose: that package ships TypeScript source rather
|
|
6
|
+
* than compiled JavaScript (`"main": "index.ts"`) and exports a runtime enum,
|
|
7
|
+
* so depending on it would make this package unusable outside a bundler that
|
|
8
|
+
* transpiles `node_modules`. Card Anvil's `src/models/layout.sync.test.ts`
|
|
9
|
+
* fails if the two ever drift.
|
|
10
|
+
*/
|
|
11
|
+
export const SCRYFALL_LAYOUTS = [
|
|
12
|
+
"normal",
|
|
13
|
+
"split",
|
|
14
|
+
"flip",
|
|
15
|
+
"transform",
|
|
16
|
+
"modal_dfc",
|
|
17
|
+
"meld",
|
|
18
|
+
"leveler",
|
|
19
|
+
"class",
|
|
20
|
+
"saga",
|
|
21
|
+
"adventure",
|
|
22
|
+
"mutate",
|
|
23
|
+
"prototype",
|
|
24
|
+
"battle",
|
|
25
|
+
"planar",
|
|
26
|
+
"scheme",
|
|
27
|
+
"vanguard",
|
|
28
|
+
"token",
|
|
29
|
+
"double_faced_token",
|
|
30
|
+
"emblem",
|
|
31
|
+
"augment",
|
|
32
|
+
"host",
|
|
33
|
+
"art_series",
|
|
34
|
+
"reversible_card",
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Card shapes that Scryfall does not distinguish but frames must: a
|
|
38
|
+
* planeswalker and a Case are `normal`/`class` to Scryfall, yet need their own
|
|
39
|
+
* box geometry. Crossed with every Scryfall layout to form `<layout>_<special>`.
|
|
40
|
+
*/
|
|
41
|
+
export const specialLayouts = ["planeswalker", "case"];
|
|
42
|
+
const customLayouts = SCRYFALL_LAYOUTS.flatMap((layout) => specialLayouts.map((special) => `${layout}_${special}`));
|
|
43
|
+
/**
|
|
44
|
+
* Explicit additional layouts not covered by the `specialLayouts` cross-product.
|
|
45
|
+
* `saga_transform`/`saga_transform_creature` cover a transform DFC where EITHER
|
|
46
|
+
* face is a Saga — which physical side it is on varies per card and is resolved
|
|
47
|
+
* at render time, not by separate keys.
|
|
48
|
+
*/
|
|
49
|
+
export const additionalLayouts = [
|
|
50
|
+
"saga_creature",
|
|
51
|
+
"saga_transform",
|
|
52
|
+
"saga_transform_creature",
|
|
53
|
+
];
|
|
54
|
+
export const layouts = [
|
|
55
|
+
...SCRYFALL_LAYOUTS,
|
|
56
|
+
...customLayouts,
|
|
57
|
+
...additionalLayouts,
|
|
58
|
+
];
|
|
59
|
+
export const LayoutEnum = z.enum(layouts);
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cardanvil/frame-kit",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The contract Card Anvil frames are authored against: schemas, types, authoring helpers, and the distributable manifest format.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Card-Anvil/frames.git",
|
|
11
|
+
"directory": "packages/frame-kit"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"schema",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./manifest": {
|
|
24
|
+
"types": "./dist/manifest/index.d.ts",
|
|
25
|
+
"default": "./dist/manifest/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./packaging": {
|
|
28
|
+
"types": "./dist/cli/index.d.ts",
|
|
29
|
+
"default": "./dist/cli/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./package.json": "./package.json"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"vite": "^6 || ^7",
|
|
35
|
+
"zod": "^4"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22"
|
|
42
|
+
},
|
|
43
|
+
"peerDependenciesMeta": {
|
|
44
|
+
"vite": {
|
|
45
|
+
"optional": true
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^24.13.2",
|
|
50
|
+
"@types/yazl": "^3.3.1",
|
|
51
|
+
"vite": "^7.3.6"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"yazl": "^3.3.1"
|
|
55
|
+
},
|
|
56
|
+
"bin": {
|
|
57
|
+
"frame-kit": "./dist/cli/main.js"
|
|
58
|
+
},
|
|
59
|
+
"scripts": {
|
|
60
|
+
"build": "tsc -p tsconfig.build.json && node dist/cli/emitSchemas.js"
|
|
61
|
+
}
|
|
62
|
+
}
|