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

daily-soup-widget

v0.3.0

Published

Embeddable daily quote widget — React-based, growth-themed, deterministic, copyright-safe. Drop a script tag or npm install.

Readme

Daily Soup Widget

Embeddable daily-quote widget. Drop a <script> tag on any website, or npm install it as a React component. Same deterministic quote everywhere, every day — growth-themed, copyright-safe, zero config.

  • Live demo: https://daily-soup-widget.vercel.app
  • Source: https://github.com/mshmwr/daily-soup-widget
  • License: MIT

Why this exists

A widget shaped like an answer to three questions at once:

  1. Technical practice — Shadow DOM, UMD bundle, container queries, dual NPM/CDN distribution.
  2. Portfolio piece — a shipped end-to-end embeddable tool with a hosted live demo.
  3. Future product — v1 is intentionally small so v2 (analytics, personalisation, monetisation) has room.

The content theme is 人生成長 (personal growth). 30 zh seed quotes ship in v0.2; one per day, deterministically scheduled by UTC+8 date. Past dates never change once published. v0.2 is zh-only — the earlier en pool was removed in favour of a larger zh repertoire.


Install — script tag (any website)

<div id="daily-soup"></div>
<script src="https://daily-soup-widget.vercel.app/embed.js" async></script>

Optional config via data-* attributes:

<div data-daily-soup data-theme="dark" data-max-width="640px"></div>

Multiple instances on one page are supported — the bundle auto-mounts every [data-daily-soup] and #daily-soup node it finds.

Install — NPM (React / Next.js)

npm install daily-soup-widget
import { DailySoup } from 'daily-soup-widget';

export default function Page() {
  return <DailySoup lang="zh" theme="auto" />;
}

The component is SSR-safe: it emits a placeholder during render and hydrates the widget inside useEffect. No window access at module top-level.

Install — imperative API (NPM)

import { mount } from 'daily-soup-widget';

const host = document.querySelector('#my-mount-point') as HTMLElement;
const handle = mount(host, { lang: 'zh', theme: 'auto' });
// later: handle.destroy();

Same React peer requirement as the <DailySoup> component — install React + ReactDOM (>=18) alongside.


Configuration

| Option | Values | Default | Description | | ------------- | ------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------- | | lang | 'zh' | 'zh' | Content language. zh-only since v0.2. | | theme | 'auto' / 'light' / 'dark' / color object | 'auto' | auto follows host's prefers-color-scheme and reacts to changes. Pass an object to override colors — see below. | | scheduleUrl | any HTTPS URL or '' (same-origin) | https://daily-soup-widget.vercel.app | Override the CDN that serves /schedule-<lang>.json. | | maxWidth | any valid CSS width (e.g. '100%', '48rem') | '32rem' | Cap on the card's rendered width. Set to '100%' to fill the embed container. |

Theme as object

For a quick palette tweak without a full restyle, pass an object instead of a string:

<DailySoup
  theme={{ base: 'light', bg: '#fff8ee', border: 'transparent' }}
  maxWidth="100%"
/>

Recognised keys: base ('light' | 'dark', controls default vars before overrides), bg, ink, muted, border, accent. Any omitted key falls back to the base palette. Object themes do not follow system preference — they're explicit.

CDN attribute support is string-only — use data-theme="auto|light|dark" and data-max-width="...". The object form requires the NPM API.

Container queries still apply for layout. Three breakpoints: <320px (icon-only share row), 320–500px (standard), 500–700px (larger quote), >700px (comfortable spacing). The card never exceeds maxWidth.


How "today" is decided

schedule-<lang>.json ships as a static JSON file mapping calendar date → quote ID:

{
  "launchDate": "2026-05-15",
  "entries": {
    "2026-05-15": "0001",
    "2026-05-16": "0003"
  },
  "quotes": { "0001": { "text": "...", "author": "..." } }
}

The client fetches one JSON, computes today's UTC+8 date, looks up the entry, and renders. No second request. No cold start. No personalisation. Past dates carry permanent content even if the quote pool reshuffles.

