👁️
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

ingest keyrune

+563 -3
public/fonts/keyrune/keyrune.woff2

This is a binary file and will not be displayed.

+138 -3
scripts/download-scryfall.ts
··· 34 34 const OUTPUT_DIR = join(__dirname, "../public/data"); 35 35 const CARDS_DIR = join(OUTPUT_DIR, "cards"); 36 36 const SYMBOLS_DIR = join(__dirname, "../public/symbols"); 37 + const KEYRUNE_DIR = join(__dirname, "../public/fonts/keyrune"); 37 38 const TEMP_DIR = join(__dirname, "../.cache"); 38 39 39 40 // Fields to keep from Scryfall data ··· 956 957 return { count: symbology.data.length, names: symbolNames }; 957 958 } 958 959 960 + interface KeyruneResult { 961 + version: string; 962 + setCount: number; 963 + } 964 + 965 + /** 966 + * Downloads Keyrune set symbol font from GitHub release 967 + * https://github.com/andrewgioia/keyrune 968 + * 969 + * Font licensed under SIL OFL 1.1, CSS under MIT 970 + */ 971 + async function downloadKeyrune(offline: boolean): Promise<KeyruneResult> { 972 + const cssCachePath = join(TEMP_DIR, "keyrune.css"); 973 + const versionCachePath = join(TEMP_DIR, "keyrune-version.json"); 974 + 975 + let version = "unknown"; 976 + 977 + if (!offline) { 978 + console.log("Checking Keyrune version..."); 979 + 980 + // Get latest release tag 981 + const releaseInfo = await fetchJSON<{ tag_name: string }>( 982 + "https://api.github.com/repos/andrewgioia/keyrune/releases/latest", 983 + ); 984 + version = releaseInfo.tag_name; 985 + const keyruneBase = `https://raw.githubusercontent.com/andrewgioia/keyrune/${version}`; 986 + 987 + console.log(`Latest Keyrune version: ${version}`); 988 + 989 + // Check if we already have this version 990 + let needsDownload = true; 991 + try { 992 + const cached = JSON.parse( 993 + await readFile(versionCachePath, "utf-8"), 994 + ) as { version: string }; 995 + 996 + if (cached.version === version) { 997 + console.log(`✓ Already have Keyrune ${version}, skipping download`); 998 + needsDownload = false; 999 + } else { 1000 + console.log(`Local version: ${cached.version}, updating...`); 1001 + } 1002 + } catch { 1003 + console.log("No cached Keyrune found, downloading..."); 1004 + } 1005 + 1006 + if (needsDownload) { 1007 + await mkdir(KEYRUNE_DIR, { recursive: true }); 1008 + await mkdir(TEMP_DIR, { recursive: true }); 1009 + 1010 + // Download woff2 font to public 1011 + await downloadFile( 1012 + `${keyruneBase}/fonts/keyrune.woff2`, 1013 + join(KEYRUNE_DIR, "keyrune.woff2"), 1014 + ); 1015 + 1016 + // Download CSS to cache for processing 1017 + console.log(`Fetching: ${keyruneBase}/css/keyrune.css`); 1018 + const cssResponse = await fetch(`${keyruneBase}/css/keyrune.css`); 1019 + if (!cssResponse.ok) { 1020 + throw new Error(`HTTP ${cssResponse.status}: ${cssResponse.statusText}`); 1021 + } 1022 + const css = await cssResponse.text(); 1023 + await writeFile(cssCachePath, css); 1024 + 1025 + // Cache version 1026 + await writeFile(versionCachePath, JSON.stringify({ version })); 1027 + } 1028 + } else { 1029 + // Offline mode: read cached version 1030 + try { 1031 + const cached = JSON.parse( 1032 + await readFile(versionCachePath, "utf-8"), 1033 + ) as { version: string }; 1034 + version = cached.version; 1035 + console.log(`Using cached Keyrune ${version}`); 1036 + } catch { 1037 + console.log("Using cached Keyrune (unknown version)"); 1038 + } 1039 + } 1040 + 1041 + // Process: extract mappings from cached CSS 1042 + const css = await readFile(cssCachePath, "utf-8"); 1043 + 1044 + const mappings: Record<string, number> = {}; 1045 + const regex = /\.ss-([a-z0-9]+):before\s*\{\s*content:\s*"\\([0-9a-f]+)"/gi; 1046 + let match: RegExpExecArray | null; 1047 + 1048 + while ((match = regex.exec(css)) !== null) { 1049 + const [, setCode, codepoint] = match; 1050 + mappings[setCode] = parseInt(codepoint, 16); 1051 + } 1052 + 1053 + const setCount = Object.keys(mappings).length; 1054 + console.log(`Extracted ${setCount} set symbol mappings`); 1055 + 1056 + // Generate TypeScript file 1057 + const tsContent = `/** 1058 + * Keyrune set symbol unicode mappings 1059 + * Auto-generated by scripts/download-scryfall.ts from Keyrune ${version} 1060 + * https://github.com/andrewgioia/keyrune 1061 + * 1062 + * Font licensed under SIL OFL 1.1 1063 + */ 1064 + 1065 + export const SET_SYMBOLS: Record<string, number> = { 1066 + ${Object.entries(mappings) 1067 + .sort(([a], [b]) => a.localeCompare(b)) 1068 + .map(([code, cp]) => `\t${JSON.stringify(code)}: 0x${cp.toString(16)},`) 1069 + .join("\n")} 1070 + }; 1071 + 1072 + /** 1073 + * Get unicode character for a set symbol 1074 + * Returns empty string if set not found 1075 + */ 1076 + export function getSetSymbol(setCode: string): string { 1077 + const codepoint = SET_SYMBOLS[setCode.toLowerCase()]; 1078 + return codepoint ? String.fromCodePoint(codepoint) : ""; 1079 + } 1080 + `; 1081 + 1082 + const tsPath = join(__dirname, "../src/lib/set-symbols.ts"); 1083 + await writeFile(tsPath, tsContent); 1084 + console.log(`Wrote set symbols: ${tsPath}`); 1085 + 1086 + console.log(`✓ Keyrune ${version}: ${setCount} set symbols`); 1087 + return { version, setCount }; 1088 + } 1089 + 959 1090 async function main(): Promise<void> { 960 1091 try { 961 1092 const offline = process.argv.includes("--offline"); ··· 964 1095 965 1096 if (offline) { 966 1097 console.log("Running in offline mode (reprocessing only)\n"); 967 - const [cards, cachedSymbols] = await Promise.all([ 1098 + const [cards, cachedSymbols, keyrune] = await Promise.all([ 968 1099 processBulkData(true), 969 1100 getCachedSymbolNames(), 1101 + downloadKeyrune(true), 970 1102 ]); 971 1103 972 1104 await writeManifest( ··· 978 1110 979 1111 console.log("\n=== Summary ==="); 980 1112 console.log(`Cards: ${cards.data.cardCount.toLocaleString()}`); 1113 + console.log(`Set symbols: ${keyrune.setCount.toLocaleString()}`); 981 1114 console.log(`Version: ${cards.data.version}`); 982 1115 console.log("\n✓ Done!"); 983 1116 } else { 984 - const [cards, migrations, symbols] = await Promise.all([ 1117 + const [cards, migrations, symbols, keyrune] = await Promise.all([ 985 1118 processBulkData(false), 986 1119 processMigrations(), 987 1120 downloadSymbols(), 1121 + downloadKeyrune(false), 988 1122 ]); 989 1123 990 1124 await writeManifest( ··· 997 1131 console.log("\n=== Summary ==="); 998 1132 console.log(`Cards: ${cards.data.cardCount.toLocaleString()}`); 999 1133 console.log(`Migrations: ${Object.keys(migrations).length.toLocaleString()}`); 1000 - console.log(`Symbols: ${symbols.count.toLocaleString()}`); 1134 + console.log(`Mana symbols: ${symbols.count.toLocaleString()}`); 1135 + console.log(`Set symbols: ${keyrune.setCount.toLocaleString()}`); 1001 1136 console.log(`Version: ${cards.data.version}`); 1002 1137 console.log("\n✓ Done!"); 1003 1138 }
+403
src/lib/set-symbols.ts
··· 1 + /** 2 + * Keyrune set symbol unicode mappings 3 + * Auto-generated by scripts/download-scryfall.ts from Keyrune v3.18.0 4 + * https://github.com/andrewgioia/keyrune 5 + * 6 + * Font licensed under SIL OFL 1.1 7 + */ 8 + 9 + export const SET_SYMBOLS: Record<string, number> = { 10 + "10e": 0xe60b, 11 + "1e": 0xe947, 12 + "2e": 0xe948, 13 + "2ed": 0xe602, 14 + "2u": 0xe949, 15 + "2x2": 0xe99c, 16 + "2xm": 0xe96e, 17 + "30a": 0xe9aa, 18 + "3e": 0xe94a, 19 + "3ed": 0xe603, 20 + "40k": 0xe998, 21 + "4ed": 0xe604, 22 + "5dn": 0xe633, 23 + "5ed": 0xe606, 24 + "6ed": 0xe607, 25 + "7ed": 0xe608, 26 + "8ed": 0xe609, 27 + "9ed": 0xe60a, 28 + "a25": 0xe93d, 29 + "acr": 0xe9ce, 30 + "aer": 0xe90f, 31 + "afc": 0xe981, 32 + "afr": 0xe972, 33 + "akh": 0xe914, 34 + "akr": 0xe970, 35 + "ala": 0xe641, 36 + "all": 0xe61a, 37 + "ann": 0xe92d, 38 + "apc": 0xe62a, 39 + "arb": 0xe643, 40 + "arc": 0xe657, 41 + "arn": 0xe613, 42 + "ath": 0xe65f, 43 + "atq": 0xe614, 44 + "avr": 0xe64c, 45 + "azorius": 0xe94e, 46 + "bbd": 0xe942, 47 + "bcore": 0xe612, 48 + "bfz": 0xe699, 49 + "big": 0xe9d6, 50 + "blb": 0xe9cd, 51 + "blc": 0xe9d4, 52 + "bng": 0xe651, 53 + "bok": 0xe635, 54 + "boros": 0xe94f, 55 + "bot": 0xe99e, 56 + "br": 0xe9c1, 57 + "brb": 0xe660, 58 + "brc": 0xe99f, 59 + "bro": 0xe99d, 60 + "brr": 0xe9a0, 61 + "btd": 0xe661, 62 + "c13": 0xe65b, 63 + "c14": 0xe65d, 64 + "c15": 0xe900, 65 + "c16": 0xe9e5, 66 + "c17": 0xe934, 67 + "c18": 0xe946, 68 + "c19": 0xe95f, 69 + "c20": 0xe966, 70 + "c21": 0xe97e, 71 + "cc1": 0xe968, 72 + "cc2": 0xe987, 73 + "chk": 0xe634, 74 + "chr": 0xe65e, 75 + "clb": 0xe991, 76 + "clu": 0xe9cb, 77 + "cm1": 0xe65a, 78 + "cm2": 0xe940, 79 + "cma": 0xe916, 80 + "cmc": 0xe969, 81 + "cmd": 0xe658, 82 + "cmm": 0xe9b5, 83 + "cn2": 0xe904, 84 + "cns": 0xe65c, 85 + "con": 0xe642, 86 + "csp": 0xe61b, 87 + "dd2": 0xe66a, 88 + "ddc": 0xe66b, 89 + "ddd": 0xe66c, 90 + "dde": 0xe66d, 91 + "ddf": 0xe66e, 92 + "ddg": 0xe66f, 93 + "ddh": 0xe670, 94 + "ddi": 0xe671, 95 + "ddj": 0xe672, 96 + "ddk": 0xe673, 97 + "ddl": 0xe674, 98 + "ddm": 0xe675, 99 + "ddn": 0xe676, 100 + "ddo": 0xe677, 101 + "ddp": 0xe698, 102 + "ddq": 0xe908, 103 + "ddr": 0xe90d, 104 + "dds": 0xe921, 105 + "ddt": 0xe933, 106 + "ddu": 0xe93e, 107 + "dft": 0xe9e0, 108 + "dgm": 0xe64f, 109 + "dimir": 0xe950, 110 + "dis": 0xe639, 111 + "dka": 0xe64b, 112 + "dkm": 0xe662, 113 + "dmc": 0xe994, 114 + "dmr": 0xe9a4, 115 + "dmu": 0xe993, 116 + "dom": 0xe93f, 117 + "dpa": 0xe689, 118 + "drb": 0xe678, 119 + "drc": 0xe9e8, 120 + "drk": 0xe616, 121 + "dsc": 0xe9dc, 122 + "dsk": 0xe9d7, 123 + "dst": 0xe632, 124 + "dtk": 0xe693, 125 + "dvk": 0xea03, 126 + "e02": 0xe931, 127 + "ea1": 0xe9b4, 128 + "ecc": 0xea11, 129 + "ecl": 0xea04, 130 + "eld": 0xe95e, 131 + "ema": 0xe903, 132 + "emn": 0xe90b, 133 + "eoc": 0xe9f6, 134 + "eoe": 0xe9f0, 135 + "eos": 0xea00, 136 + "eve": 0xe640, 137 + "evg": 0xe669, 138 + "exo": 0xe621, 139 + "exp": 0xe69a, 140 + "fca": 0xe9f8, 141 + "fdc": 0xe9e4, 142 + "fdn": 0xe9d8, 143 + "fem": 0xe617, 144 + "fic": 0xe9f5, 145 + "fin": 0xe9ed, 146 + "frf": 0xe654, 147 + "fut": 0xe63c, 148 + "gk1": 0xe94b, 149 + "gk2": 0xe959, 150 + "gn2": 0xe964, 151 + "gn3": 0xe9a5, 152 + "gnt": 0xe94d, 153 + "golgari": 0xe951, 154 + "gpt": 0xe638, 155 + "gruul": 0xe952, 156 + "gs1": 0xe945, 157 + "gtc": 0xe64e, 158 + "h09": 0xe67f, 159 + "h17": 0xe938, 160 + "ha1": 0xe96b, 161 + "hbg": 0xe9a6, 162 + "hml": 0xe618, 163 + "hop": 0xe656, 164 + "hou": 0xe924, 165 + "htr17": 0xe687, 166 + "ice": 0xe619, 167 + "ice2": 0xe925, 168 + "iko": 0xe962, 169 + "ima": 0xe935, 170 + "inr": 0xe9e2, 171 + "inv": 0xe628, 172 + "isd": 0xe64a, 173 + "izzet": 0xe953, 174 + "j20": 0xe96a, 175 + "j21": 0xe983, 176 + "j22": 0xe9ad, 177 + "j25": 0xe9df, 178 + "j25a": 0xe9db, 179 + "jmp": 0xe96f, 180 + "jou": 0xe652, 181 + "jud": 0xe62d, 182 + "khc": 0xe97d, 183 + "khm": 0xe974, 184 + "kld": 0xe90e, 185 + "klr": 0xe97c, 186 + "ktk": 0xe653, 187 + "lcc": 0xe9c7, 188 + "lci": 0xe9c2, 189 + "lea": 0xe600, 190 + "leb": 0xe601, 191 + "leg": 0xe615, 192 + "lgn": 0xe62f, 193 + "lrw": 0xe63d, 194 + "ltc": 0xe9b6, 195 + "ltr": 0xe9af, 196 + "m10": 0xe60c, 197 + "m11": 0xe60d, 198 + "m12": 0xe60e, 199 + "m13": 0xe60f, 200 + "m14": 0xe610, 201 + "m15": 0xe611, 202 + "m19": 0xe941, 203 + "m20": 0xe95d, 204 + "m21": 0xe960, 205 + "m3c": 0xe9d0, 206 + "mar": 0xe9f9, 207 + "mat": 0xe9a3, 208 + "mb1": 0xe971, 209 + "mb2": 0xe9d9, 210 + "mbs": 0xe648, 211 + "md1": 0xe682, 212 + "me1": 0xe68d, 213 + "me2": 0xe68e, 214 + "me3": 0xe68f, 215 + "me4": 0xe690, 216 + "med": 0xe94c, 217 + "mh1": 0xe95b, 218 + "mh2": 0xe97b, 219 + "mh3": 0xe9cf, 220 + "mic": 0xe985, 221 + "mid": 0xe978, 222 + "mir": 0xe61c, 223 + "mkc": 0xe9ca, 224 + "mkm": 0xe9c9, 225 + "mm2": 0xe695, 226 + "mm3": 0xe912, 227 + "mma": 0xe663, 228 + "mmq": 0xe625, 229 + "moc": 0xe9a9, 230 + "mom": 0xe9a2, 231 + "mor": 0xe63e, 232 + "mp1": 0xe913, 233 + "mp2": 0xe922, 234 + "mrd": 0xe631, 235 + "mul": 0xe9ba, 236 + "ncc": 0xe98e, 237 + "nec": 0xe98d, 238 + "neo": 0xe98c, 239 + "nms": 0xe626, 240 + "nph": 0xe649, 241 + "ody": 0xe62b, 242 + "ogw": 0xe901, 243 + "om1": 0xea0e, 244 + "omb": 0xea10, 245 + "onc": 0xe9a8, 246 + "one": 0xe9a1, 247 + "ons": 0xe62e, 248 + "ori": 0xe697, 249 + "orzhov": 0xe954, 250 + "otc": 0xe9d2, 251 + "otj": 0xe9cc, 252 + "otp": 0xe9d5, 253 + "papac": 0xe92a, 254 + "parl": 0xe688, 255 + "parl2": 0xe68c, 256 + "parl3": 0xe943, 257 + "past": 0xe68b, 258 + "pbook": 0xe68a, 259 + "pc2": 0xe659, 260 + "pca": 0xe911, 261 + "pcy": 0xe627, 262 + "pd2": 0xe680, 263 + "pd3": 0xe681, 264 + "pdep": 0xe93a, 265 + "pdrc": 0xe932, 266 + "peuro": 0xe92b, 267 + "pfnm": 0xe937, 268 + "pgru": 0xe683, 269 + "pheart": 0xe936, 270 + "pidw": 0xe92c, 271 + "pio": 0xe9e7, 272 + "pip": 0xe9c3, 273 + "plc": 0xe63b, 274 + "pleaf": 0xe686, 275 + "pls": 0xe629, 276 + "pm2": 0xea02, 277 + "pma": 0xea01, 278 + "pmodo": 0xe91b, 279 + "pmps": 0xe919, 280 + "pmpu": 0xe91a, 281 + "pmtg1": 0xe684, 282 + "pmtg2": 0xe685, 283 + "po2": 0xe665, 284 + "por": 0xe664, 285 + "psalvat05": 0xe909, 286 + "psalvat11": 0xe90a, 287 + "psega": 0xe93b, 288 + "psld": 0xe687, 289 + "psum": 0xe605, 290 + "ptg": 0xe965, 291 + "ptk": 0xe666, 292 + "ptsa": 0xe93c, 293 + "pxbox": 0xe915, 294 + "pz2": 0xe91f, 295 + "pza": 0xea08, 296 + "rakdos": 0xe955, 297 + "rav": 0xe637, 298 + "rex": 0xe9c4, 299 + "rix": 0xe92f, 300 + "roe": 0xe646, 301 + "rtr": 0xe64d, 302 + "rvr": 0xe9bb, 303 + "s00": 0xe668, 304 + "s99": 0xe667, 305 + "scd": 0xe9ab, 306 + "scg": 0xe630, 307 + "selesnya": 0xe956, 308 + "shm": 0xe63f, 309 + "simic": 0xe957, 310 + "sir": 0xe9b1, 311 + "sis": 0xe9b2, 312 + "sld": 0xe687, 313 + "sld2": 0xe9bc, 314 + "slu": 0xe687, 315 + "snc": 0xe98b, 316 + "soi": 0xe902, 317 + "sok": 0xe636, 318 + "som": 0xe647, 319 + "spe": 0xe9f3, 320 + "spg": 0xe9c8, 321 + "spm": 0xe9f1, 322 + "ss1": 0xe944, 323 + "ss2": 0xe95c, 324 + "ss3": 0xe96d, 325 + "sta": 0xe980, 326 + "sth": 0xe620, 327 + "stx": 0xe975, 328 + "td2": 0xe91c, 329 + "tdc": 0xe9f4, 330 + "tdm": 0xe9e6, 331 + "thb": 0xe961, 332 + "ths": 0xe650, 333 + "tla": 0xe9fb, 334 + "tle": 0xea0b, 335 + "tmc": 0xea15, 336 + "tmp": 0xe61f, 337 + "tmt": 0xea06, 338 + "tor": 0xe62c, 339 + "tpr": 0xe694, 340 + "tsp": 0xe63a, 341 + "tsr": 0xe976, 342 + "uds": 0xe624, 343 + "ugl": 0xe691, 344 + "ulg": 0xe623, 345 + "uma": 0xe958, 346 + "una": 0xe9be, 347 + "und": 0xe96c, 348 + "unf": 0xe98a, 349 + "unh": 0xe692, 350 + "usg": 0xe622, 351 + "ust": 0xe930, 352 + "v09": 0xe679, 353 + "v0x": 0xe920, 354 + "v10": 0xe67a, 355 + "v11": 0xe67b, 356 + "v12": 0xe67c, 357 + "v13": 0xe67d, 358 + "v14": 0xe67e, 359 + "v15": 0xe905, 360 + "v16": 0xe906, 361 + "v17": 0xe939, 362 + "van": 0xe655, 363 + "vis": 0xe61d, 364 + "vma": 0xe696, 365 + "voc": 0xe986, 366 + "vow": 0xe977, 367 + "w16": 0xe907, 368 + "w17": 0xe923, 369 + "war": 0xe95a, 370 + "who": 0xe9b0, 371 + "woc": 0xe9b9, 372 + "woe": 0xe9ae, 373 + "wot": 0xe9c0, 374 + "wth": 0xe61e, 375 + "wwk": 0xe645, 376 + "x2ps": 0xe928, 377 + "x4ea": 0xe929, 378 + "xcle": 0xe926, 379 + "xduels": 0xe91d, 380 + "xice": 0xe927, 381 + "xlcu": 0xe90c, 382 + "xln": 0xe92e, 383 + "xmods": 0xe91e, 384 + "xren": 0xe917, 385 + "xrin": 0xe918, 386 + "yeoe": 0xe9da, 387 + "yone": 0xe9a7, 388 + "ysnc": 0xe989, 389 + "ywoe": 0xe9bd, 390 + "zen": 0xe644, 391 + "znc": 0xe967, 392 + "zne": 0xe97a, 393 + "znr": 0xe963, 394 + }; 395 + 396 + /** 397 + * Get unicode character for a set symbol 398 + * Returns empty string if set not found 399 + */ 400 + export function getSetSymbol(setCode: string): string { 401 + const codepoint = SET_SYMBOLS[setCode.toLowerCase()]; 402 + return codepoint ? String.fromCodePoint(codepoint) : ""; 403 + }
+22
src/styles.css
··· 1 1 @import "tailwindcss"; 2 2 3 + /** 4 + * Keyrune set symbol font 5 + * https://github.com/andrewgioia/keyrune 6 + * Font licensed under SIL OFL 1.1 7 + */ 8 + @font-face { 9 + font-family: "Keyrune"; 10 + src: url("/fonts/keyrune/keyrune.woff2") format("woff2"); 11 + font-weight: normal; 12 + font-style: normal; 13 + font-display: swap; 14 + } 15 + 16 + @theme { 17 + /* MTG rarity colors (from Keyrune) */ 18 + --color-rarity-common: #1a1718; 19 + --color-rarity-uncommon: #707883; 20 + --color-rarity-rare: #a58e4a; 21 + --color-rarity-mythic: #bf4427; 22 + --color-rarity-timeshifted: #652978; 23 + } 24 + 3 25 @custom-variant dark (&:where(.dark, .dark *)); 4 26 5 27 body {