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

@damian-buho/support-ukraine

v2.0.1

Published

Browser library that adds a Ukraine-support charity banner to any website.

Readme

@damian-buho/support-ukraine

StandWithUkraine NPM Version npm package minimized gzipped size (scoped) NPM Downloads NPM License PRs Welcome Libraries.io dependency status for GitHub repo

Pipeline CodeQL Known Vulnerabilities REUSE status

  • Browser library that adds a Ukraine-support charity banner to any website.
  • Shows randomly selected Ukrainian charity localized to the visitor's language and respects RTL scripts.
  • You can choose kinds of charities.
  • Inspired by hejny/Ukraine.

Screenshots

Demo / Play with settings

I18N support

Dark theme support

RTL Support

Install

npm

npm install @damian-buho/support-ukraine

CDN (no build step)

<script type="module">
  import { supportUkraineBlock } from 'https://cdn.jsdelivr.net/npm/@damian-buho/support-ukraine@2/+esm'

  await supportUkraineBlock()
</script>

For optimal Core Web Vitals, avoid the chained locale fetch by loading a pre-localized build:

<script type="module">
  import { supportUkraineBlock } from 'https://cdn.jsdelivr.net/npm/@damian-buho/support-ukraine@2/dist/es.js'

  await supportUkraineBlock()
</script>

Usage

import { supportUkraineBlock } from '@damian-buho/support-ukraine'

// Auto-detect locale from navigator.language
await supportUkraineBlock()

// Force a specific locale
await supportUkraineBlock({ locale: 'es' })

For optimal Core Web Vitals, let your server or router decide the language and load only the needed build — no second network request:

import { supportUkraineBlock } from '@damian-buho/support-ukraine/es'

await supportUkraineBlock()

Available localized entry points: ar, de, en, es, fr, hi, it, ja, ko, nl, pl, pt, sv, th, uk, zh. The locale option is ignored in these builds — they are already localized.

If you still want to read navigator.language yourself and avoid the library’s chained fetch, detect the base language tag and load the matching bundle:

<script type="module">
  const base = navigator.language.split('-')[0]
  const supported = [
    'ar',
    'de',
    'en',
    'es',
    'fr',
    'hi',
    'it',
    'ja',
    'ko',
    'nl',
    'pl',
    'pt',
    'sv',
    'th',
    'uk',
    'zh'
  ]
  const locale = supported.includes(base) ? base : 'en'

  const { supportUkraineBlock } = await import(
    `https://cdn.jsdelivr.net/npm/@damian-buho/support-ukraine@2/dist/${locale}.js`
  )

  await supportUkraineBlock()
</script>

With a bundler, do not interpolate the locale into the import specifier — Vite/webpack cannot statically resolve a computed package subpath, so the build either fails or pulls in every locale, which defeats the point. Use a literal specifier per branch instead; each one still gets its own chunk:

const bundles = {
  es: () => import('@damian-buho/support-ukraine/es'),
  fr: () => import('@damian-buho/support-ukraine/fr'),
  // … one entry per supported locale
  en: () => import('@damian-buho/support-ukraine/en')
}

const base = navigator.language.split('-')[0]
const { supportUkraineBlock } = await (bundles[base] ?? bundles.en)()
await supportUkraineBlock()

In both cases only one network request is made — the pre-localized dist/<locale>.js — instead of index.js → locale chunk.

The banner is prepended to document.body by default. It displays a randomly selected charity with the format:

🇺🇦 Support Ukraine: Come Back Alive: Strengthening Ukraine's defense

The entire block is a clickable link to the charity's donation page.

Options

| Option | Type | Default | Description | | ------------- | ----------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------- | | element | HTMLElement | document.body | Target mount element for the banner | | mode | 'shift' \| 'overlap' \| 'replace' | 'shift' | 'shift' pushes page content down, 'overlap' floats on top, 'replace' swaps a same-class placeholder element | | fontSize | string | '87.5%' | Banner font size; % anchors to the widget's own 16px host box, ignoring document root scaling | | charities | Charity[] | (built-in) | Custom charity list; replaces the built-in database | | tags | CharityTag[] | (all) | Filter charities by category: 'military', 'humanitarian', 'animals' | | exclude | string[] | (none) | Exclude charities by id; unknown ids are ignored | | dontRepeat | boolean | true | Avoid repeating charities; seen ids stay in session memory by default | | persistSeen | boolean | false | Persist seen charity ids in localStorage across visits (needs visitor consent) | | isInConsole | boolean | true | Log the selected charity to the dev console | | showRefreshButton | boolean | false | Show a refresh button that loads the next random charity | | autoRefreshInterval | number | 0 | Auto-refresh interval in milliseconds; 0 disables automatic refresh | | showRefreshAnimation | boolean | false | Show a fade animation when the charity changes (respects prefers-reduced-motion) | | locale | string | (auto-detected) | Override the auto-detected BCP 47 language tag |

