npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jashwanthj/gestify

v1.0.0

Published

Add gesture navigation to your website with ease.

Readme

Gestify

Add gesture navigation to your website with ease.

Version License Size Types


Gestify lets visitors navigate your website using hand gestures through their webcam. It's privacy-first, needs no API key, and takes one line of code to integrate.

Gestify.init();

The SDK handles everything — consent prompt, camera permission, gesture detection — automatically.


Why Gestify

Most gesture libraries are complex. Gestify is not.

  • No configuration required — works out of the box
  • No API key, no server — fully offline capable
  • No video leaves the browser — all processing is local
  • No buttons to wire up — built-in consent modal
  • No framework lock-in — works everywhere

Installation

npm install @jashwanthj/gestify

CDN (no build step required):

<script src="https://unpkg.com/@jashwanthj/gestify/dist/gestify.min.js"></script>

Quick Start

npm / ESM

import Gestify from '@jashwanthj/gestify';

Gestify.init();

CDN / Script tag

<script src="https://unpkg.com/@jashwanthj/gestify/dist/gestify.min.js"></script>
<script>
  Gestify.init();
</script>

That's it. Gestify shows a consent prompt on first visit, remembers the user's choice, and handles everything automatically from there.


Profiles

Choose a preset tuned for your use case:

Gestify.init({ profile: 'presentation' });

| Profile | Best For | Speed | Hold Time | |:--|:--|:--|:--| | default | Most websites | 1× | 700 ms | | presentation | Slides, demos | 1.3× | 650 ms | | accessibility | Motor impairments | 0.8× | 1000 ms | | fast | Power users | 1.7× | 500 ms | | gaming | Interactive UIs | 2× | 400 ms |


Configuration

Fine-tune individual parameters on top of any profile:

Gestify.init({
  profile: 'default',
  overrides: {
    holdTime: 800,           // ms to hold open-palm to enter cursor mode
    exitHoldTime: 2000,      // ms to hold fist to exit cursor mode
    gestureSpeed: 1.2,       // cursor speed multiplier
    scrollSpeed: 1.0,        // scroll speed multiplier
    clickCooldown: 700,      // ms between gesture-triggered clicks
    cursorModeEnabled: true, // enable cursor-mode gestures
    sensitivity: 'medium',   // 'low' | 'medium' | 'high'
  },
  selector: 'button, a',     // custom CSS selector for gesture targets
  debug: true,               // enable [Gestify] console logs
});

API Reference

Gestify.init(options?) — start here

Initializes Gestify and runs the full consent flow automatically.

| Scenario | What Gestify does | |:--|:--| | First visit | Shows a consent toast | | Previously enabled | Silently restores camera | | Camera already granted | Starts immediately | | Camera blocked | Stays quiet | | Dismissed this session | Stays quiet |

Gestify.enable() — advanced

Programmatically start gestures, bypassing the built-in modal. Only needed if you're building a custom consent UI.

Gestify.disable()

Stop gesture tracking. Camera is released. SDK stays initialized.

Gestify.destroy()

Full teardown — stops camera, removes all DOM elements, clears observers.

Gestify.isEnabled()boolean

Gestify.getPermissionState()Promise<'granted' | 'denied' | 'prompt' | 'unknown'>

Cursor Mode

Gestify.enableCursorMode()
Gestify.disableCursorMode()
Gestify.toggleCursorMode()
Gestify.isCursorModeActive() // → boolean

Framework Examples

React

// components/GestureInit.jsx
import { useEffect } from 'react';

export default function GestureInit({ profile = 'default' }) {
  useEffect(() => {
    import('@jashwanthj/gestify').then(({ default: Gestify }) => Gestify.init({ profile }));
    return () => import('@jashwanthj/gestify').then(({ default: Gestify }) => Gestify.destroy());
  }, []);
  return null;
}

// App.jsx
import GestureInit from './GestureInit';

export default function App() {
  return <>
    <GestureInit />
    {/* rest of your app */}
  </>;
}

Next.js

// components/GestureInit.jsx
'use client';
import { useEffect } from 'react';

export default function GestureInit({ profile = 'default' }) {
  useEffect(() => {
    import('@jashwanthj/gestify').then(({ default: Gestify }) => Gestify.init({ profile }));
    return () => import('@jashwanthj/gestify').then(({ default: Gestify }) => Gestify.destroy());
  }, []);
  return null;
}

App Router (app/layout.jsx):

import GestureInit from '@/components/GestureInit';

export default function RootLayout({ children }) {
  return <html><body>{children}<GestureInit /></body></html>;
}

Pages Router (_app.jsx):

