create-spud 0.0.1

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.
@@ -0,0 +1,208 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "spud-game-template",
7
+ "dependencies": {
8
+ "@spud.gg/api": "0.0.24",
9
+ },
10
+ "devDependencies": {
11
+ "sharp": "0.34.5",
12
+ "svgo": "4.0.0",
13
+ "typescript": "^5",
14
+ "vite": "8.0.0-beta.10",
15
+ "vite-plugin-image-optimizer": "2.0.3",
16
+ },
17
+ },
18
+ },
19
+ "packages": {
20
+ "@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
21
+
22
+ "@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
23
+
24
+ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
25
+
26
+ "@img/colour": ["@img/colour@1.0.0", "", {}, "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw=="],
27
+
28
+ "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
29
+
30
+ "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
31
+
32
+ "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
33
+
34
+ "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
35
+
36
+ "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
37
+
38
+ "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
39
+
40
+ "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
41
+
42
+ "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
43
+
44
+ "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
45
+
46
+ "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
47
+
48
+ "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
49
+
50
+ "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
51
+
52
+ "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
53
+
54
+ "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
55
+
56
+ "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
57
+
58
+ "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
59
+
60
+ "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
61
+
62
+ "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
63
+
64
+ "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
65
+
66
+ "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
67
+
68
+ "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
69
+
70
+ "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
71
+
72
+ "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
73
+
74
+ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
75
+
76
+ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="],
77
+
78
+ "@oxc-project/runtime": ["@oxc-project/runtime@0.110.0", "", {}, "sha512-4t5lYmPneAGKGN7zDhK2iQrn+Ax3DXLCNqVr3z6K2VqemKWfQTlLyzjgjilxZmwFAKe65qI4WG7Bsj05UgUHaA=="],
79
+
80
+ "@oxc-project/types": ["@oxc-project/types@0.110.0", "", {}, "sha512-6Ct21OIlrEnFEJk5LT4e63pk3btsI6/TusD/GStLi7wYlGJNOl1GI9qvXAnRAxQU9zqA2Oz+UwhfTOU2rPZVow=="],
81
+
82
+ "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.1", "", { "os": "android", "cpu": "arm64" }, "sha512-He6ZoCfv5D7dlRbrhNBkuMVIHd0GDnjJwbICE1OWpG7G3S2gmJ+eXkcNLJjzjNDpeI2aRy56ou39AJM9AD8YFA=="],
83
+
84
+ "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YzJdn08kSOXnj85ghHauH2iHpOJ6eSmstdRTLyaziDcUxe9SyQJgGyx/5jDIhDvtOcNvMm2Ju7m19+S/Rm1jFg=="],
85
+
86
+ "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cIvAbqM+ZVV6lBSKSBtlNqH5iCiW933t1q8j0H66B3sjbe8AxIRetVqfGgcHcJtMzBIkIALlL9fcDrElWLJQcQ=="],
87
+
88
+ "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-rVt+B1B/qmKwCl1XD02wKfgh3vQPXRXdB/TicV2w6g7RVAM1+cZcpigwhLarqiVCxDObFZ7UgXCxPC7tpDoRog=="],
89
+
90
+ "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.1", "", { "os": "linux", "cpu": "arm" }, "sha512-69YKwJJBOFprQa1GktPgbuBOfnn+EGxu8sBJ1TjPER+zhSpYeaU4N07uqmyBiksOLGXsMegymuecLobfz03h8Q=="],
91
+
92
+ "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-9JDhHUf3WcLfnViFWm+TyorqUtnSAHaCzlSNmMOq824prVuuzDOK91K0Hl8DUcEb9M5x2O+d2/jmBMsetRIn3g=="],
93
+
94
+ "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UvApLEGholmxw/HIwmUnLq3CwdydbhaHHllvWiCTNbyGom7wTwOtz5OAQbAKZYyiEOeIXZNPkM7nA4Dtng7CLw=="],
95
+
96
+ "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.1", "", { "os": "linux", "cpu": "x64" }, "sha512-uVctNgZHiGnJx5Fij7wHLhgw4uyZBVi6mykeWKOqE7bVy9Hcxn0fM/IuqdMwk6hXlaf9fFShDTFz2+YejP+x0A=="],
97
+
98
+ "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.1", "", { "os": "linux", "cpu": "x64" }, "sha512-T6Eg0xWwcxd/MzBcuv4Z37YVbUbJxy5cMNnbIt/Yr99wFwli30O4BPlY8hKeGyn6lWNtU0QioBS46lVzDN38bg=="],
99
+
100
+ "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.1", "", { "os": "none", "cpu": "arm64" }, "sha512-PuGZVS2xNJyLADeh2F04b+Cz4NwvpglbtWACgrDOa5YDTEHKwmiTDjoD5eZ9/ptXtcpeFrMqD2H4Zn33KAh1Eg=="],
101
+
102
+ "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.1", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.1.1" }, "cpu": "none" }, "sha512-2mOxY562ihHlz9lEXuaGEIDCZ1vI+zyFdtsoa3M62xsEunDXQE+DVPO4S4x5MPK9tKulG/aFcA/IH5eVN257Cw=="],
103
+
104
+ "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-oQVOP5cfAWZwRD0Q3nGn/cA9FW3KhMMuQ0NIndALAe6obqjLhqYVYDiGGRGrxvnjJsVbpLwR14gIUYnpIcHR1g=="],
105
+
106
+ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Ydsxxx++FNOuov3wCBPaYjZrEvKOOGq3k+BF4BPridhg2pENfitSRD2TEuQ8i33bp5VptuNdC9IzxRKU031z5A=="],
107
+
108
+ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.1", "", {}, "sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA=="],
109
+
110
+ "@spud.gg/api": ["@spud.gg/api@0.0.24", "", {}, "sha512-QyNEBehQWpQf72wEk050yeo1CaFec6H4mDEXs270qjioGWhbmIOc5GHnYsNa+M26LMk88Slv3VQAEOrHG6tO/w=="],
111
+
112
+ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
113
+
114
+ "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
115
+
116
+ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
117
+
118
+ "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
119
+
120
+ "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
121
+
122
+ "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
123
+
124
+ "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
125
+
126
+ "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
127
+
128
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
129
+
130
+ "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
131
+
132
+ "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
133
+
134
+ "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
135
+
136
+ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
137
+
138
+ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
139
+
140
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
141
+
142
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
143
+
144
+ "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
145
+
146
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
147
+
148
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
149
+
150
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
151
+
152
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
153
+
154
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
155
+
156
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
157
+
158
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
159
+
160
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
161
+
162
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
163
+
164
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
165
+
166
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
167
+
168
+ "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
169
+
170
+ "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
171
+
172
+ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
173
+
174
+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
175
+
176
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
177
+
178
+ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
179
+
180
+ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
181
+
182
+ "rolldown": ["rolldown@1.0.0-rc.1", "", { "dependencies": { "@oxc-project/types": "=0.110.0", "@rolldown/pluginutils": "1.0.0-rc.1" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.1", "@rolldown/binding-darwin-arm64": "1.0.0-rc.1", "@rolldown/binding-darwin-x64": "1.0.0-rc.1", "@rolldown/binding-freebsd-x64": "1.0.0-rc.1", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.1", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.1", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.1", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.1", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.1", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.1", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.1", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.1", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.1" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-M3AeZjYE6UclblEf531Hch0WfVC/NOL43Cc+WdF3J50kk5/fvouHhDumSGTh0oRjbZ8C4faaVr5r6Nx1xMqDGg=="],
183
+
184
+ "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="],
185
+
186
+ "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
187
+
188
+ "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
189
+
190
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
191
+
192
+ "svgo": ["svgo@4.0.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.4.1" }, "bin": "./bin/svgo.js" }, "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw=="],
193
+
194
+ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
195
+
196
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
197
+
198
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
199
+
200
+ "vite": ["vite@8.0.0-beta.10", "", { "dependencies": { "@oxc-project/runtime": "0.110.0", "fdir": "^6.5.0", "lightningcss": "^1.30.2", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-rc.1", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-YXbwlvG+57+LRRJBJYCHki0Z1LWRkPEy3khQ0ZphzW5aJaz17fFBCeefOtHC5VgRuLbG155+lq98I+BjeizQ5Q=="],
201
+
202
+ "vite-plugin-image-optimizer": ["vite-plugin-image-optimizer@2.0.3", "", { "dependencies": { "ansi-colors": "^4.1.3", "pathe": "^2.0.3" }, "peerDependencies": { "sharp": ">=0.34.0", "svgo": ">=4", "vite": ">=5" }, "optionalPeers": ["sharp", "svgo"] }, "sha512-1vrFOTcpSvv6DCY7h8UXab4wqMAjTJB/ndOzG/Kmj1oDOuPF6mbjkNQoGzzCEYeWGe7qU93jc8oQqvoJ57al3A=="],
203
+
204
+ "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
205
+
206
+ "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
207
+ }
208
+ }
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>game_name</title>
8
+ <style>
9
+ canvas {
10
+ position: fixed;
11
+ inset: 0;
12
+ height: 100vh;
13
+ width: 100vw;
14
+ /* pixel_art_canvas_css */
15
+ }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <script type="module" src="/src/demo.ts"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>game_name</title>
8
+ <style>
9
+ canvas {
10
+ position: fixed;
11
+ inset: 0;
12
+ height: 100vh;
13
+ width: 100vw;
14
+ /* pixel_art_canvas_css */
15
+ }
16
+ </style>
17
+ </head>
18
+ <body>
19
+ <script type="module" src="/src/index.ts"></script>
20
+ </body>
21
+ </html>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "game_slug",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "dev:demo": "vite --open /demo.html",
9
+ "build": "tsc && vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@spud.gg/api": "0.0.24"
14
+ },
15
+ "devDependencies": {
16
+ "sharp": "0.34.5",
17
+ "svgo": "4.0.0",
18
+ "typescript": "^5",
19
+ "vite": "8.0.0-beta.10",
20
+ "vite-plugin-image-optimizer": "2.0.3"
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ <svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><defs><style>.cls-1{fill:#2f0007;}.cls-2{fill:#fff;}</style></defs><title>102565_VECTOR_DT_R_01</title><rect class="cls-1" y="7.8" width="32" height="16.36" rx="8.2"/><rect class="cls-2" x="1.7" y="9.5" width="28.6" height="13.03" rx="6.5"/><path class="cls-1" d="M7.4,11.6h0A1.4,1.4,0,0,1,8.8,13v5.7a1.4,1.4,0,0,1-1.4,1.4h0a1.3,1.3,0,0,1-1.3-1.4V13A1.3,1.3,0,0,1,7.4,11.6Z"/><path class="cls-1" d="M11.8,15.8h0a1.4,1.4,0,0,1-1.4,1.4H4.7a1.4,1.4,0,0,1-1.4-1.4h0a1.3,1.3,0,0,1,1.4-1.3h5.7A1.3,1.3,0,0,1,11.8,15.8Z"/><circle class="cls-1" cx="20.7" cy="16" r="1.7"/><circle class="cls-1" cx="23.8" cy="12.8" r="1.7"/><circle class="cls-1" cx="23.8" cy="19.2" r="1.7"/><circle class="cls-1" cx="27.1" cy="16" r="1.7"/><ellipse class="cls-1" cx="14" cy="17.9" rx="1" ry="0.9"/><ellipse class="cls-1" cx="17" cy="17.9" rx="1" ry="0.9"/></svg>
Binary file
@@ -0,0 +1,52 @@
1
+ # game_name
2
+
3
+ # Getting started
4
+
5
+ Useful scripts:
6
+
7
+ ```bash
8
+ # (re)install dependencies
9
+ bun i
10
+ ```
11
+
12
+ ```bash
13
+ # start the local development server
14
+ bun dev
15
+ ```
16
+
17
+ ```bash
18
+ # start the local development server and open the demo.html directly
19
+ bun run dev:demo
20
+ ```
21
+
22
+ ```bash
23
+ # prepare a production build
24
+ bun run build
25
+ ```
26
+
27
+ # Resources:
28
+
29
+ - **Game assets**
30
+ - [itch.io game assets](https://itch.io/game-assets)
31
+
32
+ - **Audio**
33
+ - [sfxr.me sfx generator](https://pro.sfxr.me/)
34
+ - [OpenGameArt cc0 sound effects](https://opengameart.org/art-search-advanced?keys=&title=&field_art_tags_tid_op=or&field_art_tags_tid=&name=&field_art_type_tid%5B%5D=13&field_art_licenses_tid%5B%5D=4)
35
+
36
+ - **Animation**
37
+ - [easings.net](https://easings.net/)
38
+
39
+ - **Sprites & pixel art**
40
+ - [Aseprite](https://www.aseprite.org/)
41
+ - [Pixel planet generator (itch.io)](https://deep-fold.itch.io/pixel-planet-generator)
42
+
43
+ - **Fonts**
44
+ - [Google Fonts](https://fonts.google.com/)
45
+ - [Font Squirrel](https://www.fontsquirrel.com/)
46
+
47
+ - **Documentation for dev tools & APIs used in this project**
48
+ - [Vite docs](https://vite.dev/)
49
+ - [Bun docs](https://bun.com/docs)
50
+ - [TypeScript handbook](https://www.typescriptlang.org/docs/handbook/intro.html)
51
+ - [MDN Canvas API tutorial](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial)
52
+ - [@spud.gg/api (npm)](https://www.npmjs.com/package/@spud.gg/api)
@@ -0,0 +1,36 @@
1
+ import { audio } from "@spud.gg/api";
2
+
3
+ /*
4
+ when you import binary assets using vite, you'll get a url string.
5
+ read more at https://vite.dev/guide/assets#importing-asset-as-url
6
+ */
7
+ import shootAudioUrl from "./assets/audio/shoot.wav";
8
+ import menuMusicUrl from "./assets/audio/menu-music.mp3";
9
+
10
+ /*
11
+ use createSounds for short, snappy SFX that you want pre-buffered
12
+ for low-latency playback. we recommend creating your own effects
13
+ using a tool like https://pro.sfxr.me/
14
+ */
15
+ export const sfx = audio.createSounds({
16
+ shoot: {
17
+ url: shootAudioUrl,
18
+ },
19
+ });
20
+
21
+ /*
22
+ createMusic is for longer tracks that can
23
+ stream and don't need frame-perfect timing.
24
+ */
25
+ export const menuMusic = audio.createMusic({
26
+ url: menuMusicUrl,
27
+ loop: true,
28
+ volume: 0.4,
29
+ });
30
+
31
+ /*
32
+ in spud demo mode (spud.isDemoMode === true), audio methods are no-ops.
33
+ that means you can always call sfx("...").play() or music.play() without
34
+ adding conditional checks; the demo stays silent automatically.
35
+ */
36
+ menuMusic.play();
@@ -0,0 +1,40 @@
1
+ /*
2
+ demo.ts is the entry point for the demo.html file, which displays a
3
+ non-interactive demo of your game. it should be a minimal preview
4
+ version of your game that starts automatically and does not play any
5
+ sounds, use local storage, or handle inputs.
6
+ */
7
+
8
+ import { draw, update } from "./gameplay";
9
+ import { state } from "./state";
10
+ import { resizeCanvasForDpi } from "./helpers";
11
+
12
+ const canvas = document.createElement("canvas");
13
+ document.body.appendChild(canvas);
14
+
15
+ const ctx = canvas.getContext("2d", { alpha: false })!;
16
+ /* pixel_art_image_smoothing */
17
+
18
+ let lastFrameTime = 0;
19
+ let timeToProcessPhysics = 0;
20
+
21
+ function gameLoop(now: number) {
22
+ const dt = Math.min(now - lastFrameTime, 100); // clamp time delta to 100ms in case the user switched tabs
23
+ lastFrameTime = now;
24
+
25
+ resizeCanvasForDpi(ctx);
26
+
27
+ {
28
+ timeToProcessPhysics += dt;
29
+ const physicsTickMs = 1000 / 120; // process physics updates at a fixed 120hz time step
30
+ while (timeToProcessPhysics > physicsTickMs) {
31
+ timeToProcessPhysics -= physicsTickMs;
32
+ update(state, physicsTickMs);
33
+ }
34
+ }
35
+
36
+ draw(state, ctx);
37
+ requestAnimationFrame(gameLoop); // queue up the next tick
38
+ }
39
+
40
+ requestAnimationFrame(gameLoop);
@@ -0,0 +1,47 @@
1
+ import { Button, gamepads } from "@spud.gg/api";
2
+ import { State } from "./state";
3
+ /* moon_sprite_import */
4
+ /* audio_import */
5
+
6
+ /*
7
+ the update loop runs in a fixed time-step and should be used for any
8
+ state updates. `dt` is the time since the last update call.
9
+ */
10
+ export function update(state: State, dt: number) {
11
+ state.elapsedSeconds += dt / 1000;
12
+ if (gamepads.anyPlayer.buttonJustPressed(Button.RightTrigger)) {
13
+ /* audio_shoot_sfx */
14
+ }
15
+ }
16
+
17
+ export function draw(state: State, ctx: CanvasRenderingContext2D) {
18
+ const { width, height } = ctx.canvas.getBoundingClientRect();
19
+ const center = {
20
+ x: width / 2,
21
+ y: height / 2,
22
+ };
23
+
24
+ // clear out the background
25
+ ctx.fillStyle = "#0b0d1a";
26
+ ctx.fillRect(0, 0, width, height);
27
+
28
+ // write some text
29
+ ctx.fillStyle = "white";
30
+ ctx.textAlign = "center";
31
+ ctx.textBaseline = "middle";
32
+ ctx.font = "32px sans-serif";
33
+ ctx.fillText("hello, gamer", center.x, center.y);
34
+
35
+ // draw an orbiting circle
36
+ const moon = {
37
+ radius: 30,
38
+ orbitRadius: 100,
39
+ get x() {
40
+ return center.x + Math.cos(state.elapsedSeconds) * moon.orbitRadius;
41
+ },
42
+ get y() {
43
+ return center.y + Math.sin(state.elapsedSeconds) * moon.orbitRadius;
44
+ },
45
+ };
46
+ /* moon_sprite_draw */
47
+ }
@@ -0,0 +1,14 @@
1
+ /*
2
+ by default, canvas rendering is blurry on high-dpi screens.
3
+ this fixes it by scaling the pixel buffer by the devicePixelRatio
4
+ and then setting a matching transform so that the drawing coords
5
+ can stay in CSS pixels.
6
+
7
+ see also: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas
8
+ */
9
+ export function resizeCanvasForDpi(ctx: CanvasRenderingContext2D) {
10
+ const { width, height } = ctx.canvas.getBoundingClientRect();
11
+ ctx.canvas.width = width * window.devicePixelRatio;
12
+ ctx.canvas.height = height * window.devicePixelRatio;
13
+ ctx.setTransform(window.devicePixelRatio, 0, 0, window.devicePixelRatio, 0, 0);
14
+ }
@@ -0,0 +1,46 @@
1
+ /*
2
+ index.ts is the main entry point for your game. it's responsible for
3
+ setting up the canvas and the two main game loops: a 120hz fixed time
4
+ step physics/update loop, and a variable framerate draw cycle.
5
+ see also:
6
+ - https://gafferongames.com/post/fix_your_timestep/
7
+ - https://gist.github.com/HipHopHuman/3e9b4a94b30ac9387d9a99ef2d29eb1a
8
+ */
9
+
10
+ import { spud, gamepads } from "@spud.gg/api";
11
+ import { draw, update } from "./gameplay";
12
+ import { state } from "./state";
13
+ import { resizeCanvasForDpi } from "./helpers";
14
+
15
+ const canvas = document.createElement("canvas");
16
+ document.body.appendChild(canvas);
17
+
18
+ const ctx = canvas.getContext("2d", { alpha: false })!;
19
+ /* pixel_art_image_smoothing */
20
+
21
+ let lastFrameTime = 0;
22
+ let timeToProcessPhysics = 0;
23
+
24
+ function gameLoop(now: number) {
25
+ const dt = Math.min(now - lastFrameTime, 100); // clamp time delta to 100ms in case the user switched tabs
26
+ lastFrameTime = now;
27
+
28
+ resizeCanvasForDpi(ctx);
29
+
30
+ {
31
+ timeToProcessPhysics += dt;
32
+ const physicsTickMs = 1000 / 120; // process physics updates at a fixed 120hz time step
33
+ while (timeToProcessPhysics > physicsTickMs) {
34
+ if (!spud.isPaused) {
35
+ timeToProcessPhysics -= physicsTickMs;
36
+ update(state, physicsTickMs);
37
+ }
38
+ gamepads.clearInputs();
39
+ }
40
+ }
41
+
42
+ draw(state, ctx);
43
+ requestAnimationFrame(gameLoop); // queue up the next tick
44
+ }
45
+
46
+ requestAnimationFrame(gameLoop);
@@ -0,0 +1,52 @@
1
+ /*
2
+ small sprites will be automatically inlined by Vite.
3
+ sprite files larger than the default 4kb assetsInlineLimit will be
4
+ optimized and fetched after the initial bundle has loaded.
5
+ read more at https://vite.dev/guide/assets#importing-asset-as-url
6
+
7
+ moon sprite credit: https://deep-fold.itch.io/pixel-planet-generator
8
+ */
9
+ import sprite from "./assets/images/sprite.png";
10
+
11
+ type SpriteSheet = {
12
+ image: HTMLImageElement;
13
+ frameWidthPx: number;
14
+ frameHeightPx: number;
15
+ frameCount: number;
16
+ };
17
+
18
+ export const moonSheet: SpriteSheet = {
19
+ image: new Image(),
20
+ frameWidthPx: 50,
21
+ frameHeightPx: 50,
22
+ frameCount: 5000 / 50,
23
+ };
24
+
25
+ moonSheet.image.src = sprite;
26
+
27
+ export function drawSprite(
28
+ ctx: CanvasRenderingContext2D,
29
+ sheet: SpriteSheet,
30
+ frameIndex: number,
31
+ x: number,
32
+ y: number,
33
+ ) {
34
+ const frame = frameIndex % sheet.frameCount;
35
+ const sourceX = frame * sheet.frameWidthPx;
36
+ const sourceY = 0;
37
+ const drawWidthPx = sheet.frameWidthPx;
38
+ const drawHeightPx = sheet.frameHeightPx;
39
+
40
+ // see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
41
+ ctx.drawImage(
42
+ sheet.image,
43
+ sourceX,
44
+ sourceY,
45
+ sheet.frameWidthPx,
46
+ sheet.frameHeightPx,
47
+ x,
48
+ y,
49
+ drawWidthPx,
50
+ drawHeightPx,
51
+ );
52
+ }
@@ -0,0 +1,5 @@
1
+ export const state = {
2
+ elapsedSeconds: 0,
3
+ };
4
+
5
+ export type State = typeof state;
@@ -0,0 +1,37 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2024",
4
+ "module": "ESNext",
5
+ "lib": [
6
+ "ESNext",
7
+ "DOM",
8
+ "DOM.Iterable"
9
+ ],
10
+ "types": [
11
+ "vite/client"
12
+ ],
13
+ "skipLibCheck": true,
14
+ /* Bundler mode */
15
+ "moduleResolution": "bundler",
16
+ "allowImportingTsExtensions": true,
17
+ "moduleDetection": "force",
18
+ "noEmit": true,
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUncheckedIndexedAccess": true,
22
+ "noPropertyAccessFromIndexSignature": true,
23
+ "noImplicitOverride": true,
24
+ "erasableSyntaxOnly": true,
25
+ "noFallthroughCasesInSwitch": true,
26
+ "noUncheckedSideEffectImports": true,
27
+ /* Aliases */
28
+ "paths": {
29
+ "~/*": [
30
+ "./src/*"
31
+ ]
32
+ }
33
+ },
34
+ "include": [
35
+ "src"
36
+ ]
37
+ }
@@ -0,0 +1,24 @@
1
+ import { defineConfig } from "vite";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { ViteImageOptimizer } from "vite-plugin-image-optimizer";
5
+
6
+ const dir = dirname(fileURLToPath(import.meta.url));
7
+
8
+ export default defineConfig({
9
+ build: {
10
+ modulePreload: {
11
+ polyfill: false,
12
+ },
13
+ rolldownOptions: {
14
+ input: {
15
+ main: resolve(dir, "index.html"),
16
+ demo: resolve(dir, "demo.html"),
17
+ },
18
+ },
19
+ },
20
+ plugins: [ViteImageOptimizer()],
21
+ resolve: {
22
+ tsconfigPaths: true,
23
+ },
24
+ });
package/index.ts ADDED
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env bun
2
+ import { mkdir, readdir } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { intro, outro, cancel, text, confirm, tasks, group, multiselect } from "@clack/prompts";
6
+ import kebabcase from "lodash.kebabcase";
7
+
8
+ intro("It's spud time.");
9
+
10
+ const Feature = { Audio: "Audio", PixelArt: "PixelArt" } as const;
11
+ type Feature = (typeof Feature)[keyof typeof Feature];
12
+
13
+ const { rawGameName, features, shouldContinue } = await group(
14
+ {
15
+ rawGameName: () =>
16
+ text({
17
+ message: "Game name",
18
+ placeholder: "eg. Space Invaders",
19
+ validate(value) {
20
+ if (!value?.trim()) return "Game name is required. Esc to cancel.";
21
+ },
22
+ }),
23
+ features: () =>
24
+ multiselect({
25
+ message: "Features",
26
+ initialValues: [Feature.Audio],
27
+ required: false,
28
+ options: [
29
+ { label: "Audio", value: Feature.Audio },
30
+ { label: "Pixel art", value: Feature.PixelArt },
31
+ ],
32
+ }),
33
+ shouldContinue: ({ results }) =>
34
+ confirm({
35
+ message: `We'll create a folder for "${kebabcase((results.rawGameName ?? "").trim())}" in the current directory. Do you want to continue?`,
36
+ }),
37
+ },
38
+ {
39
+ onCancel() {
40
+ cancel("Cancelled.");
41
+ process.exit(0);
42
+ },
43
+ },
44
+ );
45
+
46
+ if (!shouldContinue) {
47
+ cancel("Operation cancelled.");
48
+ process.exit(0);
49
+ }
50
+
51
+ const gameName = rawGameName.trim();
52
+ const slug = kebabcase(gameName);
53
+ const targetDir = path.resolve(process.cwd(), slug);
54
+ const templateDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "example");
55
+
56
+ await tasks([
57
+ {
58
+ title: `Scaffolding project in ./${slug}/`,
59
+ async task() {
60
+ await mkdir(targetDir, { recursive: true });
61
+ await copyDir(templateDir, targetDir, { gameName, slug, features });
62
+ return "Initialized project from spud starter template";
63
+ },
64
+ },
65
+ {
66
+ title: "Installing dependencies with Bun",
67
+ async task() {
68
+ const proc = Bun.spawn(["bun", "install"], {
69
+ cwd: targetDir,
70
+ });
71
+ await proc.exited;
72
+ return "Installed via bun";
73
+ },
74
+ },
75
+ ]);
76
+
77
+ outro(`You're all set! Now: cd ${slug} && bun dev`);
78
+
79
+ async function copyDir(
80
+ sourceDir: string,
81
+ destDir: string,
82
+ replacements: { gameName: string; slug: string; features: Feature[] },
83
+ ) {
84
+ await mkdir(destDir, { recursive: true });
85
+ const entries = await readdir(sourceDir, { withFileTypes: true });
86
+
87
+ for (const entry of entries) {
88
+ const sourcePath = path.join(sourceDir, entry.name);
89
+ const destPath = path.join(destDir, entry.name);
90
+
91
+ if (entry.isDirectory()) {
92
+ if (entry.name === "node_modules" || entry.name === "dist") continue;
93
+ if (entry.name === "audio" && !replacements.features.includes(Feature.Audio)) continue;
94
+ await copyDir(sourcePath, destPath, replacements);
95
+ continue;
96
+ }
97
+
98
+ if (!entry.isFile()) continue;
99
+ if (entry.name === "audio.ts" && !replacements.features.includes(Feature.Audio)) continue;
100
+ if (entry.name === "sprite.ts" && !replacements.features.includes(Feature.PixelArt)) continue;
101
+
102
+ const ext = path.extname(entry.name);
103
+ const extensionsThatMayContainTemplateVariables = [".html", ".json", ".md", ".ts"];
104
+
105
+ if (!extensionsThatMayContainTemplateVariables.includes(ext)) {
106
+ const file = Bun.file(sourcePath);
107
+ await Bun.write(destPath, file);
108
+ continue;
109
+ }
110
+
111
+ const text = await Bun.file(sourcePath).text();
112
+ const replaced = text
113
+ .replaceAll("game_name", replacements.gameName)
114
+ .replaceAll("game_slug", replacements.slug)
115
+ .replaceAll(
116
+ " /* pixel_art_canvas_css */\n",
117
+ replacements.features.includes(Feature.PixelArt)
118
+ ? " image-rendering: pixelated;\n"
119
+ : "",
120
+ )
121
+ .replaceAll(
122
+ "/* pixel_art_canvas_css */\n",
123
+ replacements.features.includes(Feature.PixelArt) ? "image-rendering: pixelated;\n" : "",
124
+ )
125
+ .replaceAll(
126
+ "/* pixel_art_image_smoothing */\n",
127
+ replacements.features.includes(Feature.PixelArt)
128
+ ? "ctx.imageSmoothingEnabled = false;\n"
129
+ : "",
130
+ )
131
+ .replaceAll(
132
+ "/* moon_sprite_import */\n",
133
+ replacements.features.includes(Feature.PixelArt)
134
+ ? 'import { drawSprite, moonSheet } from "./sprite";\n'
135
+ : "",
136
+ )
137
+ .replaceAll(
138
+ "/* audio_import */\n",
139
+ replacements.features.includes(Feature.Audio) ? 'import { sfx } from "./audio";\n' : "",
140
+ )
141
+ .replaceAll(
142
+ " /* moon_sprite_draw */\n",
143
+ replacements.features.includes(Feature.PixelArt)
144
+ ? ` const moonFrameIndex = Math.floor(state.elapsedSeconds * 12);
145
+ drawSprite(ctx, moonSheet, moonFrameIndex, moon.x - moon.radius, moon.y - moon.radius);
146
+ `
147
+ : ` ctx.beginPath();
148
+ ctx.arc(moon.x, moon.y, moon.radius, 0, Math.PI * 2);
149
+ ctx.fill();
150
+ `,
151
+ )
152
+ .replaceAll(
153
+ " /* audio_shoot_sfx */\n",
154
+ replacements.features.includes(Feature.Audio)
155
+ ? ` sfx("shoot").play({
156
+ detune: -1000 + Math.random() * 2000,
157
+ playbackRate: 0.5 + Math.random(),
158
+ });
159
+ `
160
+ : " /* handle inputs and update state */\n",
161
+ );
162
+ await Bun.write(destPath, replaced);
163
+ }
164
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "create-spud",
3
+ "version": "0.0.1",
4
+ "bin": {
5
+ "create-spud": "./index.ts"
6
+ },
7
+ "files": [
8
+ "index.ts",
9
+ "example"
10
+ ],
11
+ "type": "module",
12
+ "module": "index.ts",
13
+ "dependencies": {
14
+ "@clack/prompts": "0.11.0",
15
+ "lodash.kebabcase": "4.1.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/bun": "latest",
19
+ "@types/lodash.kebabcase": "4.1.9"
20
+ },
21
+ "engines": {
22
+ "bun": ">=1.0.0"
23
+ }
24
+ }
package/readme.md ADDED
@@ -0,0 +1,35 @@
1
+ # `create-spud`
2
+
3
+ The quickest way to set up and scaffold a [spud](https://spud.gg) game.
4
+
5
+ ## Quick start
6
+
7
+ ```bash
8
+ bun create spud
9
+ ```
10
+
11
+ ## What gets created
12
+
13
+ - A new folder named after your game
14
+ - A ready-to-run spud game project based on the `example/` template
15
+
16
+ ## Requirements
17
+
18
+ - [Bun](https://bun.com/) (runtime + package manager)
19
+
20
+ <details>
21
+ <summary><strong>Bun installation guide</strong></summary>
22
+
23
+ 1. Install Bun ([docs](https://bun.com/docs/installation)):
24
+
25
+ ```bash
26
+ curl -fsSL https://bun.sh/install | bash
27
+ ```
28
+
29
+ 2. Then restart your terminal so `bun` is on your `$PATH`.
30
+
31
+ </details>
32
+
33
+ ## Notes
34
+
35
+ - This CLI is Bun-only. If you try to run it with Node (`npx create spud`, for example), that won't work.