changelog-invaders 1.0.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 +438 -0
- package/dist/index.d.ts +396 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1332 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kamil Banc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# Changelog Invaders đđž
|
|
2
|
+
|
|
3
|
+
> **Your changelog is boring. Let's fix that.**
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
7
|
+
â WHAT IF YOUR USERS ACTUALLY LOOKED FORWARD TO â
|
|
8
|
+
â READING YOUR CHANGELOG? â
|
|
9
|
+
âââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Drop this React component into your app and watch your changelog transform into a **full arcade game** where users blast through bugs, collect power-ups, and fly through stargate portals representing your version milestones.
|
|
13
|
+
|
|
14
|
+
**8-bit chiptune soundtrack included.** đĩ
|
|
15
|
+
|
|
16
|
+
[](https://www.npmjs.com/package/changelog-invaders)
|
|
17
|
+
[](https://opensource.org/licenses/MIT)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## đŦ See It In Action
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
*Blast through bugs, collect power-ups, fly through version stargates!*
|
|
26
|
+
|
|
27
|
+
### đšī¸ [Play the Live Demo â](https://rightclickprompt.com/changelog)
|
|
28
|
+
|
|
29
|
+
Try it right now on the Right Click Prompt changelog page â no install needed!
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## đ Add to Your Project in 3 Steps
|
|
34
|
+
|
|
35
|
+
### Step 1: Install the package
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install changelog-invaders
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Step 2: Create a changelog page (or add to existing one)
|
|
42
|
+
|
|
43
|
+
Create a new file like `app/changelog/page.tsx` (Next.js) or `src/pages/Changelog.tsx` (Vite/CRA):
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import { ChangelogInvaders } from 'changelog-invaders'
|
|
47
|
+
|
|
48
|
+
export default function ChangelogPage() {
|
|
49
|
+
return (
|
|
50
|
+
<ChangelogInvaders
|
|
51
|
+
gameTitle="MY APP ODYSSEY"
|
|
52
|
+
versions={["v1.0.0", "v1.1.0", "v2.0.0"]}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Step 3: There is no step 3. You're done! đ
|
|
59
|
+
|
|
60
|
+
Visit your changelog page and start playing. High scores save automatically.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### Why Add This to Your Changelog?
|
|
65
|
+
|
|
66
|
+
- đŽ **Users will actually visit your changelog** â Nobody reads changelogs. Everyone plays games.
|
|
67
|
+
- đ **Built-in engagement** â High scores make users come back
|
|
68
|
+
- đ **Ship your versions as literal stargates** â v1.0 â v2.0 becomes an epic journey
|
|
69
|
+
- đ **Make bug fixes satisfying** â Your users get to literally shoot the bugs you fixed
|
|
70
|
+
- ⥠**5 minutes to integrate** â One component, zero config required
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
Originally created by [Kamil Banc](https://x.com/kamilbanc) for [Right Click Prompt](https://rightclickprompt.com) and open-sourced for the community.
|
|
75
|
+
|
|
76
|
+
## Features
|
|
77
|
+
|
|
78
|
+
- đŽ **Full arcade experience** - Complete game with shooting, power-ups, and boss gates
|
|
79
|
+
- đĩ **Procedural chiptune audio** - Heavy metal-inspired 8-bit music and sound effects
|
|
80
|
+
- đ¨ **Retro pixel art** - CRT screen effects, scanlines, and pixel-perfect graphics
|
|
81
|
+
- đ **Stargate portals** - Fly through version gates to progress
|
|
82
|
+
- đ **High scores** - Works offline with localStorage, **no database required**
|
|
83
|
+
- âī¸ **Fully customizable** - Colors, sprites, enemies, power-ups, and more
|
|
84
|
+
- đą **Responsive** - Works on any screen size
|
|
85
|
+
- đ **Audio toggle** - Players can mute/unmute with M key
|
|
86
|
+
- đ **Zero dependencies** - Only requires React, no database or backend needed
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm install changelog-invaders
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { ChangelogInvaders } from 'changelog-invaders'
|
|
96
|
+
|
|
97
|
+
export default function ChangelogPage() {
|
|
98
|
+
return (
|
|
99
|
+
<ChangelogInvaders
|
|
100
|
+
gameTitle="MY APP ODYSSEY"
|
|
101
|
+
versions={["v1.0.0", "v1.1.0", "v1.2.0", "v2.0.0"]}
|
|
102
|
+
/>
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**That's literally it. Three lines of config.** Your changelog is now a game with:
|
|
108
|
+
- Generic spaceship sprite
|
|
109
|
+
- Default color palette (blue/green/purple/amber)
|
|
110
|
+
- localStorage high scores
|
|
111
|
+
- Full procedural audio engine
|
|
112
|
+
- All game mechanics
|
|
113
|
+
|
|
114
|
+
## Configuration
|
|
115
|
+
|
|
116
|
+
### Basic Props
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
<ChangelogInvaders
|
|
120
|
+
// Required
|
|
121
|
+
gameTitle="ACME ODYSSEY" // Title on the start screen
|
|
122
|
+
versions={["v1.0", "v2.0", "v3.0"]} // Version gates to fly through
|
|
123
|
+
|
|
124
|
+
// Optional branding
|
|
125
|
+
gameSubtitle="â JOURNEY TO V3 â" // Subtitle below title
|
|
126
|
+
/>
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Custom Colors
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
<ChangelogInvaders
|
|
133
|
+
gameTitle="MY APP"
|
|
134
|
+
versions={["v1.0", "v2.0"]}
|
|
135
|
+
colors={{
|
|
136
|
+
bug: "#ff6b6b", // Blue enemies
|
|
137
|
+
feature: "#4ecdc4", // Green enemies
|
|
138
|
+
improvement: "#ffe66d", // Purple enemies
|
|
139
|
+
breaking: "#ff8c42", // Amber enemies
|
|
140
|
+
accent: "#6c5ce7" // UI accent (bullets, etc.)
|
|
141
|
+
}}
|
|
142
|
+
/>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Custom Enemies & Power-ups
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
<ChangelogInvaders
|
|
149
|
+
gameTitle="MY APP"
|
|
150
|
+
versions={["v1.0", "v2.0"]}
|
|
151
|
+
enemyItems={[
|
|
152
|
+
{ type: "bug", text: "CRASH BUG" },
|
|
153
|
+
{ type: "bug", text: "MEMORY LEAK" },
|
|
154
|
+
{ type: "feature", text: "SCOPE CREEP" },
|
|
155
|
+
{ type: "improvement", text: "TECH DEBT" },
|
|
156
|
+
{ type: "breaking", text: "BREAKING!" },
|
|
157
|
+
]}
|
|
158
|
+
powerUpItems={[
|
|
159
|
+
{ type: "weapon", text: "SHIPPED!" },
|
|
160
|
+
{ type: "weapon", text: "DEPLOYED!" },
|
|
161
|
+
{ type: "shield", text: "TESTED!" },
|
|
162
|
+
]}
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Backend Integration (100% Optional)
|
|
167
|
+
|
|
168
|
+
> **Note:** The game works perfectly fine without any backend. High scores are automatically saved to localStorage and persist across sessions. You only need a backend if you want a **global leaderboard** shared across all users.
|
|
169
|
+
|
|
170
|
+
If you want a global leaderboard, provide an API endpoint:
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
<ChangelogInvaders
|
|
174
|
+
gameTitle="MY APP"
|
|
175
|
+
versions={["v1.0", "v2.0"]}
|
|
176
|
+
highscoresEndpoint="/api/game/highscores" // Only if you want global scores
|
|
177
|
+
/>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Your API should implement:
|
|
181
|
+
- `GET /api/game/highscores` â Returns `{ highScores: HighScore[] }`
|
|
182
|
+
- `POST /api/game/highscores` â Accepts `{ playerName, score, gatesPassed, maxSpeed }`
|
|
183
|
+
|
|
184
|
+
See the [Backend Setup](#backend-setup) section for a Supabase example (or use any backend you prefer).
|
|
185
|
+
|
|
186
|
+
### All Props
|
|
187
|
+
|
|
188
|
+
| Prop | Type | Default | Description |
|
|
189
|
+
|------|------|---------|-------------|
|
|
190
|
+
| `gameTitle` | `string` | **required** | Game title on start screen |
|
|
191
|
+
| `versions` | `string[]` | **required** | Version gates to fly through |
|
|
192
|
+
| `gameSubtitle` | `string` | `""` | Subtitle below title |
|
|
193
|
+
| `colors` | `Partial<ColorPalette>` | Default palette | Custom color scheme |
|
|
194
|
+
| `shipSprite` | `string[]` | Generic ship | Custom ship pixel art |
|
|
195
|
+
| `shipColors` | `ShipColors` | Default colors | Color mapping for ship |
|
|
196
|
+
| `enemyItems` | `EnemyItem[]` | Default items | Custom enemy definitions |
|
|
197
|
+
| `powerUpItems` | `PowerUpItem[]` | Default items | Custom power-up definitions |
|
|
198
|
+
| `highscoresEndpoint` | `string` | `undefined` | API endpoint for scores |
|
|
199
|
+
| `localStorageKey` | `string` | `"changelog-invaders-high-scores"` | Local storage key |
|
|
200
|
+
| `enableSound` | `boolean` | `true` | Enable/disable audio |
|
|
201
|
+
| `enableLeaderboard` | `boolean` | `true` | Show leaderboard panel |
|
|
202
|
+
| `height` | `number` | `280` | Canvas height in pixels |
|
|
203
|
+
|
|
204
|
+
## Gameplay
|
|
205
|
+
|
|
206
|
+
### Controls
|
|
207
|
+
|
|
208
|
+
| Key | Action |
|
|
209
|
+
|-----|--------|
|
|
210
|
+
| `â` / `A` | Move left |
|
|
211
|
+
| `â` / `D` | Move right |
|
|
212
|
+
| `Space` | Shoot |
|
|
213
|
+
| `M` | Toggle mute |
|
|
214
|
+
| `Esc` | Return to demo |
|
|
215
|
+
|
|
216
|
+
### Mechanics
|
|
217
|
+
|
|
218
|
+
1. **Shoot Bugs** - Destroy enemy bugs for points
|
|
219
|
+
2. **Collect Power-ups** - Lightning bolts upgrade your weapons (up to level 5)
|
|
220
|
+
3. **Fly Through Gates** - Pass through version stargates for speed bonuses
|
|
221
|
+
4. **Don't Let Bugs Escape** - Penalties increase for each escaped bug
|
|
222
|
+
5. **Reach the Final Version** - Complete the journey to win!
|
|
223
|
+
|
|
224
|
+
### Scoring
|
|
225
|
+
|
|
226
|
+
- Bugs: 10-20 points (Ãweapon level)
|
|
227
|
+
- Power-ups: 100 points (Ãweapon level)
|
|
228
|
+
- Gates: 150 points (Ãspeed multiplier)
|
|
229
|
+
- Time survival: 1 point every 0.5 seconds
|
|
230
|
+
- Movement multiplier: Up to 5Ã for continuous movement
|
|
231
|
+
- Victory bonus: 5000 points
|
|
232
|
+
|
|
233
|
+
## Backend Setup (Optional)
|
|
234
|
+
|
|
235
|
+
> **You don't need this section unless you want a global leaderboard.** The game works completely offline with localStorage for high scores.
|
|
236
|
+
|
|
237
|
+
### Supabase Example
|
|
238
|
+
|
|
239
|
+
1. Create a migration file:
|
|
240
|
+
|
|
241
|
+
```sql
|
|
242
|
+
-- supabase/migrations/001_high_scores.sql
|
|
243
|
+
CREATE TABLE game_high_scores (
|
|
244
|
+
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
|
|
245
|
+
player_name VARCHAR(10) NOT NULL,
|
|
246
|
+
score INTEGER NOT NULL,
|
|
247
|
+
gates_passed INTEGER DEFAULT 0,
|
|
248
|
+
max_speed DECIMAL(4,2) DEFAULT 1.0,
|
|
249
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
CREATE INDEX idx_high_scores_score ON game_high_scores(score DESC);
|
|
253
|
+
|
|
254
|
+
-- Row Level Security
|
|
255
|
+
ALTER TABLE game_high_scores ENABLE ROW LEVEL SECURITY;
|
|
256
|
+
|
|
257
|
+
CREATE POLICY "Anyone can read high scores"
|
|
258
|
+
ON game_high_scores FOR SELECT
|
|
259
|
+
TO anon, authenticated
|
|
260
|
+
USING (true);
|
|
261
|
+
|
|
262
|
+
CREATE POLICY "Anyone can insert high scores"
|
|
263
|
+
ON game_high_scores FOR INSERT
|
|
264
|
+
TO anon, authenticated
|
|
265
|
+
WITH CHECK (true);
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
2. Create API routes (Next.js example):
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
// app/api/game/highscores/route.ts
|
|
272
|
+
import { createClient } from '@supabase/supabase-js'
|
|
273
|
+
|
|
274
|
+
const supabase = createClient(
|
|
275
|
+
process.env.SUPABASE_URL!,
|
|
276
|
+
process.env.SUPABASE_SERVICE_KEY!
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
export async function GET() {
|
|
280
|
+
const { data, error } = await supabase
|
|
281
|
+
.from('game_high_scores')
|
|
282
|
+
.select('id, player_name, score, gates_passed, max_speed, created_at')
|
|
283
|
+
.order('score', { ascending: false })
|
|
284
|
+
.limit(10)
|
|
285
|
+
|
|
286
|
+
if (error) {
|
|
287
|
+
return Response.json({ error: error.message }, { status: 500 })
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const highScores = data.map((row, index) => ({
|
|
291
|
+
...row,
|
|
292
|
+
rank: index + 1
|
|
293
|
+
}))
|
|
294
|
+
|
|
295
|
+
return Response.json({ highScores })
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function POST(request: Request) {
|
|
299
|
+
const body = await request.json()
|
|
300
|
+
const { playerName, score, gatesPassed, maxSpeed } = body
|
|
301
|
+
|
|
302
|
+
const { data, error } = await supabase
|
|
303
|
+
.from('game_high_scores')
|
|
304
|
+
.insert({
|
|
305
|
+
player_name: playerName.substring(0, 10),
|
|
306
|
+
score,
|
|
307
|
+
gates_passed: gatesPassed,
|
|
308
|
+
max_speed: maxSpeed
|
|
309
|
+
})
|
|
310
|
+
.select()
|
|
311
|
+
.single()
|
|
312
|
+
|
|
313
|
+
if (error) {
|
|
314
|
+
return Response.json({ error: error.message }, { status: 500 })
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Check if it's top 10
|
|
318
|
+
const { count } = await supabase
|
|
319
|
+
.from('game_high_scores')
|
|
320
|
+
.select('*', { count: 'exact', head: true })
|
|
321
|
+
.gt('score', score)
|
|
322
|
+
|
|
323
|
+
const rank = (count || 0) + 1
|
|
324
|
+
const isTop10 = rank <= 10
|
|
325
|
+
|
|
326
|
+
return Response.json({ success: true, isTop10, rank })
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Custom Ship Sprite
|
|
331
|
+
|
|
332
|
+
Create your own ship using pixel art strings:
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
const myShip = [
|
|
336
|
+
"........BB........", // B = Black outline
|
|
337
|
+
".......BWWB.......", // W = White
|
|
338
|
+
"......BWWWWB......", // R = Red/accent
|
|
339
|
+
".....BWWRRWWB.....", // G = Gray
|
|
340
|
+
"....BWWWWWWWWB....", // . = Transparent
|
|
341
|
+
"...BWWWWWWWWWWB...",
|
|
342
|
+
"..BWWWWWWWWWWWWB..",
|
|
343
|
+
".BWWWWWWWWWWWWWWB.",
|
|
344
|
+
"BWWWWWWWWWWWWWWWWB",
|
|
345
|
+
"BWWWGWWWWWWWWGWWWB",
|
|
346
|
+
"BWWWGWWWWWWWWGWWWB",
|
|
347
|
+
".BWWGWWWWWWWWGWWB.",
|
|
348
|
+
"..BWGGWWWWWWGGWB..",
|
|
349
|
+
"...BGGGWWWWGGGB...",
|
|
350
|
+
"....BGGGGGGGB.....",
|
|
351
|
+
".....BBBBBBB......",
|
|
352
|
+
]
|
|
353
|
+
|
|
354
|
+
<ChangelogInvaders
|
|
355
|
+
gameTitle="MY APP"
|
|
356
|
+
versions={["v1.0", "v2.0"]}
|
|
357
|
+
shipSprite={myShip}
|
|
358
|
+
shipColors={{
|
|
359
|
+
B: "#000000",
|
|
360
|
+
W: "#ffffff",
|
|
361
|
+
R: "#ff0000",
|
|
362
|
+
G: "#666666",
|
|
363
|
+
}}
|
|
364
|
+
/>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## TypeScript
|
|
368
|
+
|
|
369
|
+
Full TypeScript support with exported types:
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import {
|
|
373
|
+
ChangelogInvaders,
|
|
374
|
+
ChangelogInvadersConfig,
|
|
375
|
+
InvaderType,
|
|
376
|
+
PowerUpType,
|
|
377
|
+
ColorPalette,
|
|
378
|
+
EnemyItem,
|
|
379
|
+
PowerUpItem,
|
|
380
|
+
} from 'changelog-invaders'
|
|
381
|
+
|
|
382
|
+
const config: ChangelogInvadersConfig = {
|
|
383
|
+
gameTitle: "MY APP",
|
|
384
|
+
versions: ["v1.0", "v2.0"],
|
|
385
|
+
// ... fully typed
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Browser Support
|
|
390
|
+
|
|
391
|
+
- Chrome 80+
|
|
392
|
+
- Firefox 75+
|
|
393
|
+
- Safari 13+
|
|
394
|
+
- Edge 80+
|
|
395
|
+
|
|
396
|
+
Requires:
|
|
397
|
+
- Web Audio API (for sound)
|
|
398
|
+
- Canvas 2D API (for graphics)
|
|
399
|
+
- ResizeObserver (for responsive sizing)
|
|
400
|
+
|
|
401
|
+
## Contributing
|
|
402
|
+
|
|
403
|
+
Contributions are welcome! Please read our contributing guidelines first.
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
# Clone the repo
|
|
407
|
+
git clone https://github.com/kbanc85/changelog-invaders.git
|
|
408
|
+
cd changelog-invaders
|
|
409
|
+
|
|
410
|
+
# Install dependencies
|
|
411
|
+
npm install
|
|
412
|
+
|
|
413
|
+
# Start development
|
|
414
|
+
npm run dev
|
|
415
|
+
|
|
416
|
+
# Build
|
|
417
|
+
npm run build
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
MIT Š [Kamil Banc](https://github.com/kbanc85)
|
|
423
|
+
|
|
424
|
+
## Credits
|
|
425
|
+
|
|
426
|
+
Originally built for [Right Click Prompt](https://rightclickprompt.com) - a prompt management tool for AI workflows.
|
|
427
|
+
|
|
428
|
+
Created by **Kamil Banc** â follow for more developer tools:
|
|
429
|
+
- đĻ X/Twitter: [@kamilbanc](https://x.com/kamilbanc)
|
|
430
|
+
- đģ GitHub: [@kbanc85](https://github.com/kbanc85)
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
**Built with â¤ī¸ for developers who believe changelogs should be fun.**
|
|
435
|
+
|
|
436
|
+
```
|
|
437
|
+
đž PEW PEW PEW đž
|
|
438
|
+
```
|