{"_id":"@dotopototo/phosphor-vue","_rev":"9-791cb3e37df86f239bbd2adedd0eb4ce","name":"@dotopototo/phosphor-vue","dist-tags":{"latest":"0.5.0"},"versions":{"0.2.1":{"name":"@dotopototo/phosphor-vue","version":"0.2.1","keywords":["crt","phosphor","scanlines","barrel","vintage","retro","terminal","vue","vue3","directive"],"author":{"name":"DotoPototo"},"license":"MIT","_id":"@dotopototo/phosphor-vue@0.2.1","maintainers":[{"name":"dotopototo","email":"mail@doto.gg"}],"homepage":"https://github.com/DotoPototo/phosphor#readme","bugs":{"url":"https://github.com/DotoPototo/phosphor/issues"},"dist":{"shasum":"9be746f8871fa5d12da3347e526a77fb33e9c79c","tarball":"https://registry.npmjs.org/@dotopototo/phosphor-vue/-/phosphor-vue-0.2.1.tgz","fileCount":20,"integrity":"sha512-Swrpw6Y8Q4GC9JRSNgfh5moRwl9YhRLiIG9cjZ8yFkREhu2OEsTNqB+2Yk27ZeoxBSOClxRmDCu1N9aQz9bCMg==","signatures":[{"sig":"MEUCIH0TogjeP+c7hvW7PVNYYSCQx7B7IgMPMRdSqXpCQwXOAiEAzNHW/CRoVNgDePQnVQE/qLQNdfY74Zg6tVOkm32cuLQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":261145},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./style.css":"./dist/style.css","./package.json":"./package.json"},"gitHead":"c5c20bd4684532ed553be5c8a9149fccab9e2d41","scripts":{"dev":"bunx --bun vite serve playground","lint":"run-s lint:oxlint lint:eslint","build":"vue-tsc --build && bunx --bun vite build","format":"prettier --write \"src/**/*.{ts,vue,css}\" \"playground/**/*.{ts,vue}\"","preview":"bunx --bun vite preview","release":"bun ./scripts/release.js","lint:check":"run-s lint:check:oxlint lint:check:eslint","playground":"bunx --bun vite serve playground","type-check":"vue-tsc --build","lint:eslint":"eslint . --fix --cache","lint:oxlint":"oxlint . --fix","format:check":"prettier --check \"src/**/*.{ts,vue,css}\" \"playground/**/*.{ts,vue}\"","playground:build":"bunx --bun vite build playground","lint:check:eslint":"eslint . --cache","lint:check:oxlint":"oxlint .","playground:preview":"bunx --bun vite preview playground"},"_npmUser":{"name":"dotopototo","email":"mail@doto.gg"},"repository":{"url":"git+https://github.com/DotoPototo/phosphor.git","type":"git"},"_npmVersion":"10.9.3","description":"CRT effect for live DOM — Vue 3 directive + component. Barrel distortion, scanlines, phosphor glow, chromatic fringing, noise, jitter, flicker, tracking artifacts.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"22.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.34","jiti":"^2.7.0","vite":"^8.0.12","eslint":"^10.4.0","oxlint":"^1.65.0","globals":"^17.6.0","vue-tsc":"^3.2.8","prettier":"^3.8.3","bun-types":"^1.3.14","typescript":"~6.0.2","@types/node":"^24.12.3","npm-run-all2":"^8.0.4","@vue/tsconfig":"^0.9.1","vite-plugin-dts":"^5.0.0","@tsconfig/node24":"^24.0.4","eslint-plugin-vue":"^10.9.1","@vitejs/plugin-vue":"^6.0.6","eslint-plugin-oxlint":"^1.65.0","eslint-config-prettier":"^10.1.8","@vue/eslint-config-typescript":"^14.7.0"},"peerDependencies":{"vue":"^3.5.0"},"_npmOperationalInternal":{"tmp":"tmp/phosphor-vue_0.2.1_1779030595520_0.42572197174978443","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@dotopototo/phosphor-vue","version":"0.4.0","keywords":["crt","phosphor","scanlines","barrel","vintage","retro","terminal","vue","vue3","directive"],"author":{"name":"DotoPototo"},"license":"MIT","_id":"@dotopototo/phosphor-vue@0.4.0","maintainers":[{"name":"dotopototo","email":"mail@doto.gg"}],"homepage":"https://phosphor-vue.vercel.app","bugs":{"url":"https://github.com/DotoPototo/phosphor/issues"},"dist":{"shasum":"227d494a528e854e727dad409f65afc58c843c25","tarball":"https://registry.npmjs.org/@dotopototo/phosphor-vue/-/phosphor-vue-0.4.0.tgz","fileCount":50,"integrity":"sha512-Vo3puYgf5kXtlBG5G76pVeHUh2GZis8cwt4txsG+dOupIdh6xBTjiiJXN+y59PNOrbGOH3iGkAHPj8puA5IUAw==","signatures":[{"sig":"MEUCIBj4yo35LxszAahJJnwe4ATpER8B24qYAFw8LZZzvb1ZAiEAqSMpS3Ee3LAsdRqhdGjwFTc2RApLVVY5KTjCaA215Qs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":786207},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./audio":{"types":"./dist/audio.d.ts","import":"./dist/audio.mjs","require":"./dist/audio.cjs"},"./style.css":"./dist/style.css","./package.json":"./package.json"},"gitHead":"a2c8c99eae38646ef375799de24fff70310d97f4","scripts":{"dev":"bunx --bun vite serve playground","lint":"run-s lint:oxlint lint:eslint","test":"bun run build && bun test","build":"vue-tsc --build && bunx --bun vite build","format":"prettier --write \"src/**/*.{ts,vue,css}\" \"playground/**/*.{ts,vue}\"","preview":"bunx --bun vite preview","release":"bun ./scripts/release.js","lint:check":"run-s lint:check:oxlint lint:check:eslint","playground":"bunx --bun vite serve playground","test:quick":"bun test","type-check":"vue-tsc --build","lint:eslint":"eslint . --fix --cache","lint:oxlint":"oxlint . --fix","format:check":"prettier --check \"src/**/*.{ts,vue,css}\" \"playground/**/*.{ts,vue}\"","playground:build":"bunx --bun vite build playground","lint:check:eslint":"eslint . --cache","lint:check:oxlint":"oxlint .","playground:preview":"bunx --bun vite preview playground"},"_npmUser":{"name":"dotopototo","email":"mail@doto.gg"},"repository":{"url":"git+https://github.com/DotoPototo/phosphor.git","type":"git"},"_npmVersion":"10.9.3","description":"CRT effect for live DOM — Vue 3 directive + component. Barrel distortion, scanlines, phosphor glow, chromatic fringing, noise, jitter, flicker, tracking artifacts.","directories":{},"sideEffects":["**/*.css"],"_nodeVersion":"22.18.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.5.34","jiti":"^2.7.0","vite":"^8.0.12","jsdom":"^29.1.1","eslint":"^10.4.0","oxlint":"^1.66.0","globals":"^17.6.0","vue-tsc":"^3.3.0","prettier":"^3.8.3","bun-types":"^1.3.14","typescript":"~6.0.2","@types/node":"^25.9.0","npm-run-all2":"^9.0.0","@vue/tsconfig":"^0.9.1","vite-plugin-dts":"^5.0.1","@tsconfig/node24":"^24.0.4","eslint-plugin-vue":"^10.9.1","@vitejs/plugin-vue":"^6.0.6","eslint-plugin-oxlint":"^1.66.0","eslint-config-prettier":"^10.1.8","@happy-dom/global-registrator":"^20.9.0","@vue/eslint-config-typescript":"^14.7.0"},"peerDependencies":{"vue":"^3.5.0"},"_npmOperationalInternal":{"tmp":"tmp/phosphor-vue_0.4.0_1779204222095_0.18840405243566427","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@dotopototo/phosphor-vue","version":"0.5.0","description":"CRT effect for live DOM — Vue 3 directive + component. Barrel distortion, scanlines, phosphor glow, chromatic fringing, noise, jitter, flicker, tracking artifacts.","type":"module","license":"MIT","author":{"name":"DotoPototo"},"homepage":"https://phosphor-vue.vercel.app","bugs":{"url":"https://github.com/DotoPototo/phosphor/issues"},"repository":{"type":"git","url":"git+https://github.com/DotoPototo/phosphor.git"},"publishConfig":{"access":"public"},"main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./audio":{"types":"./dist/audio.d.ts","import":"./dist/audio.mjs","require":"./dist/audio.cjs"},"./style.css":"./dist/style.css","./package.json":"./package.json"},"sideEffects":["**/*.css"],"scripts":{"dev":"bunx --bun vite serve playground","playground":"bunx --bun vite serve playground","build":"vue-tsc --build && bunx --bun vite build","playground:build":"bunx --bun vite build playground","playground:preview":"bunx --bun vite preview playground","preview":"bunx --bun vite preview","type-check":"vue-tsc --build","test":"bun run build && bun test","test:quick":"bun test","lint":"bun run lint:oxlint && bun run lint:eslint","lint:oxlint":"oxlint . --fix","lint:eslint":"eslint . --fix --cache","lint:check":"bun run lint:check:oxlint && bun run lint:check:eslint","lint:check:oxlint":"oxlint .","lint:check:eslint":"eslint . --cache","format":"prettier --write \"src/**/*.{ts,vue,css}\" \"playground/**/*.{ts,vue}\"","format:check":"prettier --check \"src/**/*.{ts,vue,css}\" \"playground/**/*.{ts,vue}\"","release":"bun ./scripts/release.js"},"peerDependencies":{"vue":"^3.5.0"},"devDependencies":{"@happy-dom/global-registrator":"^20.9.0","@socketsecurity/bun-security-scanner":"^1.1.2","@tsconfig/node24":"^24.0.4","@types/node":"^25.9.1","@vitejs/plugin-vue":"^6.0.6","@vue/eslint-config-typescript":"^14.7.0","@vue/tsconfig":"^0.9.1","bun-types":"^1.3.14","eslint":"^10.4.0","eslint-config-prettier":"^10.1.8","eslint-plugin-oxlint":"^1.67.0","eslint-plugin-vue":"^10.9.1","globals":"^17.6.0","jiti":"^2.7.0","jsdom":"^29.1.1","oxlint":"^1.67.0","prettier":"^3.8.3","typescript":"~6.0.2","vite":"^8.0.14","vite-plugin-dts":"^5.0.1","vue":"^3.5.35","vue-tsc":"^3.3.2"},"keywords":["crt","phosphor","scanlines","barrel","vintage","retro","terminal","vue","vue3","directive"],"gitHead":"32a315d48885e3b7665ae69035bf248397ad4cb5","_id":"@dotopototo/phosphor-vue@0.5.0","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-enG0+yVsKRSSxtFxRFfe2JZODAkRIVJWpBEqj8RzZ5NsLm8U+wcBsFr0Ed4Kux3vldCb7mEUPRv2FXY/ZOjaPw==","shasum":"71d15a43e8ed2e886562571dc145171873ecb752","tarball":"https://registry.npmjs.org/@dotopototo/phosphor-vue/-/phosphor-vue-0.5.0.tgz","fileCount":72,"unpackedSize":813119,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCeiCdN3I0vHbDzdMCUPPhhyheA7QdtEkyFHPjU7DdC2AIgLKrh1M6HO31+rb7CBsarSLvoOadV7tKJi2Hj5bSOQHQ="}]},"_npmUser":{"name":"dotopototo","email":"mail@doto.gg"},"directories":{},"maintainers":[{"name":"dotopototo","email":"mail@doto.gg"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/phosphor-vue_0.5.0_1784836299623_0.08064338162992435"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-17T12:45:06.694Z","modified":"2026-07-23T19:51:40.193Z","0.1.0-beta.0":"2026-05-17T12:45:06.990Z","0.2.0-beta.0":"2026-05-17T14:07:28.692Z","0.2.1":"2026-05-17T15:09:55.652Z","0.4.0":"2026-05-19T15:23:42.325Z","0.5.0":"2026-07-23T19:51:39.866Z"},"bugs":{"url":"https://github.com/DotoPototo/phosphor/issues"},"author":{"name":"DotoPototo"},"license":"MIT","homepage":"https://phosphor-vue.vercel.app","keywords":["crt","phosphor","scanlines","barrel","vintage","retro","terminal","vue","vue3","directive"],"repository":{"type":"git","url":"git+https://github.com/DotoPototo/phosphor.git"},"description":"CRT effect for live DOM — Vue 3 directive + component. Barrel distortion, scanlines, phosphor glow, chromatic fringing, noise, jitter, flicker, tracking artifacts.","maintainers":[{"name":"dotopototo","email":"mail@doto.gg"}],"readme":"# @dotopototo/phosphor-vue\n\nCRT visual + audio effect for Vue 3 — barrel, scanlines, phosphor glow, chromatic fringing, and optional programmatic audio (hum, whine, pops, ticks).\n\n## Install\n\n```bash\nbun add @dotopototo/phosphor-vue\n```\n\nImport the stylesheet **once** in your app entry:\n\n```ts\nimport '@dotopototo/phosphor-vue/style.css';\n```\n\n## Usage\n\nFour ways, pick whichever fits.\n\n**Plugin + directive (most common):**\n\n```ts\n// main.ts\nimport { createApp } from 'vue';\nimport { PhosphorPlugin } from '@dotopototo/phosphor-vue';\nimport '@dotopototo/phosphor-vue/style.css';\nimport App from './App.vue';\n\ncreateApp(App).use(PhosphorPlugin).mount('#app');\n```\n\n```vue\n<template>\n  <div v-crt=\"{ theme: 'phosphor' }\">\n    your content here\n  </div>\n</template>\n```\n\n**Component wrapper** (same effect, slot-based):\n\n```vue\n<script setup lang=\"ts\">\nimport { CrtSurface } from '@dotopototo/phosphor-vue';\n</script>\n\n<template>\n  <CrtSurface :options=\"{ theme: 'amber' }\">\n    your content here\n  </CrtSurface>\n</template>\n```\n\n**Composable** (when you want reactive state alongside the effect):\n\n```vue\n<script setup lang=\"ts\">\nimport { ref, useTemplateRef } from 'vue';\nimport { useCrt } from '@dotopototo/phosphor-vue';\n\nconst host = useTemplateRef<HTMLElement>('host');\nconst options = ref({ theme: 'phosphor' as const, transition: true });\nconst { snapshot, attached, powered, phase } = useCrt(host, options);\n</script>\n\n<template>\n  <div ref=\"host\">your content here</div>\n  <button v-if=\"attached && !powered\" @click=\"options.powered = true\">Power on</button>\n</template>\n```\n\n**Imperative** (script-driven, framework-agnostic):\n\n```ts\nimport { CRT } from '@dotopototo/phosphor-vue';\n\nconst controller = CRT.attach(myElement, { theme: 'apple' });\ncontroller.update({ screen: { jitter: 0.2 } });\ncontroller.detach();\n```\n\n## Power & lifecycle\n\nToggle the effect on and off by flipping `powered`. With the directive, mutate the bound options object; with the composable or controller, call `update({ powered })`.\n\n```vue\n<template>\n  <!-- Snap on/off (default) -->\n  <div v-crt=\"{ powered }\">…</div>\n\n  <!-- Animated, library defaults (1200ms on / 800ms off) -->\n  <div v-crt=\"{ powered, transition: true }\">…</div>\n\n  <!-- Custom durations — clamped to [500, 2000] ms or 0 for snap -->\n  <div v-crt=\"{ powered, transition: { on: 1500, off: 600 } }\">…</div>\n</template>\n```\n\nThe composable surfaces three reactive views over the controller's live state:\n\n```ts\nconst { snapshot, attached, powered, phase } = useCrt(host, options);\n\n// snapshot.value:  { attached, powered, phase }\n// attached.value:  is the effect bound to the element right now?\n// powered.value:   live visible state — true during on / powering-on / powering-off,\n//                  false ONLY once the off animation has fully settled.\n// phase.value:     'off' | 'powering-on' | 'on' | 'powering-off'\n```\n\nUse `powered` (not `options.value.powered`) for UI gating like a Power-on button — it stays `true` through the entire fade-out, so the button doesn't appear over the still-visible screen.\n\nFor non-Vue code, subscribe directly to the controller:\n\n```ts\nconst c = CRT.attach(el, { transition: true });\nlet state = c.snapshot;\nconst unsubscribe = c.onChange((s) => { state = s; /* re-render */ });\n```\n\n`controller.attached` / `controller.powered` are plain getters and *not* reactive — `onChange` (or the `useCrt` composable, which uses it internally) is the reactive bridge.\n\n## Audio\n\nOptional programmatic audio engine — hum, flyback whine, pops, ticks, zaps, plus power-on/off events. Imported from a separate sub-entry so consumers who don't need audio don't ship it:\n\n```ts\nimport { CrtAudio, useCrtAudio } from '@dotopototo/phosphor-vue/audio';\n```\n\nBrowsers block `AudioContext` until a user gesture. The composable holds a session iff BOTH `master.enabled === true` AND `powerIntent === true`. The audible startup / shutdown ceremony only plays alongside an *animated* visual transition — snap on/off stays silent.\n\n```vue\n<script setup lang=\"ts\">\nimport { ref } from 'vue';\nimport { CRT } from '@dotopototo/phosphor-vue';\nimport { CrtAudio, useCrtAudio } from '@dotopototo/phosphor-vue/audio';\n\nconst visualOpts = ref({ powered: true, transition: true });\nconst audioOpts = ref(CrtAudio.defaults);\n\nconst { running, needsGesture, start, audition } = useCrtAudio(\n  audioOpts,\n  () => visualOpts.value.powered,                              // power coordination\n  () => CRT.normalize(visualOpts.value).transition,            // ceremony fires only on animated transitions\n);\n\n// Enabling audio in owner mode: flip master.enabled (the source of truth)\n// AND call start() in the same click handler. The flip keeps the persisted\n// option in sync; start() resumes the AudioContext synchronously inside\n// the user-gesture window (the autoplay policy requires gesture-driven\n// resume). Skipping start() would push the resume into the reconcile\n// watcher's microtask, which may fall outside the gesture window.\nfunction onEnable() {\n  audioOpts.value.master.enabled = true;\n  void start();\n}\n</script>\n\n<template>\n  <button @click=\"onEnable\">Enable audio</button>\n  <button @click=\"audition('startup')\">Audition startup</button>\n</template>\n```\n\n12 named presets ship as a frozen registry:\n\n```ts\nimport { CRT_AUDIO_PRESETS } from '@dotopototo/phosphor-vue/audio';\naudioOpts.value = structuredClone(CRT_AUDIO_PRESETS.bunkerTerminal);\n```\n\n(`roomCrt`, `cleanOffice`, `bunkerTerminal`, `oldTv`, `dyingTube`, `palWorkstation`, `arcadeMonitor`, `distantRoom`, `whisper`, `brandNew`, `failingPower`, `serverRoom`.)\n\n## Options\n\nRather than mirror every field in this README — they'd drift — explore the options two ways:\n\n- **TypeScript types.** Your editor surfaces every field with its bounds and defaults as you author config. The relevant types: `CrtOptions`, `ResolvedCrtOptions`, `CrtAudioOptions`, `ResolvedCrtAudioOptions`, `CrtControllerSnapshot`, `CrtAudioState`.\n- **Live playground.** Clone the repo, run `bun run dev`, tinker visually, hit *Copy Settings* to paste the JS literal of your overrides into your code.\n\nIf you need bounds at runtime (e.g. building your own settings UI), the same registries the library uses internally are exported:\n\n```ts\nimport { CRT_FIELDS, type CrtNumericFieldPath } from '@dotopototo/phosphor-vue';\nimport { AUDIO_FIELDS } from '@dotopototo/phosphor-vue/audio';\n\nCRT_FIELDS['screen.barrel'];    // { type: 'number', default: 0.018, min: 0, max: 0.035 }\nAUDIO_FIELDS['hum.level'];      // { type: 'number', default: 0.34, min: 0, max: 1 }\n```\n\nEvery row is deeply frozen. Keys are type-checked — typos are compile errors.\n\n`CRT.defaults` and `CrtAudio.defaults` are the frozen *active* defaults — i.e. with mode exclusivity applied. `CRT.normalize(opts)` and `CrtAudio.normalize(opts)` show how an input resolves.\n\n## Caveats\n\n- **The element you hand to CRT is a positioning shell, not a layout container.** When CRT attaches, it moves your children into an internal `.crt-content` wrapper and adds an overlay sibling. Author your layout one level deeper:\n\n  ```html\n  <!-- bad: grid on the v-crt host breaks when the wrapper is inserted -->\n  <div v-crt style=\"display: grid; grid-template-rows: auto 1fr auto\">\n    <header/><main/><footer/>\n  </div>\n\n  <!-- good: grid lives in your own inner div -->\n  <div v-crt>\n    <div style=\"display: grid; grid-template-rows: auto 1fr auto; height: 100%\">\n      <header/><main/><footer/>\n    </div>\n  </div>\n  ```\n\n- **CSS is separate.** The one stylesheet import is required; there's no auto-inject because it doesn't play nicely with SSR.\n- **SSR-safe import.** Importing the package server-side won't crash. `CRT.normalize()` and `CRT.defaults` work anywhere. DOM-touching methods (`attach`, `attachAll`, `prime`) throw a clear \"requires a browser environment\" error if called server-side — keep them inside `onMounted` / `useEffect`. The `v-crt` directive and `<CrtSurface>` component are already mount-gated.\n- **Modern Chromium only.** Chrome, Edge, Brave, Arc, recent Electron. Firefox and Safari may render acceptably but no compatibility work is invested.\n- **Glow + ancestor `overflow: hidden`.** Text glow is a `text-shadow` halo around 30–40px wide. Any ancestor with `overflow: hidden` (including `text-overflow: ellipsis`) clips the halo at the box edge, leaving visible hard edges. Put clipping on a wrapper with enough padding, or move it higher up the tree.\n- **Form controls.** Native rendering (`appearance: button` / `textfield`) draws text via the OS and ignores `text-shadow`. To get glow + chromatic on form controls, set `appearance: none` and theme them yourself — standard practice for terminal/CRT UIs.\n- **Position.** If the host has `position: static`, the library sets it to `relative` so the overlay can anchor. Authored `absolute` / `fixed` / `sticky` is preserved.\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","readmeFilename":"README.md"}