A GitHub Actions cron (monthly, day 1 at 16:00 UTC) regenerates the JSON to extend the rolling 90-day window and redeploys. New quotes appended to the pool extend the rotation tail; existing schedule slots stay untouched.


What's in the box

  • 30 zh seed quotes spanning five growth dimensions: 行動 / 學習 / 堅持 / 心態 / 蛻變.
  • All classical Chinese — 蘇軾、屈原、論語、孟子、莊子、荀子、王勃、杜甫、陸游、朱熹 等.
  • Copyright posture: all public-domain (Taiwan 50-year rule). Each quote carries author + source attribution with a 出處 link.

Browse content/quotes/*.md to see the seed. Adding a quote = one PR per markdown file.


Repository layout

content/quotes/         hand-curated markdown quotes (the source of truth)
scripts/
  build-schedule.ts     content/*.md → dist/schedule-zh.json
  build-bundle.ts       esbuild UMD + ESM + CJS bundles
src/
  embed.ts              UMD entry (CDN, auto-mounts; React bundled in)
  index.ts              ESM entry (NPM)
  component.tsx         React wrapper around mount()
  widget.tsx            React widget core (Shadow DOM root, render, fetch)
  theme.ts              auto/light/dark resolution + system-theme listener
  i18n.ts               UI strings (zh)
  share.ts              copy / X / LINE share builders
  styles.ts             widget CSS (injected into Shadow DOM)
app/                    Next.js landing page (live demo + install snippets)
tests/
  unit/                 Vitest — build-schedule, theme, i18n, date
  e2e/                  Playwright — landing page smoke

Local development

npm install
npm run build:schedule     # content/quotes/*.md → dist/schedule-*.json
npm run build:bundle       # esbuild UMD + ESM + CJS into dist/ + public/
npm run dev                # next dev — landing page at http://localhost:3000
npm test                   # vitest
npm run test:e2e           # playwright (requires running dev server)
npm run typecheck          # tsc --noEmit
npm run build              # full prod build (schedule + bundle + types + Next.js)

Adding a quote

  1. Create content/quotes/<id>-<slug>.md with frontmatter (see existing files for the schema).
  2. npm run build:schedule — extends the schedule, preserving past dates.
  3. Commit + PR. Merge triggers the GH Action to redeploy.

Releasing a new npm version

  1. Bump version in package.json, commit, merge to main.
  2. Tag and push: git tag v0.2.3 && git push --tags.
  3. .github/workflows/publish.yml runs npm test + typecheck + build:lib + npm publish against the NPM_TOKEN repo secret. No local npm login ever needed.

The NPM_TOKEN must be a granular automation token scoped to read & write on daily-soup-widget.

Verifying the auth chain without a version bump: trigger gh workflow run publish.yml -R mshmwr/daily-soup-widget against the current main. The workflow runs all pre-publish steps and then attempts npm publish on the already-published version, which the registry rejects with E403 You cannot publish over the previously published versions: <version>. Reaching that exact error confirms the NPM_TOKEN secret is present and has publish scope — only the uniqueness check rejected. Use this whenever you rotate the token or change the workflow, instead of minting a throwaway version. Note: npm whoami on a granular automation token returns 401 and is not a valid auth-chain test; use npm publish --dry-run (locally) or this workflow_dispatch trick (in CI) instead.

Auditing source URLs

npm run check:source-urls HEADs every sourceUrl in content/quotes/** and flags pages whose body matches 資料已刪除|404 Not Found. Run before each release; null any broken URLs in frontmatter (the widget renders source as plain text when sourceUrl is empty).


Browser support

  • Modern evergreens (Chrome / Safari / Firefox / Edge), 2022+.
  • Container queries: Chrome 105+ / Safari 16+ / Firefox 110+. The widget renders without RWD on older browsers (fixed-size card).
  • Shadow DOM fallback: light-DOM render with a console warning.

License

MIT © 2026 mshmwr. See LICENSE.

Quote sources are public-domain or PD-in-Taiwan; see frontmatter on each quote file for attribution.