emmetandrews 1.0.0 → 1.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.
Files changed (2) hide show
  1. package/index.js +235 -38
  2. package/package.json +8 -4
package/index.js CHANGED
@@ -17,7 +17,6 @@ const NAME = 'Emmet Andrews';
17
17
  const HANDLE = 'emmetandrews';
18
18
  const WIDTH = () => Math.min(process.stdout.columns || 72, 72);
19
19
 
20
- // Word-wrap a string to a given width, with an optional hanging indent
21
20
  function wrap(text, width, indent = '') {
22
21
  const words = text.split(' ');
23
22
  const lines = [];
@@ -35,20 +34,17 @@ function wrap(text, width, indent = '') {
35
34
  return lines.map((l, i) => (i === 0 ? l : indent + l)).join('\n');
36
35
  }
37
36
 
38
- // Wrap a bullet point with hanging indent so wrapped lines align past the bullet
39
37
  function bullet(text, width) {
40
38
  return ' ' + wrap(text, width - 2, ' ');
41
39
  }
42
40
 
43
- // Section header
44
41
  function header(title) {
45
42
  return `${C.yellow}${C.bold}${title}${C.reset}`;
46
43
  }
47
44
 
48
- // Job block
49
45
  function job(title, company, dates, bullets, width) {
50
46
  const lines = [
51
- `${C.bold}${C.yellow}${title} @ ${company} · ${dates}${C.reset}`,
47
+ `${C.bold}${C.yellow}${title} @ ${company}${C.reset} · ${dates}`,
52
48
  ];
53
49
  for (const b of bullets) lines.push(bullet('· ' + b, width));
54
50
  return lines.join('\n');
@@ -119,17 +115,21 @@ const DATA = {
119
115
  ].join('\n'),
120
116
 
121
117
  projects: (_w) => [
122
- `${C.yellow}portfolio-cli${C.reset}`,
123
- ` ${C.dim}This resume — you're running it right now${C.reset}`,
118
+ `${C.yellow}portfolio-cli${C.reset}\n ${C.dim}This resume — you're running it right now${C.reset}`,
124
119
  '',
125
- `${C.yellow}Movie Recommender Engine${C.reset}`,
126
- ` github.com/emmetand/streaming-recs-project`,
120
+ `${C.yellow}Movie Recommender Engine${C.reset}\n github.com/emmetand/streaming-recs-project`,
127
121
  '',
128
- `${C.yellow}Quant Equity Simulator${C.reset}`,
129
- ` github.com/emmetand/dynamic-quant-equity-simulator`,
122
+ `${C.yellow}Quant Equity Simulator${C.reset}\n github.com/emmetand/dynamic-quant-equity-simulator`,
130
123
  '',
131
- `${C.yellow}Customer Retention Dashboard${C.reset}`,
132
- ` github.com/emmetand/customer-retention-project`,
124
+ `${C.yellow}Customer Retention Dashboard${C.reset}\n github.com/emmetand/customer-retention-project`,
125
+ ].join('\n'),
126
+
127
+ interests: (w) => [
128
+ header('What I Do Outside of Data Science'),
129
+ bullet('Creative & Building: Woodworking, building fun little projects', w),
130
+ bullet('Media & Entertainment: Film/TV/Animation, video games, reading (sci-fi, fantasy, and everything else), collecting physical media, live comedy, and going to punk rock shows.', w),
131
+ bullet('Leisure & Travel: Going on long drives, golfing, thrifting, and tracking down little trinkets on vacation.', w),
132
+ bullet('Coffee: Aspiring home barista (Im my #1 customer).', w),
133
133
  ].join('\n'),
134
134
 
135
135
  contact: (_w) => [
@@ -139,6 +139,221 @@ const DATA = {
139
139
  ].join('\n'),
140
140
  };
141
141
 
142
+ // ─── Trivia Game ─────────────────────────────────────────────────────────────
143
+
144
+ const QUESTIONS = [
145
+ { q: 'What was the worldwide box office gross of Top Gun: Maverick (2022)?', a: 1493, unit: 'million USD' },
146
+ { q: 'How many total episodes did Breaking Bad run across all seasons?', a: 62, unit: 'episodes' },
147
+ { q: 'What year did Netflix launch its streaming service?', a: 2007, unit: 'year' },
148
+ { q: "What was The Godfather's worldwide box office gross at time of release (1972)?", a: 246, unit: 'million USD' },
149
+ { q: 'How many episodes did The Sopranos run across all seasons?', a: 86, unit: 'episodes' },
150
+ { q: 'What year did HBO launch as a network?', a: 1972, unit: 'year' },
151
+ { q: 'What was the opening weekend gross of Avengers: Endgame?', a: 357, unit: 'million USD' },
152
+ { q: 'How many seasons did The Wire run?', a: 5, unit: 'seasons' },
153
+ { q: 'What was the production budget of Titanic (1997)?', a: 200, unit: 'million USD' },
154
+ { q: 'How many total episodes has The Simpsons aired as of 2024?', a: 757, unit: 'episodes' },
155
+ { q: 'What year did the first season of Survivor air in the US?', a: 2000, unit: 'year' },
156
+ { q: 'How many films are in the Marvel Cinematic Universe as of 2024?', a: 33, unit: 'films' },
157
+ { q: 'What was the worldwide gross of Avatar (2009)?', a: 2923, unit: 'million USD' },
158
+ { q: 'How many episodes did Game of Thrones run across all seasons?', a: 73, unit: 'episodes' },
159
+ { q: 'What year did Disney+ launch?', a: 2019, unit: 'year' },
160
+ { q: 'How many screens did The Dark Knight open on in the US in 2008?', a: 4366, unit: 'screens' },
161
+ { q: 'What was the production budget of Oppenheimer (2023)?', a: 100, unit: 'million USD' },
162
+ { q: 'How many Emmy Awards has Game of Thrones won in total?', a: 59, unit: 'Emmys' },
163
+ { q: 'What year did AMC premiere Mad Men?', a: 2007, unit: 'year' },
164
+ { q: 'How many total seasons did The Office (US) run?', a: 9, unit: 'seasons' },
165
+ ];
166
+
167
+ function scoreGuess(guess, actual) {
168
+ const pct = Math.abs(guess - actual) / actual;
169
+ if (pct === 0) return { pts: 100, label: `${C.yellow}EXACT — incredible instincts!${C.reset}` };
170
+ if (pct <= 0.05) return { pts: 80, label: `${C.green}Within 5% — sharp analyst!${C.reset}` };
171
+ if (pct <= 0.15) return { pts: 60, label: `${C.cyan}Within 15% — solid estimate.${C.reset}` };
172
+ if (pct <= 0.30) return { pts: 40, label: `${C.white}Within 30% — not bad.${C.reset}` };
173
+ return { pts: 10, label: `${C.dim}More than 30% off — tough one.${C.reset}` };
174
+ }
175
+
176
+ function analystRating(total) {
177
+ if (total >= 450) return `${C.bold}${C.yellow}SENIOR ANALYST${C.reset}`;
178
+ if (total >= 350) return `${C.bold}${C.white}MID-LEVEL ANALYST${C.reset}`;
179
+ if (total >= 250) return `${C.bold}${C.cyan}JUNIOR ANALYST${C.reset}`;
180
+ return `${C.dim}INTERN${C.reset}`;
181
+ }
182
+
183
+ function shuffle(arr) {
184
+ return [...arr].sort(() => Math.random() - 0.5);
185
+ }
186
+
187
+ function playGame(rl) {
188
+ const questions = shuffle(QUESTIONS).slice(0, 5);
189
+ let round = 0;
190
+ let totalScore = 0;
191
+ const w = WIDTH();
192
+ const line = '─'.repeat(w);
193
+ const thinLine = `${C.dim}${'·'.repeat(w)}${C.reset}`;
194
+
195
+ rl.removeAllListeners('line');
196
+ rl.removeAllListeners('close');
197
+
198
+ console.log(`\n${C.dim}${line}${C.reset}`);
199
+ console.log(`${C.bold}${C.yellow} ANALYST MODE${C.reset} ${C.dim}Entertainment Industry Trivia${C.reset}`);
200
+ console.log(`${C.dim}${line}${C.reset}`);
201
+ console.log(`
202
+ ${C.bold}How to play:${C.reset}
203
+ Each question has a numerical answer. Enter your best guess.
204
+ You score points based on how close you are:
205
+
206
+ ${C.yellow}Exact answer${C.reset} → 100 pts
207
+ ${C.green}Within 5%${C.reset} → 80 pts
208
+ ${C.cyan}Within 15%${C.reset} → 60 pts
209
+ ${C.white}Within 30%${C.reset} → 40 pts
210
+ ${C.dim}Over 30% off${C.reset} → 10 pts
211
+
212
+ 5 rounds · 500 points possible · type 'quit' to exit
213
+ `);
214
+ console.log(`${C.dim}${line}${C.reset}\n`);
215
+
216
+ function printQuestion() {
217
+ const q = questions[round];
218
+ console.log(`${C.yellow}${C.bold}Round ${round + 1} of 5${C.reset} ${C.dim}(${totalScore} pts so far)${C.reset}`);
219
+ console.log(`${C.bold}${wrap(q.q, w)}${C.reset}\n`);
220
+ rl.setPrompt(` ${C.cyan}Your answer (${q.unit}):${C.reset} `);
221
+ rl.prompt();
222
+ }
223
+
224
+ function handleAnswer(input) {
225
+ const trimmed = input.trim().toLowerCase();
226
+
227
+ if (trimmed === 'quit' || trimmed === 'exit') {
228
+ console.log(`\n${C.dim}Exiting analyst mode...${C.reset}\n`);
229
+ return restoreNormalMode(rl);
230
+ }
231
+
232
+ const guess = parseInt(trimmed.replace(/[^0-9]/g, ''), 10);
233
+
234
+ if (isNaN(guess)) {
235
+ console.log(`${C.dim} Please enter a number.${C.reset}\n`);
236
+ rl.prompt();
237
+ return;
238
+ }
239
+
240
+ const q = questions[round];
241
+ const { pts, label } = scoreGuess(guess, q.a);
242
+ totalScore += pts;
243
+
244
+ console.log(`\n ${C.dim}Your guess:${C.reset} ${guess} ${q.unit}`);
245
+ console.log(` ${C.dim}Actual answer:${C.reset} ${C.bold}${q.a} ${q.unit}${C.reset}`);
246
+ console.log(` ${label}`);
247
+ console.log(` ${C.yellow}+${pts} pts${C.reset} ${C.dim}running total: ${totalScore} / 500${C.reset}\n`);
248
+ console.log(thinLine + '\n');
249
+
250
+ round++;
251
+
252
+ if (round >= questions.length) {
253
+ endGame();
254
+ } else {
255
+ printQuestion();
256
+ }
257
+ }
258
+
259
+ function endGame() {
260
+ console.log(`${C.dim}${line}${C.reset}`);
261
+ console.log(`\n ${C.bold}GAME OVER${C.reset}`);
262
+ console.log(` Final score: ${C.bold}${C.yellow}${totalScore} / 500${C.reset}`);
263
+ console.log(` Your rating: ${analystRating(totalScore)}\n`);
264
+ console.log(`${C.dim}${line}${C.reset}\n`);
265
+ restoreNormalMode(rl);
266
+ }
267
+
268
+ rl.on('line', handleAnswer);
269
+ rl.on('close', () => { console.log('\nBye!\n'); process.exit(0); });
270
+
271
+ printQuestion();
272
+ }
273
+
274
+ // ─── Shared command handler ───────────────────────────────────────────────────
275
+
276
+ function handleCommand(cmd, rl) {
277
+ const w = WIDTH();
278
+ if (cmd === 'exit' || cmd === 'quit') {
279
+ console.log('\nBye!\n');
280
+ process.exit(0);
281
+ } else if (cmd === 'clear') {
282
+ console.clear();
283
+ banner();
284
+ } else if (cmd === 'help') {
285
+ console.log('\n' + HELP() + '\n');
286
+ } else if (cmd === 'play') {
287
+ playGame(rl);
288
+ return;
289
+ } else if (cmd === 'emmet') {
290
+ console.log(`\n${C.yellow}`);
291
+ console.log(`....................................................................................................`);
292
+ console.log(`....................................................................................................`);
293
+ console.log(`............................................--:--:---:..............................................`);
294
+ console.log(`.......................................:-+######%%%###*+-...........................................`);
295
+ console.log(`....................................:--*#%%%%%%%%%%%%%%%%#**:.......................................`);
296
+ console.log(`...................................:+*#%@@%@@@@%%%%%%%@@%####+......................................`);
297
+ console.log(`.................................-=##%%%%%@@@%%%%%@@@%%@%%%%%#*:....................................`);
298
+ console.log(`................................+*#%%%%%@@%%%%%%%%%%%%%%%@@%%%@#=...................................`);
299
+ console.log(`...............................:+#%%@%%%%%%%%%#++=----+*#%@@@@@#*:..................................`);
300
+ console.log(`...............................=#%%%%%%%%%%%#+=-::::::----+#@@@@%=..................................`);
301
+ console.log(`..............................:=##%%%%######+:::.::::::---==+%%@%*..................................`);
302
+ console.log(`..............................:+##%%%%##*===-::::::::::----==*%%%#-.................................`);
303
+ console.log(`................................=#%%##*++---::::::::::::--==+*%%%#-.................................`);
304
+ console.log(`................................:#%%#*+=--::::::::::::::--===*%@%*:.................................`);
305
+ console.log(`.................................=%%*+=---:::::::::::::---==+*%%%*:.................................`);
306
+ console.log(`.................................=##*+====++==---=++**++====+=#%%+..................................`);
307
+ console.log(`..................................+++++*##%%##+--+*#**##*++=+*###*:.................................`);
308
+ console.log(`..................................=*+=====--==+--=---------=+*##+=:.................................`);
309
+ console.log(`..................................:++=--::::--=-----::::::-=++*+=-..................................`);
310
+ console.log(`...................................=++-::::-=+=--===--::--==+**=+:..................................`);
311
+ console.log(`...................................:+++=---==++--====-----=++++=:...................................`);
312
+ console.log(`....................................-+++==+==+*##*++======+++=::....................................`);
313
+ console.log(`.....................................:++======+=============+-......................................`);
314
+ console.log(`......................................===--+#+---:-=*#+--==++.......................................`);
315
+ console.log(`......................................:+=---=========---==++:.......................................`);
316
+ console.log(`........................................++=--======----==+**........................................`);
317
+ console.log(`........................................:*+=----------=++*#+........................................`);
318
+ console.log(`........................................:*#*+=------==*##**+-.......................................`);
319
+ console.log(`........................................:+*##**++*+***#**++=-==.....................................`);
320
+ console.log(`........................................-+***#########*++=::-=#*....................................`);
321
+ console.log(`.....................................:+%--=+*********++=:::::=%@%*:.................................`);
322
+ console.log(`..................................:+#%@#-::-+++++++++=:::::::=%@@@%#*-.......... ...... ...... ...`);
323
+ console.log(`..............................:+%%%@@@@*-::::-++++=-:.....:::*%@@@%%%%%%+:.. ........ ..... ........`);
324
+ console.log(`..........................:=#%%%@@@@@@%=-::...-==-.......::::%@@@@@%%%%%%%%*-.......................`);
325
+ console.log(`......................-+#%%%%@@@@@@@@@*::::.-#@%%%*:........*%%@@@@%@@@%%%%%%%#-....................`);
326
+ console.log(`....... .......=#%%%%%%@@@@@@@@@@@%=::::=+*@%%@=--......:%%%@@@@@@@@%%%%%%%%%%%%#=........... ...`);
327
+ console.log(`................-%%@%%%@@@@@@@@@@@@@@#::::-:::*@@*..:-:....-%%@@@@@@@@@@@@@%%%%%%%%%%%%*:...........`);
328
+ console.log(`...............:#%@@@@@@@@@@@@@@@@@@@*:.::....+%%*.....::..=%@@@@@@@@@@@@@@@@@@%%%%%%%@@#. .........`);
329
+ console.log(`...............=%@@@@@@@@@@@@@@@@@@@%+:..:...:@%%%:........*@@@@@@@@@@@@@@@@@@@@@%%%@@@@%+...... ...`);
330
+ console.log(`....... .......%@@@@@@@@@@@@@@@@@@@@%=.......*@%%%=.......:#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%..........`);
331
+ console.log(`..............:%@@@@@@@@@@@@@@@@@@@@%-..:...-#%%%%*.......-%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%..........`);
332
+ console.log(`..............+@@@@@@@@@@@@@@@@@@@@@#:..:...=%%%%%#.......+@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@-.........`);
333
+ console.log(`..............%@@@@@@@@@@@@@@@@@@@@@*...:...+%%%%%#:.....:#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%:........`);
334
+ console.log(`............ @@@@@@@@@@@@@@@@@@@@@@@...:...*%%%%%#:.....:%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*........`);
335
+ console.log(`...... ......*@@@@@@@@@@@@@@@@@@@@@@=..:...:*%%%%%#:.....=@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%........`);
336
+ console.log(`............:#@@@@@@@@@@@@@@@@@@@@@@=..:...:#%%%%%#:.....*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@=.......`);
337
+ console.log(`............-%@@@@@@@@@@@@@@@@@@@@@@-.:....:#%%%%%*:....:%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%.......`);
338
+ } else if (DATA[cmd]) {
339
+ console.log('\n' + DATA[cmd](w) + '\n');
340
+ } else if (cmd !== '') {
341
+ console.log(`${C.dim}command not found: ${cmd} — try 'help'${C.reset}\n`);
342
+ }
343
+ rl.prompt();
344
+ }
345
+
346
+ function restoreNormalMode(rl) {
347
+ rl.removeAllListeners('line');
348
+ rl.removeAllListeners('close');
349
+ rl.setPrompt(`${C.cyan}visitor@${HANDLE}${C.reset}:~$ `);
350
+ rl.on('line', (line) => handleCommand(line.trim().toLowerCase(), rl));
351
+ rl.on('close', () => { console.log('\nBye!\n'); process.exit(0); });
352
+ rl.prompt();
353
+ }
354
+
355
+ // ─── Help & Banner ────────────────────────────────────────────────────────────
356
+
142
357
  const HELP = () => [
143
358
  `${C.yellow}Commands${C.reset}`,
144
359
  ` ${C.cyan}about${C.reset} who I am`,
@@ -146,6 +361,7 @@ const HELP = () => [
146
361
  ` ${C.cyan}experience${C.reset} work history`,
147
362
  ` ${C.cyan}education${C.reset} academic background`,
148
363
  ` ${C.cyan}projects${C.reset} things I've built`,
364
+ ` ${C.cyan}interests${C.reset} hobbies & life outside work`,
149
365
  ` ${C.cyan}contact${C.reset} get in touch`,
150
366
  ` ${C.cyan}clear${C.reset} clear the screen`,
151
367
  ` ${C.cyan}exit${C.reset} quit`,
@@ -157,9 +373,12 @@ function banner() {
157
373
  console.log('\n' + C.dim + line + C.reset);
158
374
  console.log(`${C.bold}${C.yellow} ${NAME}${C.reset} ${C.dim}Interactive Resume${C.reset}`);
159
375
  console.log(C.dim + line + C.reset);
160
- console.log(`${C.dim} Type 'help' to see available commands${C.reset}\n`);
376
+ console.log(`\n ${C.bold}Welcome to Emmet's Interactive Resume!${C.reset}`);
377
+ console.log(`\n${HELP()}\n`);
161
378
  }
162
379
 
380
+ // ─── Boot ─────────────────────────────────────────────────────────────────────
381
+
163
382
  const rl = readline.createInterface({
164
383
  input: process.stdin,
165
384
  output: process.stdout,
@@ -168,27 +387,5 @@ const rl = readline.createInterface({
168
387
 
169
388
  banner();
170
389
  rl.prompt();
171
-
172
- rl.on('line', (line) => {
173
- const cmd = line.trim().toLowerCase();
174
- const w = WIDTH();
175
-
176
- if (cmd === 'exit' || cmd === 'quit') {
177
- console.log('\nBye!\n');
178
- process.exit(0);
179
- } else if (cmd === 'clear') {
180
- console.clear();
181
- banner();
182
- } else if (cmd === 'help') {
183
- console.log('\n' + HELP() + '\n');
184
- } else if (DATA[cmd]) {
185
- console.log('\n' + DATA[cmd](w) + '\n');
186
- } else if (cmd !== '') {
187
- console.log(`${C.dim}command not found: ${cmd} — try 'help'${C.reset}\n`);
188
- }
189
-
190
- rl.prompt();
191
- }).on('close', () => {
192
- console.log('\nBye!\n');
193
- process.exit(0);
194
- });
390
+ rl.on('line', (line) => handleCommand(line.trim().toLowerCase(), rl));
391
+ rl.on('close', () => { console.log('\nBye!\n'); process.exit(0); });
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "emmetandrews",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Interactive Terminal resume for Emmet Andrews",
5
- "bin": {
6
- "emmetandrews": "./index.js"
5
+ "bin": {
6
+ "emmetandrews": "index.js"
7
7
  },
8
8
  "preferGlobal": false,
9
- "keywords": ["resume", "cli", "portfolio"],
9
+ "keywords": [
10
+ "resume",
11
+ "cli",
12
+ "portfolio"
13
+ ],
10
14
  "author": "Emmet Andrews"
11
15
  }