Filtering by category

Show only military charities:

await supportUkraineBlock({ tags: ['military'] })

Excluding charities

Hide specific charities by id (unknown ids are ignored):

await supportUkraineBlock({ exclude: ['united24'] })

Localized charity URLs

Charities carry per-language donation URLs via a urls map (base language code → URL, en required). The banner follows the visitor’s language and falls back to en:

await supportUkraineBlock({
  charities: [
    {
      id: 'hospitallers',
      name: 'Hospitallers',
      tagline: '…',
      urls: { en: 'https://www.hospitallers.org.uk', de: 'https://www.hospitallers.org.uk/de' },
      tags: ['humanitarian']
    }
  ]
})

Replace mode (no layout shift)

Use replace mode to swap a same-class placeholder element (any tag) so the banner takes its place without shifting page content:

<!-- Static HTML: renders nothing until JS runs. Mirror the host box (font-size 16px,
     min-height 2.5em = the banner's reserved height) so swap-in causes zero shift. -->
<section
  aria-label="Support Ukraine banner"
  class="support-ukraine-block"
  style="font-size:16px;min-height:2.5em"
></section>
await supportUkraineBlock({ mode: 'replace' })

The banner finds the first element with class support-ukraine-block inside the mount element and replaces it in place. If no placeholder is found, it falls back to prepending. This eliminates cumulative layout shift (CLS) because the placeholder already reserves the exact space the banner needs.

Disabling repeat prevention

Allow the same charity to appear on every page load:

await supportUkraineBlock({ dontRepeat: false })

Persisting seen charities

By default the repeat prevention above keeps seen charity ids in session memory only — nothing is written to the visitor’s device. Set persistSeen to remember them in localStorage across visits. Storing data on a visitor’s device needs their consent under the GDPR/ePrivacy rules, so only enable this after the site owner has obtained it:

await supportUkraineBlock({ persistSeen: true })

Locale support

The banner is translated to the visitor's language automatically. The following locales are supported:

| Language | Code | RTL | | ---------- | ---- | ------------------ | | Arabic | ar | Yes | | Chinese | zh | | | Dutch | nl | | | English | en | (default fallback) | | French | fr | | | German | de | | | Hindi | hi | | | Italian | it | | | Japanese | ja | | | Korean | ko | | | Polish | pl | | | Portuguese | pt | | | Spanish | es | | | Swedish | sv | | | Thai | th | | | Ukrainian | uk | |

RTL scripts are detected automatically and the banner direction is set accordingly.

For best performance, pick the locale yourself (from Accept-Language, <html lang>, or your i18n router) and import the matching entry point. The generic dist/index.js uses navigator.language and a dynamic import() for the locale chunk — that chained request delays the banner and hurts CWV. Each dist/<locale>.js bundles its messages statically, so it renders in a single fetch.

Architecture

src/
├── index.ts          # Auto-detect build (navigator.language + dynamic import)
├── banner.ts         # Shared banner rendering (mountBanner, DEFAULT_CHARITIES)
├── types.ts          # Charity, SupportUkraineBlockOptions, etc.
├── i18n.ts           # Locale detection, loading, merging (generic build only)
├── locales/          # Per-locale translation files
├── charities.yaml    # Built-in charity database
├── entries/          # Per-locale entry points (ar.ts, es.ts, …) — static import, no fetch
└── styles.scss       # Banner CSS (compiled by tsup)

dist/
├── index.js          # Generic auto-detect build + locale chunks (en-*.js, es-*.js …)
├── en.js, es.js …    # Pre-localized self-contained builds — one fetch, no chained request
└── index.d.ts        # Types (shared by all entry points)

Contributing

See CONTRIBUTING.md for development setup and guidelines.

License

MIT