pre-mortem 0.1.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/src/blocks.ts ADDED
@@ -0,0 +1,182 @@
1
+ import Matter from 'matter-js';
2
+
3
+ export enum BlockType {
4
+ BOOTSTRAP = 'bootstrap',
5
+ VC = 'vc',
6
+ SLACKER = 'slacker',
7
+ CONTROL_FREAK = 'control_freak',
8
+ PRIMA_DONNA = 'prima_donna',
9
+ QUIET_QUITTER = 'quiet_quitter',
10
+ SLACK_SPAMMER = 'slack_spammer'
11
+ }
12
+
13
+ export interface BlockConfig {
14
+ w: number;
15
+ h: number;
16
+ color: string;
17
+ density: number;
18
+ friction: number;
19
+ restitution: number;
20
+ label: string;
21
+ possibleLabels?: string[];
22
+ }
23
+
24
+ export const BLOCK_DEFINITIONS: Record<BlockType, BlockConfig> = {
25
+ [BlockType.BOOTSTRAP]: {
26
+ w: 132,
27
+ h: 44,
28
+ color: '#555555', // Concrete/Rust
29
+ density: 0.005, // High relative density (Matter.js default is 0.001)
30
+ friction: 0.8,
31
+ restitution: 0.0,
32
+ label: 'Refactoring',
33
+ possibleLabels: [
34
+ // The Work (Old & New)
35
+ 'Refactoring', 'Unit Tests', 'Bug Fixes', 'User Support',
36
+ 'Documentation', 'Security Audit', 'Compliance', 'Cleanup',
37
+ 'Optimization', 'CI/CD Pipeline', 'Code Review', 'DB Tuning',
38
+ 'API Design', 'Accessibility', 'Localization', 'Error Logs',
39
+ 'Backups', 'Legacy Code', 'Hiring', 'Customer Love',
40
+ 'Bossware', 'YAML Hell', 'DNS Propagation', 'Regex', 'npm audit',
41
+ 'GDPR / SOC2', 'Migration', 'Certificate Expiry', 'On-Call',
42
+ 'Technical Debt', 'Works on my machine', 'Yak Shaving'
43
+ ]
44
+ },
45
+ [BlockType.VC]: {
46
+ w: 120,
47
+ h: 40,
48
+ color: '#FF00FF', // Neon Pink
49
+ density: 0.0005, // Slightly heavier (was 0.0001)
50
+ friction: 0.3, // Less slippery (was 0.1)
51
+ restitution: 0.6, // Less bouncy (was 0.9)
52
+ label: 'AI Blockchain',
53
+ possibleLabels: [
54
+ // The Hype (Old & New)
55
+ 'GenAI', 'Blockchain', 'Web3', 'Metaverse', 'NFTs',
56
+ 'Viral Growth', 'Pivot', 'Synergy', 'Disruption',
57
+ 'Thought Leader', 'Influencers', 'Hyper-Scale', 'Quantum',
58
+ 'Big Data', 'Growth Hack', 'Paradigm Shift', '10x Engineer',
59
+ 'Visionary', 'Series B Pitch', 'Exit Strategy',
60
+ 'Vibe Coding', 'Founder Mode', 'Prompt Engineering', 'AGI',
61
+ 'AI Wrapper', 'Unlimited PTO', 'Radical Candor', 'Community Led',
62
+ 'Pre-Revenue', 'Fractional CxO'
63
+ ]
64
+ },
65
+ [BlockType.SLACKER]: {
66
+ w: 64, // 80 * 0.8
67
+ h: 64, // 80 * 0.8
68
+ color: '#FFFFFF',
69
+ density: 0.01,
70
+ friction: 1.0,
71
+ restitution: 0.1,
72
+ label: 'The Slacker',
73
+ possibleLabels: ['The Slacker']
74
+ },
75
+ [BlockType.CONTROL_FREAK]: {
76
+ w: 64, // Square to match image aspect ratio
77
+ h: 64,
78
+ color: '#FF0000', density: 0.012, friction: 0.5, restitution: 0.2,
79
+ label: 'Control Freak', possibleLabels: ['Control Freak']
80
+ },
81
+ [BlockType.PRIMA_DONNA]: {
82
+ w: 56, h: 56, // 70 * 0.8
83
+ color: '#FFFF00', density: 0.008, friction: 0.1, restitution: 1.2,
84
+ label: 'Prima Donna', possibleLabels: ['Prima Donna']
85
+ },
86
+ [BlockType.QUIET_QUITTER]: {
87
+ w: 48, h: 48, // 60 * 0.8
88
+ color: '#00FFFF', density: 0.005, friction: 0.01, restitution: 0.9,
89
+ label: 'Quiet Quitter', possibleLabels: ['Quiet Quitter']
90
+ },
91
+ [BlockType.SLACK_SPAMMER]: {
92
+ w: 60, h: 60, // 75 * 0.8
93
+ color: '#00FF00', density: 0.01, friction: 0.4, restitution: 0.4,
94
+ label: 'Slack Spammer', possibleLabels: ['Slack Spammer']
95
+ }
96
+ };
97
+
98
+ export const getRandomLabel = (type: BlockType): string => {
99
+ const labels = BLOCK_DEFINITIONS[type].possibleLabels || [];
100
+ return labels[Math.floor(Math.random() * labels.length)];
101
+ }
102
+
103
+ export const createBlock = (x: number, y: number, type: BlockType) => {
104
+ const def = BLOCK_DEFINITIONS[type];
105
+ const labelText = getRandomLabel(type);
106
+
107
+ const options = {
108
+ density: def.density,
109
+ friction: def.friction,
110
+ restitution: def.restitution,
111
+ render: {
112
+ fillStyle: def.color,
113
+ strokeStyle: '#000',
114
+ lineWidth: 2,
115
+ },
116
+ label: labelText,
117
+ };
118
+
119
+ if (type === BlockType.VC) {
120
+ // "Long Side Trapezoid" / Distorted Wedge
121
+ // Create an irregular quadrilateral that is annoying to stack
122
+ const w = def.w;
123
+ const h = def.h;
124
+
125
+ // Randomize the top corners to create a slanted roof (Wedge)
126
+ // or skew the sides (Parallelogram)
127
+ const y1 = Math.random() * 15; // Top-Left drop
128
+ const y2 = Math.random() * 15; // Top-Right drop
129
+ const skew = (Math.random() - 0.5) * 20; // skew x
130
+
131
+ const vertices = [
132
+ { x: 0 + skew, y: y1 }, // TL
133
+ { x: w + skew, y: y2 }, // TR
134
+ { x: w, y: h }, // BR
135
+ { x: 0, y: h } // BL
136
+ ];
137
+
138
+ return Matter.Bodies.fromVertices(x, y, [vertices], options);
139
+ return Matter.Bodies.fromVertices(x, y, [vertices], options);
140
+ } else if (type === BlockType.SLACKER ||
141
+ type === BlockType.CONTROL_FREAK ||
142
+ type === BlockType.PRIMA_DONNA ||
143
+ type === BlockType.QUIET_QUITTER ||
144
+ type === BlockType.SLACK_SPAMMER) {
145
+
146
+ const textureMap: Record<string, string> = {
147
+ [BlockType.SLACKER]: './slacker.png',
148
+ [BlockType.CONTROL_FREAK]: './control-freak.png',
149
+ [BlockType.PRIMA_DONNA]: './prima-donna.png',
150
+ [BlockType.QUIET_QUITTER]: './quet-quitter.png', // User typo kept
151
+ [BlockType.SLACK_SPAMMER]: './slack-spammer.png'
152
+ };
153
+
154
+ const spriteOptions = {
155
+ ...options,
156
+ render: {
157
+ sprite: {
158
+ texture: textureMap[type],
159
+ xScale: (def.w / 512) * 2, // Approximate
160
+ yScale: (def.h / 512) * 2
161
+ },
162
+ lineWidth: 3,
163
+ strokeStyle: '#ffffff'
164
+ }
165
+ };
166
+
167
+ if (type === BlockType.QUIET_QUITTER) {
168
+ return Matter.Bodies.circle(x, y, def.w / 2, spriteOptions);
169
+ } else if (type === BlockType.CONTROL_FREAK) {
170
+ return Matter.Bodies.trapezoid(x, y, def.w, def.h, 0.5, spriteOptions);
171
+ } else if (type === BlockType.PRIMA_DONNA) {
172
+ return Matter.Bodies.polygon(x, y, 5, def.w / 2, spriteOptions); // Pentagon
173
+ } else if (type === BlockType.SLACK_SPAMMER) {
174
+ return Matter.Bodies.polygon(x, y, 6, def.w / 2, spriteOptions); // Hexagon
175
+ } else {
176
+ // Default Slacker (Square)
177
+ return Matter.Bodies.rectangle(x, y, def.w, def.h, spriteOptions);
178
+ }
179
+ } else {
180
+ return Matter.Bodies.rectangle(x, y, def.w, def.h, options);
181
+ }
182
+ };
package/src/game.ts ADDED
@@ -0,0 +1,366 @@
1
+ import Matter from 'matter-js';
2
+ import { createBlock, BlockType } from './blocks';
3
+ import { GameState, StageConfig } from './gamestate';
4
+
5
+ export interface GameConfig {
6
+ logoUrl?: string; // Optional logo URL
7
+ width?: number;
8
+ height?: number;
9
+ initialRunway?: number;
10
+ stages?: StageConfig[];
11
+ onCrisis?: (shout: string, desc: string) => void;
12
+ chaosInterval?: number; // in ms
13
+ }
14
+
15
+ export class PreMortemGame {
16
+ private engine: Matter.Engine;
17
+ private render: Matter.Render;
18
+ private runner: Matter.Runner;
19
+ private container: HTMLElement;
20
+ private width: number;
21
+ private height: number;
22
+ private mouseConstraint!: Matter.MouseConstraint;
23
+ private currentQuote: { text: string, author: string } | null = null;
24
+ private chaosTimer: number = 10000; // Debug: Initial event after 10s
25
+ private chaosInterval: number = 90000; // Default 1.5 mins
26
+ private onCrisis?: (shout: string, desc: string) => void;
27
+
28
+ public gameState: GameState;
29
+ public spawnVertical: boolean = false;
30
+
31
+ constructor(container: HTMLElement, config: GameConfig = {}) {
32
+ this.container = container;
33
+ this.width = config.width || container.clientWidth || 800;
34
+ this.height = config.height || container.clientHeight || 600;
35
+ this.onCrisis = config.onCrisis;
36
+ this.chaosInterval = config.chaosInterval || 90000; // 1.5 min default
37
+ // Set first timer to 10s for debug as requested, or use config if provided
38
+ this.chaosTimer = config.chaosInterval ? config.chaosInterval : 10000;
39
+
40
+ const defaultStages: StageConfig[] = [
41
+ { name: 'Seed Round', threshold: 0, burnRate: 2_000 },
42
+ { name: 'Series A', threshold: 100_000_000, burnRate: 10_000, fundingBonus: 250_000 },
43
+ { name: 'Series B', threshold: 250_000_000, burnRate: 25_000, fundingBonus: 500_000 },
44
+ { name: 'Series C', threshold: 500_000_000, burnRate: 50_000, fundingBonus: 1_000_000 },
45
+ { name: 'Series D', threshold: 700_000_000, burnRate: 75_000, fundingBonus: 2_000_000 },
46
+ { name: 'Series E', threshold: 850_000_000, burnRate: 100_000, fundingBonus: 5_000_000 },
47
+ { name: 'Unicorn Status', threshold: 1_000_000_000, burnRate: 150_000, fundingBonus: 10_000_000 },
48
+ ];
49
+
50
+ this.gameState = new GameState({
51
+ initialRunway: config.initialRunway || 200_000,
52
+ stages: config.stages || defaultStages
53
+ });
54
+
55
+ this.engine = Matter.Engine.create();
56
+ this.render = Matter.Render.create({
57
+ element: this.container,
58
+ engine: this.engine,
59
+ options: {
60
+ width: this.width,
61
+ height: this.height,
62
+ wireframes: false,
63
+ background: '#1a1a1a',
64
+ },
65
+ });
66
+
67
+ this.runner = Matter.Runner.create();
68
+
69
+ this.initWorld();
70
+ this.initMouse();
71
+ this.initRenderLoop();
72
+ this.initGameLoop();
73
+ this.initControls();
74
+
75
+ this.gameState.subscribe(() => {
76
+ if (this.gameState.status === 'paused') {
77
+ this.runner.enabled = false;
78
+ } else if (this.gameState.status === 'playing') {
79
+ this.runner.enabled = true;
80
+ }
81
+ });
82
+ }
83
+
84
+ private initControls() {
85
+ window.addEventListener('keydown', (e) => {
86
+ if ((e.key === 'r' || e.key === 'R') && this.gameState.isPlaying()) {
87
+ this.rotateHeldBody();
88
+ }
89
+ });
90
+ }
91
+
92
+ private rotateHeldBody() {
93
+ const body = this.mouseConstraint.body;
94
+ if (body) {
95
+ Matter.Body.rotate(body, 45 * (Math.PI / 180));
96
+ }
97
+ }
98
+
99
+ private initWorld() {
100
+ const { width, height } = this;
101
+ const groundHeight = 60;
102
+
103
+ const ground = Matter.Bodies.rectangle(width / 2, height - groundHeight / 2, width, groundHeight, {
104
+ isStatic: true,
105
+ render: { fillStyle: '#333' }
106
+ });
107
+
108
+ Matter.Composite.add(this.engine.world, [ground]);
109
+ }
110
+
111
+ private initMouse() {
112
+ const mouse = Matter.Mouse.create(this.render.canvas);
113
+ this.mouseConstraint = Matter.MouseConstraint.create(this.engine, {
114
+ mouse: mouse,
115
+ constraint: {
116
+ stiffness: 0.2,
117
+ render: { visible: false }
118
+ }
119
+ });
120
+
121
+ Matter.Composite.add(this.engine.world, this.mouseConstraint);
122
+ this.render.mouse = mouse;
123
+ }
124
+
125
+ public start() {
126
+ Matter.Render.run(this.render);
127
+ Matter.Runner.run(this.runner, this.engine);
128
+ }
129
+
130
+ public stop() {
131
+ Matter.Render.stop(this.render);
132
+ Matter.Runner.stop(this.runner);
133
+ }
134
+
135
+ private initRenderLoop() {
136
+ Matter.Events.on(this.render, 'afterRender', () => {
137
+ const context = this.render.context;
138
+ const cx = this.width / 2;
139
+ const officeWidth = 400;
140
+ const dangerWidth = 700;
141
+ const topOffset = 100;
142
+
143
+ const officeRect = { x: cx - officeWidth/2, y: topOffset, w: officeWidth, h: this.height - topOffset };
144
+ const dangerRect = { x: cx - dangerWidth/2, y: topOffset - 50, w: dangerWidth, h: this.height - topOffset + 100 };
145
+
146
+ // Office Zone
147
+ context.beginPath();
148
+ context.setLineDash([10, 10]);
149
+ context.lineWidth = 2;
150
+ context.strokeStyle = 'rgba(255, 255, 255, 0.3)';
151
+ context.strokeRect(officeRect.x, officeRect.y, officeRect.w, officeRect.h);
152
+
153
+ context.font = '10px "Inter", sans-serif';
154
+ context.fillStyle = 'rgba(255, 255, 255, 0.3)';
155
+ context.textAlign = 'center';
156
+ context.fillText("ACCURATE VALUATION ZONE", cx, officeRect.y - 15);
157
+
158
+ // Quote
159
+ if (!this.currentQuote) {
160
+ const quotes = [
161
+ { text: "WE ARE A\nFAMILY", author: "- CEO" },
162
+ { text: "CHANGE THE\nWORLD", author: "- Founder" },
163
+ { text: "DISRUPT\nEVERYTHING", author: "- VC" },
164
+ { text: "MOVE FAST\nBREAK THINGS", author: "- Tech Lead" },
165
+ { text: "RADICAL\nCANDOR", author: "- HR" },
166
+ { text: "UNLIMITED\nPTO", author: "- Recruiter" },
167
+ { text: "VISIONARY\nLEADER", author: "- LinkedIn" },
168
+ { text: "10X\nGROWTH", author: "- Investor" },
169
+ { text: "PIVOT\nTO AI", author: "- Board" }
170
+ ];
171
+ this.currentQuote = quotes[Math.floor(Math.random() * quotes.length)];
172
+ }
173
+
174
+ context.save();
175
+ context.translate(cx, officeRect.y + officeRect.h / 3);
176
+ context.rotate(-Math.PI / 12);
177
+ context.textAlign = 'center';
178
+ context.textBaseline = 'middle';
179
+ context.font = '900 48px "Inter", sans-serif';
180
+ context.fillStyle = 'rgba(255, 255, 255, 0.05)';
181
+
182
+ const lines = this.currentQuote.text.split('\n');
183
+ lines.forEach((line, i) => context.fillText(line, 0, i * 50));
184
+
185
+ context.font = 'italic 700 24px "Inter", sans-serif';
186
+ context.fillStyle = 'rgba(255, 255, 255, 0.04)';
187
+ context.fillText(this.currentQuote.author, 0, lines.length * 50 + 10);
188
+ context.restore();
189
+
190
+ // Danger Zone
191
+ context.beginPath();
192
+ context.setLineDash([]);
193
+ context.lineWidth = 1;
194
+ context.strokeStyle = 'rgba(255, 50, 50, 0.2)';
195
+ context.strokeRect(dangerRect.x, dangerRect.y, dangerRect.w, dangerRect.h);
196
+
197
+ // Block Labels
198
+ context.font = 'bold 13px "Inter", sans-serif';
199
+ context.textAlign = 'center';
200
+ context.textBaseline = 'middle';
201
+ context.fillStyle = '#FFFFFF';
202
+
203
+ this.engine.world.bodies.forEach((body) => {
204
+ if (!body.label || body.label.includes('Body')) return;
205
+ if (body.isStatic) return;
206
+
207
+ const data = (body as any).gameData;
208
+ const isVested = data && data.vested;
209
+
210
+ context.save();
211
+ context.translate(body.position.x, body.position.y);
212
+ context.rotate(body.angle);
213
+
214
+ if (isVested) {
215
+ context.shadowColor = '#00ff7f';
216
+ context.shadowBlur = 10;
217
+ context.fillStyle = '#00ff7f';
218
+ context.fillText(body.label + " ✓", 0, 0);
219
+ } else {
220
+ context.shadowColor = 'black';
221
+ context.shadowBlur = 4;
222
+ context.fillStyle = '#FFFFFF';
223
+ context.fillText(body.label, 0, 0);
224
+ }
225
+
226
+ context.restore();
227
+ });
228
+ });
229
+ }
230
+
231
+ private initGameLoop() {
232
+ Matter.Events.on(this.runner, 'afterUpdate', () => {
233
+ if (!this.gameState.isPlaying()) return;
234
+
235
+ const dt = this.runner.delta;
236
+ this.gameState.tick(dt / 1000);
237
+
238
+ // Chaos Timer
239
+ if (this.gameState.valuation >= 1_000_000) {
240
+ this.chaosTimer -= dt;
241
+ if (this.chaosTimer <= 0) {
242
+ this.triggerChaosEvent();
243
+ this.chaosTimer = this.chaosInterval;
244
+ }
245
+ }
246
+
247
+ const cx = this.width / 2;
248
+ const officeWidth = 400;
249
+ const dangerWidth = 700;
250
+
251
+ this.engine.world.bodies.forEach(body => {
252
+ if (body.isStatic) return;
253
+ const data = (body as any).gameData;
254
+
255
+ const isOutside = Math.abs(body.position.x - cx) > dangerWidth / 2 ||
256
+ body.position.y > this.height + 50;
257
+
258
+ if (isOutside) {
259
+ Matter.Composite.remove(this.engine.world, body);
260
+ const reasons = ["VC Scolding!", "Server Outage!", "GDPR Violation!", "TechCrunch Hit Piece!", "IP Lawsuit!"];
261
+ this.gameState.penalty(50_000, reasons[Math.floor(Math.random() * reasons.length)]);
262
+ return;
263
+ }
264
+
265
+ if (data && !data.vested) {
266
+ const IsStill = body.speed < 0.15 && Math.abs(body.angularVelocity) < 0.05;
267
+ const Inside = Math.abs(body.position.x - cx) < officeWidth / 2;
268
+
269
+ if (IsStill && Inside) {
270
+ data.vestTimer += this.runner.delta;
271
+ if (data.vestTimer > 1000) {
272
+ data.vested = true;
273
+ this.gameState.addValuation(data.value);
274
+ }
275
+ } else {
276
+ data.vestTimer = 0;
277
+ }
278
+ }
279
+ });
280
+ });
281
+ }
282
+
283
+ private triggerChaosEvent() {
284
+ const eventId = Math.floor(Math.random() * 5);
285
+ switch (eventId) {
286
+ case 0:
287
+ this.onCrisis?.("STRATEGIC PIVOT", "CEO: 'We are pivoting to AI!' (Gravity Shift)");
288
+ this.engine.world.gravity.x = 0.2; // Reduced from 0.5 as requested
289
+ setTimeout(() => { this.engine.world.gravity.x = 0; }, 5000);
290
+ break;
291
+ case 1:
292
+ this.onCrisis?.("THE BIG REORG", "HR: 'New Org Chart Announced!' (No Friction)");
293
+ this.engine.world.bodies.forEach(b => {
294
+ if (b.isStatic) return;
295
+ (b as any).oldFriction = b.friction;
296
+ b.friction = 0;
297
+ b.frictionStatic = 0;
298
+ });
299
+ setTimeout(() => {
300
+ this.engine.world.bodies.forEach(b => {
301
+ if (b.isStatic) return;
302
+ b.friction = (b as any).oldFriction || 0.1;
303
+ b.frictionStatic = 0.5;
304
+ });
305
+ }, 5000);
306
+ break;
307
+ case 2:
308
+ this.onCrisis?.("LEGACY DEPRECATION", "CTO: 'Deprecating v1 API!' (Ghost Blocks)");
309
+ const active = this.engine.world.bodies.filter(b => !b.isStatic);
310
+ if (active.length > 3) {
311
+ const target = active[Math.floor(Math.random() * active.length)];
312
+ target.isSensor = true;
313
+ setTimeout(() => { target.isSensor = false; }, 4000);
314
+ }
315
+ break;
316
+ case 3:
317
+ this.onCrisis?.("FEATURE CREEP", "PM: 'Just one more feature...' (1.2x Scaling)");
318
+ this.engine.world.bodies.forEach(b => {
319
+ if (!b.isStatic) Matter.Body.scale(b, 1.2, 1.2);
320
+ });
321
+ break;
322
+ case 4:
323
+ this.onCrisis?.("VIRAL SPIKE", "DevOps: 'Traffic Spike!' (Earthquake)");
324
+ let shakes = 0;
325
+ const interval = setInterval(() => {
326
+ this.engine.world.bodies.forEach(b => {
327
+ if (!b.isStatic) Matter.Body.applyForce(b, b.position, { x: (Math.random()-0.5)*0.05, y: (Math.random()-0.5)*0.05 });
328
+ });
329
+ if (++shakes > 20) clearInterval(interval);
330
+ }, 100);
331
+ break;
332
+ }
333
+ }
334
+
335
+ public toggleRotation() {
336
+ this.spawnVertical = !this.spawnVertical;
337
+ }
338
+
339
+ public spawnBlock(type: BlockType) {
340
+ if (!this.gameState.isPlaying()) return;
341
+ if (Math.random() < 1/15) {
342
+ const toxics = [BlockType.SLACKER, BlockType.CONTROL_FREAK, BlockType.PRIMA_DONNA, BlockType.QUIET_QUITTER, BlockType.SLACK_SPAMMER];
343
+ type = toxics[Math.floor(Math.random() * toxics.length)];
344
+ }
345
+
346
+ if (type === BlockType.BOOTSTRAP) this.gameState.spendMoney(5_000);
347
+ else if (type === BlockType.VC) this.gameState.addMoney(75_000); // 1.5x of 50k
348
+
349
+ const x = this.width / 2 + (Math.random() - 0.5) * 40;
350
+ const block = createBlock(x, y, type); // y is 100
351
+ let angle = this.spawnVertical ? 90 * (Math.PI / 180) : 0;
352
+ if (type === BlockType.VC) angle += (Math.random() - 0.5) * (30 * (Math.PI / 180));
353
+ Matter.Body.setAngle(block, angle);
354
+
355
+ (block as any).gameData = {
356
+ type,
357
+ value: type === BlockType.VC ? 50_000_000 : (type === BlockType.BOOTSTRAP ? 5_000_000 : 0),
358
+ vested: false,
359
+ vestTimer: 0
360
+ };
361
+ Matter.Composite.add(this.engine.world, block);
362
+ }
363
+ }
364
+
365
+ // Fixed constant y at the end
366
+ const y = 100;