import dynamic from 'next/dynamic';
const GestureInit = dynamic(() => import('../components/GestureInit'), { ssr: false });

export default function App({ Component, pageProps }) {
  return <><Component {...pageProps} /><GestureInit /></>;
}

Vue 3

<!-- components/GestureInit.vue -->
<template><!-- Gestify renders its own UI --></template>

<script setup>
import { onMounted, onUnmounted } from 'vue';
const props = defineProps({ profile: { type: String, default: 'default' } });

onMounted(async () => {
  const { default: Gestify } = await import('@jashwanthj/gestify');
  Gestify.init({ profile: props.profile });
});

onUnmounted(async () => {
  const { default: Gestify } = await import('@jashwanthj/gestify');
  Gestify.destroy();
});
</script>

App.vue:

<template>
  <GestureInit />
  <RouterView />
</template>

<script setup>
import GestureInit from '@/components/GestureInit.vue';
</script>

Angular

// gesture-init.component.ts
import { Component, OnDestroy, Input } from '@angular/core';

@Component({ selector: 'app-gesture-init', standalone: true, template: '' })
export class GestureInitComponent implements OnDestroy {
  @Input() profile = 'default';
  private sdk: any = null;

  constructor() {
    if (typeof window === 'undefined') return; // SSR guard
    import('@jashwanthj/gestify').then(({ default: Gestify }) => {
      this.sdk = Gestify;
      Gestify.init({ profile: this.profile });
    });
  }

  ngOnDestroy() { this.sdk?.destroy(); }
}

TypeScript

import Gestify from '@jashwanthj/gestify';
import type { GestifyInitOptions } from '@jashwanthj/gestify';

const options: GestifyInitOptions = {
  profile: 'accessibility',
  overrides: { holdTime: 1200, sensitivity: 'high' },
  debug: true,
};

Gestify.init(options);

window.addEventListener('gestify:enabled', () => {
  console.log('Gesture navigation active');
});

window.addEventListener('gestify:click', (e) => {
  const { x, y } = e.detail; // fully typed
});

Events

Listen to Gestify events on window:

window.addEventListener('gestify:enabled',          () => { /* started */ });
window.addEventListener('gestify:disabled',         () => { /* stopped */ });
window.addEventListener('gestify:cursorModeEnter',  () => { /* cursor on */ });
window.addEventListener('gestify:cursorModeExit',   (e) => console.log(e.detail.reason));
window.addEventListener('gestify:click',            (e) => console.log(e.detail)); // { x, y }
window.addEventListener('gestify:scroll',           (e) => console.log(e.detail)); // { direction, amount }
window.addEventListener('gestify:permissionDenied', (e) => console.log(e.detail.reason));
window.addEventListener('gestify:consentDismissed', () => { /* user clicked No */ });
window.addEventListener('gestify:error',            (e) => console.error(e.detail.message));

Browser Support

| Browser | Minimum Version | |:--|:--| | Chrome | 90+ | | Edge | 90+ | | Firefox | 90+ | | Safari | 15+ |

HTTPS required. Camera access is restricted to secure origins. localhost works in development.

Desktop only. Mobile and tablet devices are not supported — gesture control requires a desktop webcam.


Privacy

Gestify is built privacy-first:

| | | |:--|:--| | 🔒 All processing is local | Powered by MediaPipe — runs entirely in the browser | | 🚫 No video transmitted | Camera frames never leave the device | | 🚫 No analytics | Zero data collection | | 🚫 No background access | Camera only activates after explicit user consent | | ✅ User is in control | Can disable at any time; consent persists across sessions |

Gestify is compliant with GDPR, CCPA, and similar privacy regulations.


Troubleshooting

Camera permission denied

const state = await Gestify.getPermissionState();
// If 'denied' — ask user to reset via browser Settings → Privacy → Camera

No gestures detected

  • Ensure good lighting (avoid strong backlight)
  • Keep hand 30–60 cm from camera
  • Try profile: 'accessibility' for more forgiving thresholds
  • Enable debug: true and check the browser console

SSR errors (Next.js, Nuxt, Angular Universal)

Always use a dynamic import — Gestify is browser-only:

// ✅ correct
import('@jashwanthj/gestify').then(({ default: Gestify }) => Gestify.init());

// ❌ incorrect — will break SSR
import Gestify from '@jashwanthj/gestify';

Content Security Policy (CSP)

Gestify loads MediaPipe from jsDelivr. Allow it:

Content-Security-Policy: script-src 'self' https://cdn.jsdelivr.net;

Contributing

Pull requests are welcome. See CONTRIBUTING.md to get started.


Changelog

See CHANGELOG.md.


License

MIT © Gestify Contributors