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

@chiefaia/analytics

v0.1.1

Published

Shared GA4 analytics, consent management, and event taxonomy for Pokerzeno sites

Readme

@pokerzeno/analytics

Shared analytics package for all Pokerzeno sites. Provides:

  • Google Analytics 4 — consent-gated, deep event data
  • Cloudflare Web Analytics — cookieless, always-on pageview data
  • GDPR/CCPA Consent Banner — WCAG 2.2 AA, keyboard-accessible, focus-trapped modal
  • Typed Event Taxonomy — shared event names and typed helper functions

Architecture

AnalyticsProvider (root wrapper)
  └── ConsentProvider (state management)
       └── AnalyticsInner (loads GA4 after consent, Cloudflare immediately)
       └── ConsentBanner (bottom-fixed banner + customise modal)

Two-Layer Analytics Strategy

| Layer | Tool | Consent Required | Cookies | Purpose | |-------|------|-----------------|---------|---------| | 1 | Cloudflare Web Analytics | No | No | Baseline pageviews, Core Web Vitals | | 2 | Google Analytics 4 | Yes | Yes | Deep events, funnels, conversions |

Cloudflare fires immediately on load (cookieless = no consent needed under GDPR). GA4 only loads after the user clicks "Accept all" or toggles analytics on.

Usage

1. Add to root layout

import { AnalyticsProvider, ConsentBanner } from '@pokerzeno/analytics';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <AnalyticsProvider
          measurementId={process.env.NEXT_PUBLIC_GA4_MEASUREMENT_ID}
          appName="my-site"
          debug={process.env.NODE_ENV === 'development'}
        >
          {children}
          <ConsentBanner />
        </AnalyticsProvider>
      </body>
    </html>
  );
}

2. Add env vars

# .env.local
NEXT_PUBLIC_GA4_MEASUREMENT_ID=G-XXXXXXXXXX

3. Add "Manage cookies" link to footer

'use client';
import { useConsent } from '@pokerzeno/analytics';

function Footer() {
  const { openBanner } = useConsent();
  return (
    <footer>
      <button onClick={openBanner}>Manage cookies</button>
    </footer>
  );
}

4. Track events

import {
  trackBetPlaced, trackGameStart, trackGameEnd,
  trackCTAClicked, trackEmailCaptured, trackLessonStarted,
} from '@pokerzeno/analytics';

// Game events
trackGameStart({ variant: 'european', difficulty: 'medium', app_name: 'my-site' });
trackBetPlaced({ amount: 10, bet_type: 'red', variant: 'EU' });
trackGameEnd({ variant: 'EU', outcome: 'win', net_profit: 50, duration_sec: 120 });

// CTA clicks
trackCTAClicked({ name: 'play_now', position: 'hero', page: '/', destination: '/play' });

// Email capture
trackEmailCaptured('homepage_waitlist', '/');

5. Use the hook (in client components)

import { useAnalytics } from '@pokerzeno/analytics';

function MyComponent() {
  const { track, analyticsEnabled } = useAnalytics();
  return <button onClick={() => track('custom_event', { key: 'value' })}>Click</button>;
}

Event Taxonomy

All event names are in src/events/taxonomy.ts. Typed helpers exist for every event category.

Game Events

| Function | Event | Key Params | |----------|-------|-----------| | trackGameStart | game_start | variant, difficulty, app_name | | trackGameEnd | game_end | variant, outcome, net_profit, duration_sec | | trackBetPlaced | bet_placed | amount, bet_type, variant, chip_value | | trackActionTaken | action_taken | action, position, pot_size, street | | trackVariantChanged | variant_changed | from, to | | trackDifficultySelected | difficulty_selected | difficulty |

Content Events

| Function | Event | Key Params | |----------|-------|-----------| | trackLessonStarted | lesson_started | lesson_id, lesson_title, category | | trackLessonCompleted | lesson_completed | lesson_id, lesson_title, category | | trackArticleRead | article_read | article_id, title, scroll_pct | | trackPaperRead | paper_read | article_id, title | | trackVideoPlayed | video_played | video_id, title, duration_sec |

Community Events

| Function | Event | Key Params | |----------|-------|-----------| | trackGroupJoined | group_joined | group_id, group_name | | trackThreadPosted | thread_posted | thread_id, forum | | trackCommentAdded | comment_added | thread_id | | trackReactionAdded | reaction_added | reaction, target_type |

Commerce Events

| Function | Event | Key Params | |----------|-------|-----------| | trackProductViewed | product_viewed | product_id, product_name, price | | trackAddToCart | add_to_cart | product_id, quantity | | trackCheckoutStarted | checkout_started | value, item_count |

Engagement Events

| Function | Event | Key Params | |----------|-------|-----------| | trackCTAClicked | cta_clicked | name, position, page, destination | | trackScrollDepth | scroll_depth | scroll_pct (25/50/75/90/100) | | trackTimeOnPage | time_on_page | duration_sec | | trackFullscreenToggled | fullscreen_toggled | entered | | trackSoundToggled | sound_toggled | muted | | trackUserPreferenceChanged | user_preference_changed | preference_key, preference_value |

Conversion Events

| Function | Event | Key Params | |----------|-------|-----------| | trackSignup | signup | method | | trackEmailCaptured | email_captured | list_name, page | | trackFirstBet | first_bet | variant, amount | | trackCertificationAchieved | certification_achieved | cert_id, cert_name, score | | trackReferralSent | referral_sent | method |

Consent Banner Theming

The banner uses CSS custom properties. Set them on your <body>:

<body style={{
  '--consent-bg': '#111111',           // Banner/modal background
  '--consent-text': '#f5f5f5',         // Text color
  '--consent-accent': '#c9a961',       // Gold/brand accent (buttons, links)
  '--consent-border': 'rgba(201,169,97,0.35)', // Border color
} as React.CSSProperties}>

Privacy

  • GA4 IP anonymisation is enabled by default (GA4 property setting) and asserted via anonymize_ip: true in the config
  • DNT (navigator.doNotTrack === '1') → auto-deny, no banner
  • GPC (navigator.globalPrivacyControl === true) → auto-deny, no banner
  • Cloudflare Web Analytics is cookieless and collects no personal data

Scaling to 20+ sites

Each site needs:

  1. @pokerzeno/analytics in devDependencies (file: protocol locally)
  2. AnalyticsProvider + ConsentBanner in root layout
  3. NEXT_PUBLIC_GA4_MEASUREMENT_ID env var
  4. "Manage cookies" button in footer calling useConsent().openBanner()
  5. Track key events for its domain (game, content, CTA, conversions)

Use separate GA4 properties per site so data is clean per property. A single GA4 account ("Prakash Ventures") can hold all properties.

In GA4, use exploration reports filtered by app_name to see cross-site behaviour in one view.