{"_id":"@coffeeandfun/konami-code-detector","_rev":"2-92a0af832c741cd3eaaa1b94c134db86","name":"@coffeeandfun/konami-code-detector","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@coffeeandfun/konami-code-detector","version":"1.0.0","keywords":["konami","konami-code","easter-egg","keyboard","events","async","chrome-extension","vue","browser","cheat-code"],"author":{"name":"Robert James Gabriel","email":"robert_gabriel@outlook.com"},"license":"MIT","_id":"@coffeeandfun/konami-code-detector@1.0.0","maintainers":[{"name":"robertjgabriel","email":"robert_gabriel@outlook.com"}],"homepage":"https://github.com/coffee-and-fun/konami-code-detector#readme","bugs":{"url":"https://github.com/coffee-and-fun/konami-code-detector/issues"},"dist":{"shasum":"4493994eae1911fb6b6b7bda8ca7191ac75803f6","tarball":"https://registry.npmjs.org/@coffeeandfun/konami-code-detector/-/konami-code-detector-1.0.0.tgz","fileCount":5,"integrity":"sha512-F7NpWL0GSAAISIn7VUn3SNN9I7XmrJiFblKK62BvlkFlYbpFKpgVk0ZzezJecMO/n7Wo4f93tncvUBOXOfME8Q==","signatures":[{"sig":"MEYCIQCWNHE2taYEVm6bMyI0WERY2tNBENaU9tvG9hToqGbpVgIhAP4X81ZGlTt3tlBwdK97IzmHuM1qnYaL6OO2VaS6yDy4","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":42042},"jest":{"testMatch":["**/src/**/*.test.js"],"transform":{},"testEnvironment":"jsdom"},"main":"./src/index.js","type":"module","babel":{"presets":[["@babel/preset-env",{"targets":{"node":"current"}}]]},"module":"./src/index.js","engines":{"node":">=14.0.0"},"exports":"./src/index.js","gitHead":"99001d0937bf824b861d41e4c0e1723b7548ed29","scripts":{"test":"jest","test:watch":"jest --watch","test:coverage":"jest --coverage","prepublishOnly":"npm test"},"_npmUser":{"name":"robertjgabriel","email":"robert_gabriel@outlook.com"},"repository":{"url":"git+https://github.com/coffee-and-fun/konami-code-detector.git","type":"git"},"_npmVersion":"10.9.0","description":"A modern, async-enabled Konami Code detector","directories":{},"_nodeVersion":"22.11.0","_hasShrinkwrap":false,"devDependencies":{"jest":"^29.7.0","babel-jest":"^29.7.0","@babel/core":"^7.28.4","@babel/preset-env":"^7.28.3","jest-environment-jsdom":"^29.7.0"},"_npmOperationalInternal":{"tmp":"tmp/konami-code-detector_1.0.0_1758781652225_0.1219698422535056","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@coffeeandfun/konami-code-detector","version":"1.0.1","description":"A modern, async-enabled Konami Code detector","type":"module","main":"./index.js","types":"./index.d.ts","exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"}},"scripts":{"test":"node --experimental-vm-modules node_modules/jest/bin/jest.js","test:watch":"node --experimental-vm-modules node_modules/jest/bin/jest.js --watch","test:coverage":"node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage","prepublishOnly":"npm test"},"keywords":["konami","konami-code","easter-egg","keyboard","events","async","chrome-extension","vue","browser","cheat-code"],"engines":{"node":">=16"},"devDependencies":{"jest":"^29.7.0","jest-environment-jsdom":"^29.7.0"},"jest":{"testEnvironment":"jsdom","transform":{},"testMatch":["**/*.test.js"],"testPathIgnorePatterns":["/node_modules/"]},"author":{"name":"Robert James Gabriel","email":"robert_gabriel@outlook.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/coffee-and-fun/konami-code-detector.git"},"gitHead":"26e715c0c501fc3f721af8d76e75e4acc12b58c6","_id":"@coffeeandfun/konami-code-detector@1.0.1","bugs":{"url":"https://github.com/coffee-and-fun/konami-code-detector/issues"},"homepage":"https://github.com/coffee-and-fun/konami-code-detector#readme","_nodeVersion":"20.20.2","_npmVersion":"11.12.1","dist":{"integrity":"sha512-Cswb9JkQ8SXSvWPVDrJQqarL1V0ySIeTHFMbksQjfTaSpIDAQuEbqq+vGFpln4G1bgTcJoVckCCDPajdANWZ8Q==","shasum":"7d9781dcaad2696d54ac605ed8d157b2a9f3b8a0","tarball":"https://registry.npmjs.org/@coffeeandfun/konami-code-detector/-/konami-code-detector-1.0.1.tgz","fileCount":9,"unpackedSize":30762,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@coffeeandfun%2fkonami-code-detector@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDN0LI0I+1wLuMOSQzHfBQPnQludXL3/R1b+mBgXsJHYgIgDGpPYqDk3KxZDchrYPGv4uhSUKEKWR7TSImFvQK6jgk="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:c96db663-14fc-4183-a009-c080edb2af20"}},"directories":{},"maintainers":[{"name":"robertjgabriel","email":"robert_gabriel@outlook.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/konami-code-detector_1.0.1_1776691126022_0.5841600404970224"},"_hasShrinkwrap":false}},"time":{"created":"2025-09-25T06:27:32.140Z","modified":"2026-04-20T13:18:46.493Z","1.0.0":"2025-09-25T06:27:32.437Z","1.0.1":"2026-04-20T13:18:46.176Z"},"bugs":{"url":"https://github.com/coffee-and-fun/konami-code-detector/issues"},"author":{"name":"Robert James Gabriel","email":"robert_gabriel@outlook.com"},"license":"MIT","homepage":"https://github.com/coffee-and-fun/konami-code-detector#readme","keywords":["konami","konami-code","easter-egg","keyboard","events","async","chrome-extension","vue","browser","cheat-code"],"repository":{"type":"git","url":"git+https://github.com/coffee-and-fun/konami-code-detector.git"},"description":"A modern, async-enabled Konami Code detector","maintainers":[{"name":"robertjgabriel","email":"robert_gabriel@outlook.com"}],"readme":"# 🎮 @coffeeandfun/konami-code-detector\n\nA tiny, modern library that watches for the Konami Code (↑ ↑ ↓ ↓ ← → ← → B A) — or any key sequence you pick — and runs a callback when the user finishes it.\n\n- 🪶 Zero runtime dependencies\n- 🚀 Ships as native ESM with TypeScript declarations\n- 🧩 Works with React, Vue, Svelte, Angular, plain JavaScript, Chrome extensions — anywhere with a DOM\n- ⚙️ Configurable: custom sequences, cooldowns, one-shot mode, max-attempts, progress events, async callbacks\n- 🧪 Fully tested with Jest + jsdom\n\n## 📦 Install\n\n```bash\nnpm install @coffeeandfun/konami-code-detector\n```\n\nRequires Node.js 16+ to install. In the browser it works anywhere `AbortController` and modern classes are supported (Chrome 66+, Firefox 57+, Safari 11.1+, Edge 79+).\n\n## ⚡ Quick start\n\n```js\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\nconst konami = new KonamiCode(() => {\n  document.body.classList.add('party-mode');\n});\n\nawait konami.enable();\n// Press ↑ ↑ ↓ ↓ ← → ← → B A — the callback fires 🎉\n```\n\nThat's it. Everything else in this README is optional.\n\n## 🧠 Mental model\n\nYou create an instance, give it a callback, call `enable()`, and the detector listens for `keydown` events. Every correct key in the sequence fires a `progress` event. A wrong key resets progress and fires a `failed` event. When the user finishes the sequence:\n\n1. Your callback runs (sync or async — both are awaited).\n2. An `activated` event fires.\n3. A bubbling `konamicode` `CustomEvent` is dispatched on your target, so code outside the instance can react too.\n\nIf you stop caring, call `destroy()` and it cleans itself up.\n\n## 🖼️ What an instance looks like\n\n```js\nconst konami = new KonamiCode(callback, options);\n// →\n{\n  callback,               // the function you passed\n  config: {\n    sequence,             // resolved key list\n    timeout,              // ms of inactivity before progress resets\n    target,               // element the keydown listener is on\n    preventDefault,       // whether to preventDefault on the final key\n    once,                 // fire at most once?\n    debug,                // log state transitions?\n    maxAttempts,          // cap on activations\n    cooldown,             // ms between activations\n  },\n  attempts: 0,            // successful activations\n  stats: {\n    activations: 0,\n    attempts: 0,          // failed attempts (wrong keys)\n    lastActivated: null,  // ms timestamp of last success\n  },\n  // plus the methods documented below\n}\n```\n\n---\n\n## 🧪 Examples by framework\n\nEach example is self-contained and does the same thing: fires a callback when the Konami code is entered. Pick the one that matches your stack.\n\n### 🟨 Vanilla JavaScript (with a bundler)\n\n```js\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\nconst konami = new KonamiCode(() => {\n  alert('🎉 You found the secret!');\n});\n\nkonami.enable();\n```\n\n### 🌐 Plain HTML, no bundler (ESM CDN)\n\n```html\n<!doctype html>\n<html>\n  <body>\n    <h1>Try the Konami code 👀</h1>\n    <script type=\"module\">\n      import KonamiCode from 'https://esm.sh/@coffeeandfun/konami-code-detector';\n\n      const konami = new KonamiCode(() => {\n        document.body.style.background = 'linear-gradient(45deg, #ff006e, #8338ec)';\n      });\n\n      konami.enable();\n    </script>\n  </body>\n</html>\n```\n\n### ⚛️ React (hook version)\n\n```jsx\nimport { useEffect, useRef } from 'react';\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\nexport function App() {\n  const konamiRef = useRef(null);\n\n  useEffect(() => {\n    konamiRef.current = new KonamiCode(() => {\n      console.log('✨ activated in React!');\n    });\n    konamiRef.current.enable();\n\n    return () => {\n      konamiRef.current?.destroy();\n    };\n  }, []);\n\n  return <div>Try the Konami code 🎮</div>;\n}\n```\n\n### ⚛️ React (custom hook — reusable)\n\n```jsx\nimport { useEffect, useRef } from 'react';\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\nexport function useKonamiCode(callback, options) {\n  const callbackRef = useRef(callback);\n  callbackRef.current = callback;\n\n  useEffect(() => {\n    const konami = new KonamiCode(() => callbackRef.current?.(), options);\n    konami.enable();\n    return () => { konami.destroy(); };\n  }, []);\n}\n\n// Usage:\nfunction PartyButton() {\n  useKonamiCode(() => {\n    document.body.classList.add('party-mode');\n  });\n  return <button>Keep trying… 🕹️</button>;\n}\n```\n\n### 🟢 Vue 3 (Composition API)\n\n```vue\n<script setup>\nimport { onMounted, onUnmounted, ref } from 'vue';\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\nconst konami = ref(null);\n\nonMounted(() => {\n  konami.value = new KonamiCode(() => console.log('🎉 activated in Vue!'));\n  konami.value.enable();\n});\n\nonUnmounted(() => {\n  konami.value?.destroy();\n});\n</script>\n\n<template>\n  <p>Try the Konami code 🎮</p>\n</template>\n```\n\n### 🟢 Vue 3 (reusable composable)\n\n```js\n// composables/useKonamiCode.js\nimport { onMounted, onUnmounted, ref } from 'vue';\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\nexport function useKonamiCode(callback, options) {\n  const instance = ref(null);\n\n  onMounted(() => {\n    instance.value = new KonamiCode(callback, options);\n    instance.value.enable();\n  });\n\n  onUnmounted(() => {\n    instance.value?.destroy();\n  });\n\n  return instance;\n}\n```\n\n```vue\n<script setup>\nimport { useKonamiCode } from './composables/useKonamiCode';\n\nuseKonamiCode(() => {\n  document.body.classList.add('party-mode');\n});\n</script>\n```\n\n### 🔺 Svelte 4 / 5\n\n```svelte\n<script>\n  import { onMount, onDestroy } from 'svelte';\n  import KonamiCode from '@coffeeandfun/konami-code-detector';\n\n  let konami;\n\n  onMount(() => {\n    konami = new KonamiCode(() => console.log('🎉 activated in Svelte!'));\n    konami.enable();\n  });\n\n  onDestroy(() => {\n    konami?.destroy();\n  });\n</script>\n\n<p>Try the Konami code 🎮</p>\n```\n\n### 🅰️ Angular\n\n```ts\nimport { Component, OnDestroy, OnInit } from '@angular/core';\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\n@Component({\n  selector: 'app-root',\n  template: '<p>Try the Konami code 🎮</p>',\n})\nexport class AppComponent implements OnInit, OnDestroy {\n  private konami?: KonamiCode;\n\n  ngOnInit() {\n    this.konami = new KonamiCode(() => console.log('🎉 activated in Angular!'));\n    this.konami.enable();\n  }\n\n  ngOnDestroy() {\n    this.konami?.destroy();\n  }\n}\n```\n\n### ▲ Next.js (App Router — client component)\n\n```tsx\n'use client';\n\nimport { useEffect, useRef } from 'react';\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\nexport function KonamiListener() {\n  const ref = useRef<KonamiCode | null>(null);\n\n  useEffect(() => {\n    ref.current = new KonamiCode(() => {\n      console.log('🎉 activated on the client');\n    });\n    ref.current.enable();\n\n    return () => { ref.current?.destroy(); };\n  }, []);\n\n  return null;\n}\n```\n\nDrop `<KonamiListener />` in your root layout. The `'use client'` directive keeps it out of SSR where there's no `document`.\n\n### 🧩 Chrome extension (content script)\n\n```js\nimport KonamiCode from '@coffeeandfun/konami-code-detector';\n\nconst konami = new KonamiCode(async () => {\n  const response = await chrome.runtime.sendMessage({ type: 'KONAMI_ACTIVATED' });\n  console.log('unlocked 🔓', response);\n});\n\nkonami.enable();\n```\n\n```js\n// background.js\nchrome.runtime.onMessage.addListener((message, sender, sendResponse) => {\n  if (message.type === 'KONAMI_ACTIVATED') {\n    console.log('🎮 Konami activated on', sender.tab?.url);\n    sendResponse({ success: true });\n  }\n});\n```\n\n### 🧪 Node.js (headless testing)\n\nThe detector works without a DOM if you pass `target: null` and use the built-in `triggerKey` / `triggerSequence` helpers.\n\n```js\nimport KonamiCode, { SEQUENCES } from '@coffeeandfun/konami-code-detector';\n\nconst konami = new KonamiCode(() => console.log('🎉 activated'), { target: null });\nawait konami.enable();\nawait konami.triggerSequence(); // → logs \"🎉 activated\"\n```\n\n---\n\n## 🍳 Recipes\n\nReal patterns people use this for.\n\n### 🎨 Party mode (add a CSS class)\n\n```js\nconst konami = new KonamiCode(() => {\n  document.body.classList.add('party-mode');\n});\nkonami.enable();\n```\n\n### 🐛 Hidden debug panel (custom sequence)\n\n```js\nimport KonamiCode, { SEQUENCES } from '@coffeeandfun/konami-code-detector';\n\nconst konami = new KonamiCode(\n  () => {\n    document.querySelector('#debug-panel').hidden = false;\n    localStorage.setItem('debugMode', 'true');\n  },\n  { sequence: SEQUENCES.debug, once: true },\n);\n\nkonami.enable();\n// Type D-E-B-U-G to reveal the panel, once per page load.\n```\n\n### 📊 Progress bar\n\n```js\nconst konami = new KonamiCode();\nconst bar = document.querySelector('#konami-progress');\n\nkonami\n  .on('progress', ({ percentage }) => { bar.style.width = `${percentage}%`; })\n  .on('failed', () => { bar.style.width = '0%'; })\n  .on('activated', () => { bar.style.width = '100%'; });\n\nkonami.enable();\n```\n\n### ⏱️ Cooldown between activations\n\n```js\nconst konami = new KonamiCode(fireConfetti, { cooldown: 5000 });\nawait konami.enable();\n// Subsequent activations inside 5s are silently dropped.\n```\n\n### 🔂 Fire only once\n\n```js\nconst konami = new KonamiCode(unlockAchievement, { once: true });\nawait konami.enable();\n// After the first activation, the instance disables itself automatically.\n```\n\n### 🧭 Track analytics on activation\n\n```js\nconst konami = new KonamiCode();\n\nkonami.on('activated', ({ timestamp, activations }) => {\n  analytics.track('konami_code_activated', { timestamp, activations });\n});\n\nkonami.enable();\n```\n\n### 👂 Listen from outside the instance (DOM event)\n\n```js\nconst konami = new KonamiCode();\nkonami.enable();\n\ndocument.addEventListener('konamicode', (event) => {\n  console.log('activated at', event.detail.timestamp);\n  console.log('stats:', event.detail.stats);\n});\n```\n\n### 🔐 Admin-only unlock (async check)\n\n```js\nconst konami = new KonamiCode(async () => {\n  const user = await fetchCurrentUser();\n  if (user.role === 'admin') {\n    showAdminPanel();\n  }\n});\n\nkonami.enable();\n```\n\n### 🎚️ Multiple codes, one page\n\n```js\nconst debugMode = new KonamiCode(enableDebug, { sequence: SEQUENCES.debug });\nconst godMode   = new KonamiCode(enableGodMode, { sequence: ['g', 'o', 'd'], cooldown: 5000 });\nconst reset     = new KonamiCode(resetApp, { sequence: ['r', 'e', 's', 'e', 't'], once: true });\n\nawait Promise.all([debugMode.enable(), godMode.enable(), reset.enable()]);\n```\n\n---\n\n## 📚 API reference\n\n### Constructor\n\n```js\nnew KonamiCode(callback?, options?)\n```\n\n| Parameter  | Type       | Default           | Description                                      |\n| ---------- | ---------- | ----------------- | ------------------------------------------------ |\n| `callback` | `Function` | `defaultCallback` | Runs when the sequence completes. May be async.  |\n| `options`  | `Object`   | `{}`              | See below.                                       |\n\n### Options\n\n| Option           | Type          | Default             | Description                                                             |\n| ---------------- | ------------- | ------------------- | ----------------------------------------------------------------------- |\n| `sequence`       | `string[]`    | `SEQUENCES.classic` | Keys to detect, in order. Matched case-insensitively.                   |\n| `timeout`        | `number`      | `1000`              | Ms of inactivity before the in-progress sequence resets.                |\n| `target`         | `EventTarget` | `document`          | Element to listen on. Pass `null` for headless environments.            |\n| `preventDefault` | `boolean`     | `true`              | Call `event.preventDefault()` on the final matching keypress.           |\n| `once`           | `boolean`     | `false`             | Fire at most once, then auto-disable.                                   |\n| `debug`          | `boolean`     | `false`             | Log internal state transitions to the console.                          |\n| `maxAttempts`    | `number`      | `Infinity`          | Cap on successful activations.                                          |\n| `cooldown`       | `number`      | `0`                 | Minimum ms between successful activations.                              |\n| `autoEnable`     | `boolean`     | `false`             | Call `enable()` from the constructor.                                   |\n\n### Methods\n\n#### 🔌 Lifecycle\n\n| Method        | Returns                 | Description                                        |\n| ------------- | ----------------------- | -------------------------------------------------- |\n| `enable()`    | `Promise<KonamiCode>`   | Attach the keydown listener.                       |\n| `disable()`   | `Promise<KonamiCode>`   | Detach the listener and reset progress.            |\n| `reset()`     | `void`                  | Reset sequence progress without disabling.         |\n| `destroy()`   | `Promise<void>`         | Disable, clear event listeners, null the callback. |\n\n#### ⚙️ Configuration\n\n| Method              | Returns      | Description                                  |\n| ------------------- | ------------ | -------------------------------------------- |\n| `setCallback(fn)`   | `KonamiCode` | Replace the activation callback.             |\n| `setSequence(keys)` | `KonamiCode` | Replace the key sequence and reset progress. |\n\n#### 🔍 State inspection\n\n| Method          | Returns                                    | Description                               |\n| --------------- | ------------------------------------------ | ----------------------------------------- |\n| `isEnabled()`   | `boolean`                                  | Whether the listener is attached.         |\n| `isActivated()` | `boolean`                                  | Whether the code has fired at least once. |\n| `getProgress()` | `{ current, total, percentage }`           | Current position in the sequence.         |\n| `getStats()`    | `{ activations, attempts, lastActivated }` | Copy of stats.                            |\n| `resetStats()`  | `KonamiCode`                               | Zero out all stats.                       |\n\n#### 📣 Events\n\n| Method                | Returns           | Description                                 |\n| --------------------- | ----------------- | ------------------------------------------- |\n| `on(event, handler)`  | `KonamiCode`      | Register an event handler.                  |\n| `off(event, handler)` | `KonamiCode`      | Remove an event handler.                    |\n| `waitForActivation()` | `Promise<object>` | Resolves on the next successful activation. |\n\n#### 🧪 Testing helpers\n\n| Method              | Returns         | Description                                        |\n| ------------------- | --------------- | -------------------------------------------------- |\n| `triggerKey(key)`   | `Promise<void>` | Simulate a single keypress (no-op if not enabled). |\n| `triggerSequence()` | `Promise<void>` | Simulate the full configured sequence.             |\n\n### Events\n\nFired via `on()` / `off()`. Handlers may be async; the emitter awaits them all.\n\n| Event       | Payload                          | When it fires                             |\n| ----------- | -------------------------------- | ----------------------------------------- |\n| `enabled`   | `{ timestamp }`                  | After `enable()` attaches the listener.   |\n| `disabled`  | `{ timestamp }`                  | After `disable()` detaches it.            |\n| `progress`  | `{ current, total, percentage }` | On every correct key in the sequence.     |\n| `activated` | `{ timestamp, activations }`     | When the sequence completes.              |\n| `failed`    | `{ key, expected }`              | When a wrong key is pressed mid-sequence. |\n| `error`     | `{ error, timestamp }`           | If the callback throws.                   |\n\nA bubbling `konamicode` `CustomEvent` is also dispatched on the configured `target` when the code activates. Its `detail` is `{ timestamp, stats }`.\n\n### Built-in sequences\n\n```js\nimport { SEQUENCES } from '@coffeeandfun/konami-code-detector';\n\nSEQUENCES.classic; // ↑ ↑ ↓ ↓ ← → ← → B A\nSEQUENCES.simple;  // ↑ ↓ ← →\nSEQUENCES.debug;   // D E B U G\nSEQUENCES.admin;   // A D M I N\n```\n\nAll sequences are frozen. Pass your own array to `sequence` if none of them fit.\n\n---\n\n## 🧪 Testing\n\n```bash\nnpm test              # one run\nnpm run test:watch    # watch mode\nnpm run test:coverage # coverage report\n```\n\nTests are written with Jest and jsdom. They run under native ESM via `--experimental-vm-modules`, so `jest` is imported from `@jest/globals` inside test files rather than being a global.\n\n## 📜 License\n\nMIT — see [LICENSE](LICENSE).\n","readmeFilename":"README.md"}