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

@dreamtree-org/landing-kit

v0.7.0

Published

The shared marketing site and auth frame (AuthShell + brands) for every Dreamtree product: content-driven landing pages, login/signup shell, Express routes, prerender and SEO files.

Downloads

700

Readme

@dreamtree-org/landing-kit

The shared marketing landing pages and authentication frame (<AuthShell>, brands, getBrand) for every Dreamtree product: replaces the landing setup copied across billpro, taskpro and venturepro (page chrome, sections, "Book a demo" dialog, SEO files, Express routes and prerender) and the login/signup/reset/platform-admin frame in one package.

  • Two facades, both taking the product's brand from its own repo (dreamtree.brand.json, twreact-ui's brand format) through toLandingBrand():
    • <LandingPage brand={brand} content={content} /> — a product's landing page is a content file.
    • <AuthShell brand={brand} title="Welcome back">…</AuthShell> (also available at @dreamtree-org/landing-kit/auth + @dreamtree-org/landing-kit/auth.css).
    • The built-in brands / getBrand(id) registry is deprecated (warns once, removed in 0.5.0).
  • Plain CSS (--dt-* custom properties). It looks the same under Tailwind 3, Tailwind 4 (preflight or not) or no Tailwind. Dark mode via [data-theme="dark"] or .dark.
  • Built for prerendered, un-hydrated pages (fast and crawlable): enhanceLanding() wires the menu and the demo button in a few hundred bytes. It also works hydrated in an SPA or Next.js ('use client', onBookDemo, linkComponent).
  • Responsive from 320px up. The nav folds into a menu below 1024px. Checked with Playwright at 320/375/768/1024/1440, light and dark.

60-second start

npm install @dreamtree-org/landing-kit
// Landing.jsx
import { LandingPage, toLandingBrand } from '@dreamtree-org/landing-kit';
import brandFile from '../dreamtree.brand.json'; // the product's brand, kept in its repo
import '@dreamtree-org/landing-kit/styles.css';
import { content } from './content';

export default function Landing() {
  return <LandingPage brand={toLandingBrand(brandFile, { headline: 'Billing, inventory and GST — done right.' })} content={content} />;
}
// content.js: plain data (icons are any component, e.g. lucide-react)
import { FileText, Barcode } from 'lucide-react';

export const content = {
  nav: [{ label: 'Billing', href: '#billing' }, { label: 'FAQ', href: '#faq' }],
  hero: {
    eyebrow: 'GST billing software · Inventory',
    title: ['GST Billing & Inventory Software', 'for Indian Businesses'], // 2nd half is underlined
    lead: 'Create GST invoices, manage stock and prepare GST summaries in one app.',
    chips: ['Retail shops', 'Wholesalers'],
    secondary: { label: 'See it in action', href: '#product' },
  },
  sections: [
    { id: 'billing', eyebrow: 'Billing', title: 'GST Billing Software', body: '…',
      items: [{ title: 'GST tax invoices', body: '…', icon: FileText }, { title: 'Barcode billing', body: '…', icon: Barcode }],
      cta: { label: 'Book a billing demo', demo: true } },
    { id: 'gst', variant: 'highlight', title: 'GSTR-1 & GSTR-3B reports', note: 'Summaries, not portal filing.', items: ['GSTR-1', 'GSTR-3B'] },
    { use: 'audiences' },                       // place a tail block here instead of at the end
  ],
  audiences: { id: 'industries', title: 'Who it is for', items: [{ id: 'retail', title: 'Retail shops', body: '…', href: '/solutions/retail' }] },
  steps: { title: 'How it works', steps: [{ name: 'Set up', text: '…' }, { name: 'Bill', text: '…' }, { name: 'Review', text: '…' }] },
  faq: { title: 'Frequently asked questions', items: [{ q: 'What is BillPro?', a: '…' }] },
  cta: { title: 'See BillPro with your business', body: '…', chips: ['Retail', 'Wholesale'] },
  footer: { blurb: '…', tagline: 'Bill fast.', columns: [{ title: 'Product', links: [{ label: 'Log in', href: '/login', reload: true }] }] },
};

Then prerender it and serve it (Express recipe below), or render it in your SPA / Next page.

Reference

toLandingBrand(file, copy?)

Maps the product's dreamtree.brand.json (create it with npx @dreamtree-org/twreact-ui brand init) to a Brand, so the landing and sign-in pages share the app's colours:

| Brand field | From the brand file | |---|---| | id, name | id, name | | primary | marketing.primary, else color.primary.600 | | accent | marketing.accent, else color.primary.300 | | panel | marketing.panel, else role.shell | | logoSrc | logo.mark |

copy is the product's content (headline, pitch, points, category, platformHeadline, platformPitch). It stays in product code, not the brand file.

<LandingPage>

| Prop | Type | Default | Meaning | |---|---|---|---| | brand | Brand | required | getBrand(id) (auth-kit's brand objects) or your own { id, name, primary, accent?, logoSrc? } | | content | LandingContent | required | { nav, login, hero, sections[], audiences, steps, faq, cta, footer }; only hero is required | | preview | node | | e.g. <PreviewFrame>…your app screen in HTML…</PreviewFrame>, which rides over the hero | | slots | { [name]: node } | | a product's own sections, placed by { use: '<name>' } in content.sections | | linkComponent | component | <a> | react-router Link / next/link for in-app paths; gets to and href | | onBookDemo | (button) => void | | when hydrated; un-hydrated pages use enhanceLanding | | home | boolean | true | false on sub-pages: #faq links become /#faq |

content.sections[] render in order after the hero. Each one is FeatureSection props, or { use: 'audiences' | 'steps' | 'faq' | 'cta' | 'preview' | <slot name> }, or a React element. audiences, steps, faq and cta that are not placed with use follow the sections in that order. login: null hides "Log in".

Blocks (all exported, all usable on their own)

| Block | Purpose | |---|---| | MarketingLayout | skip link + sticky Header + <main> + DreamtreeFooter; sets brand colours and context. Use it for sub-pages | | Header | brand lockup, nav, "Log in", "Book a demo", phone menu (data-menu-toggle, #mobile-nav) | | Hero / PageHero + Breadcrumbs | the page's one h1 (#hero-title); PageHero adds breadcrumbs and #answer for sub-pages | | PreviewFrame | window chrome + "Demo data" badge + hidden heading/caption around your product screen | | FeatureSection | variant: cards (default) · split · list · intro · highlight (dark band + warning note) · band · compare · links · detail; tone: plain · tinted · muted · dark | | Audiences | who-it-is-for cards with anchors and links | | HowItWorks | numbered steps (feed the same steps to the HowTo JSON-LD) | | Faq | native <details>, every answer in the HTML | | DemoCta | the closing brand panel | | DreamtreeFooter | Dreamtree-Org mark, blurb, link columns, copyright bar | | DemoDialog | native <dialog> demo form: your fields, honeypot, consent + privacy line, onSubmit(data) | | createDemoIsland(props) | mounts DemoDialog on demand for un-hydrated pages; returns { openDemo } | | BookDemoButton, BrandLockup, Section, SectionIntro, TextLink, SmartLink | building blocks |

Tenant pages (0.6.0): a store's own landing page as data

For multi-tenant apps where each customer edits their own page (a store front, a clinic, a franchise). The page is a JSON document; the app stores it, the package validates and renders it.

// Server (CommonJS is fine): validate before storing
const { validatePage, validateTheme, sanitizeRichText } = require('@dreamtree-org/landing-kit/schema');
const page = validatePage(req.body.page);      // { ok, errors: [{ path, message }], page }
const theme = validateTheme(req.body.theme);   // { ok, errors, warnings, theme }
if (!page.ok || !theme.ok) return res.status(422).json({ errors: [...page.errors, ...theme.errors] });
// Browser: render
import { TenantPage } from '@dreamtree-org/landing-kit';
import '@dreamtree-org/landing-kit/styles.css';

<TenantPage
  page={doc} theme={theme} storeName="TechNext"
  slots={{ featured: <FeaturedProducts />, categories: (props) => <CategoryTiles title={props.title} /> }}
  resolveMedia={(ref, variant) => `/api/public-media/${ref.media_id}`}
  linkComponent={RouterLink}
  onSubscribe={({ email, consent }) => api.subscribe(email, consent)}
/>
  • Document: { version: 1, sections: [{ id, type, enabled, props }] }, at most 20 sections, any order, a type may repeat.
  • Section types (SECTION_TYPES, each a list of typed fields a builder can turn into a form): hero_slider, featured†, categories†, promo_banner, trust_badges, cta_cards, testimonials, faq, rich_text, usp_strip, brand_strip, newsletter, gallery. † rendered from the app's slots.
  • Safe by construction: links must be http(s), mailto:, tel:, /path or #fragment; icons are names (ICON_NAMES, drawn from the package's own SVGs); images are { media_id } refs the app resolves; rich text keeps an allowlist (sanitizeRichText) on save and on render.
  • Theme: { preset, primary, accent, font, radius, mode, logo, favicon }. themeVars(theme) returns the CSS variables for the page root plus palette.primary / palette.accent (50…950 from one colour, the colour itself at 600) to map onto the app's own tokens. validateTheme warns when button text or links would fall under WCAG contrast. Six THEME_PRESETS and six FONT_PAIRS (with the stylesheet to load).
  • HeroSlider: swipe, arrow keys, always-visible 44px controls, pause/play; autoplay stops on hover, focus and prefers-reduced-motion; the first slide's heading is the page's only h1.
  • newSection(type) / sectionDefaults(type) start a section with valid defaults; defaultPage({ name, tagline }) is a neutral starting page.

<AuthShell> & @dreamtree-org/landing-kit/auth

The shared auth frame for login, signup, password reset, consent and platform-admin sign-in (desktop [ brand panel 40% | form card ], mobile [ logo · form card · footer · legal links ]). Import from @dreamtree-org/landing-kit or the lightweight @dreamtree-org/landing-kit/auth subpath (with @dreamtree-org/landing-kit/auth.css or styles.css).

import { AuthShell, getBrand } from '@dreamtree-org/landing-kit/auth';
import '@dreamtree-org/landing-kit/auth.css';

export default function Login() {
  return (
    <AuthShell
      brand={getBrand('venturepro')}
      linkComponent={Link}
      title="Welcome back"
      subtitle="Sign in to manage your ventures and units."
      footer={<>New here? <Link to="/register">Register now</Link></>}
    >
      {/* your form */}
    </AuthShell>
  );
}

| Prop | Type | Default | Meaning | |---|---|---|---| | brand | Brand | required | from getBrand(id) (billpro, taskpro, venturepro, campaignpro, firepro, procurementpro, fitweavepro, aiagentbuilder, runloompro, appforge) or your own object | | title | node | required | card heading | | subtitle | node | | under the heading | | footer | node | | below the card (e.g. "Register now") | | variant | 'tenant' \| 'platform' | 'tenant' | 'platform' renders a darker panel + "Platform" badge | | legalLinks | {label, href}[] | Privacy, Terms, Data Policy | [] hides them | | linkComponent | component | <a> | receives both to and href | | demoAccounts | { load, onSelect, taps? } | | hidden demo-accounts panel (below) |

Hidden demo accounts (every product, same behaviour)

Tapping the card heading ("Sign in") 4 times quickly reveals a Demo accounts row of role chips under the form; a chip fills the form (it never submits). Credentials are never bundled: load asks the product's API, which answers 404 unless the deployment sets DEMO_ACCOUNTS=1, and a 404 renders nothing.

import { AuthShell, fetchDemoAccounts } from '@dreamtree-org/landing-kit/auth';

<AuthShell
  brand={brand}
  title="Sign in"
  demoAccounts={{
    load: fetchDemoAccounts, // GET /api/auth/demo-accounts (or fetchDemoAccounts('/other/url'))
    onSelect: (a) => { setEmail(a.identifier); setPassword(a.password); if (a.company) setCompany(a.company); },
  }}
>

Server (@dreamtree-org/landing-kit/demo-server, no dependencies):

const { demoAccountsExpress } = require('@dreamtree-org/landing-kit/demo-server');
app.get('/api/auth/demo-accounts', demoAccountsExpress({
  // read passwords from the same env vars the seed script uses, so they never drift
  accounts: (env) => [{ label: 'Admin', identifier: '[email protected]', password: env.SEED_PASSWORD || 'demo1234', company: 'demo' }],
}));

// Next.js: app/api/auth/demo-accounts/route.ts
export const GET = () => demoAccountsResponse({ accounts: [...] });

| Env | Meaning | |---|---| | DEMO_ACCOUNTS=1 | turn the endpoint on (off by default; leave it off in production) | | DEMO_ACCOUNTS_JSON | [{ label, identifier, password, company? }], replaces the built-in list (accounts a deployment created itself) |

The default demo company (every pro app)

Every pro app seeds a company with slug default — its branches and one user per role — so the whole product can be demoed end to end, and the chips list exactly those users:

const { demoUsers, demoAccountsExpress, requireDemoPassword } = require('@dreamtree-org/landing-kit/demo-server');
const manifest = [{ role: 'tenant-admin', label: 'Admin' }, { role: 'viewer', label: 'Viewer' }];

// demo:seed (idempotent): create `default`, upsert each user, password = requireDemoPassword()
// endpoint: one chip per role, emails demoEmail(product, role) → [email protected]
app.get('/api/auth/demo-accounts', demoAccountsExpress({ accounts: (env) => demoUsers(manifest, { product: 'taskpro', env }) }));

| Env | Meaning | |---|---| | DEMO_PASSWORD | the demo users' password (≥ 12 chars); demo:seed resets every demo user to it, the chips show it. Unset → no chips |

Company-first logins pick the chip on the company step: call stashDemoFill(account) there and takeDemoFill() when the password step mounts (sessionStorage, read once). Outside AuthShell, render <DemoAccounts load onSelect /> behind useTapReveal() yourself.

@dreamtree-org/landing-kit/enhance

enhanceLanding(root = document, { openDemo | loadDemo, warmDemo, hash = '#book-demo' }): wires [data-menu-toggle], [data-scroll-to] and [data-book-demo] on a prerendered page. Returns unwire().

@dreamtree-org/landing-kit/seo (pure: browser, Node, Next)

| Function | Returns | |---|---| | buildJsonLd({ site, product, org, page, faq, howTo, breadcrumbs, parts, keywords, speakable, lastmod }) | schema.org graph: Organization, WebSite, SoftwareApplication, WebPage, BreadcrumbList, FAQPage, HowTo | | contentFacts(content) | { faq, howTo, features, sections } from a LandingPage content object, so page and JSON-LD cannot drift | | buildHead({ site, page, product, jsonLd }) | <title>, description, canonical, OG/Twitter, llms alternate, JSON-LD script | | buildRobots({ privatePaths, crawlers }) · buildSitemap({ pages, lastmod }) · buildLlmsTxt(…) · buildLlmsFullTxt(…) | crawler files | | jsonLdScript(graph) · pageUrl(path, site) · escapeHtml | helpers |

site defaults to '__SITE_URL__' (filled in at serve time). Pass a real origin when you have one, or null to emit a graph without URLs. No ratings, reviews or prices are ever generated; an Offer (with no price) only appears with product.offers.

@dreamtree-org/landing-kit/server (Express, CommonJS-safe, includes everything in /seo)

| Function | Meaning | |---|---| | landingRoutes(app, { dist, isPublicHost, hasSession, redirects, onSession, onPrivateHost, trailingSlash, rawFiles, manifest, cacheControl, transformPage }) | serves / (visitors only, Vary: Cookie), every page in the manifest (default landing-pages.json; its paths are relative to its own directory), robots/sitemap/llms with the origin filled in, verification meta tags from env; 404s the raw files (rawFiles: 'redirect' 301s them to their page instead); /x/ → 301 /x (trailingSlash: 'serve' answers both); isPublicHost may be async; a non-public host gets Disallow: / and, at /, next() or the app shell (onPrivateHost: 'app-shell'); transformPage(body, { req, route, file, kind }) may rewrite a page or crawler file per request (e.g. live prices) and falls back to the prerendered body on null or a throw | | fillPage(template, page) | one page poured into the template, as a string: what prerenderPages writes. Pair it with transformPage to re-render a page with live data | | prerenderPages({ dist, pages, files, homeFile, pagesDir, manifest }) | build step: fills %TITLE% %DESCRIPTION% %PATH% %JSONLD% %HEAD% <!--landing--> per page, writes landing.html, landing-pages/…, the manifest and crawler files; throws on an unfilled slot before writing anything | | sessionCheck({ cookie: 'JWT_TOKEN', bearer, verify }) | hasSession(req) from your own JWT check | | siteUrl(req) · verificationTags(env) · readCookie · fillSite | helpers |

CSS variables

Set by brand: --dt-brand, --dt-brand-accent, --dt-brand-panel. Override on .dt-landing: --dt-lp-bg, --dt-lp-surface, --dt-lp-muted-bg, --dt-lp-border, --dt-lp-text, --dt-lp-body, --dt-lp-muted, --dt-lp-radius, --dt-lp-max, --dt-lp-font, --dt-lp-header-h.

Product extension points (0.1.1)

Keep a product's own pictures and sections without forking markup:

| Where | Prop | What it takes | |---|---|---| | FeatureSection split · band · highlight | media, reverse | your node (preview, figure, illustration) on the visual side; items become an icon row under the text; reverse puts the visual first | | FeatureSection | cta | one CtaSpec or an array (side by side) | | FeatureSection split / detail | bullets | a checklist under the text | | FeatureSection intro | cta, glanceNote | a CTA under the text; a note inside the "at a glance" card | | card items | label | a small kicker above the title ("01 · Visibility") | | Hero | backdrop | a decorative node behind the copy (aria-hidden) | | Audiences items | media, features | an illustration beside the heading; a short icon list | | HowItWorks | cta | a CTA under the steps | | DemoCta | note, link | a "what to tell us" box; a quiet link (e.g. Log in) | | DemoDialog | validate | (form) => ({ field: message }), inline errors before submit; an Error thrown by onSubmit with fields shows them too | | landingRoutes | isPublicHost async, onPrivateHost, trailingSlash, rawFiles | see /server above |

Recipes

Express + Vite, prerendered and un-hydrated (billpro / taskpro / venturepro).

// client/src/landing/main.jsx (landing.html's script)
import '@dreamtree-org/landing-kit/styles.css';
import { enhanceLanding } from '@dreamtree-org/landing-kit/enhance';
enhanceLanding(document, { loadDemo: () => import('./demoIsland.jsx') });

// client/src/landing/demoIsland.jsx
import { createDemoIsland, getBrand } from '@dreamtree-org/landing-kit';
export const { openDemo } = createDemoIsland({ brand: getBrand('billpro'), fields, onSubmit: postDemoRequest });

// client/scripts/prerender-landing.mjs, after vite builds landing.html + the SSR entry
const { prerenderPages } = await import('@dreamtree-org/landing-kit/server');
prerenderPages({ dist, pages: mod.renderPages(today), files: { 'robots.txt': robots, 'sitemap.xml': map, 'llms.txt': llms } });

// server.js (before express.static)
const { landingRoutes, sessionCheck } = require('@dreamtree-org/landing-kit/server');
landingRoutes(app, {
  dist: path.join(__dirname, 'client/dist'),
  isPublicHost: (req) => req.context?.domainKind === 'shared' && req.context?.domainCompanyId == null,
  hasSession: sessionCheck({ verify: (t) => tokenService.verify(t).success === true }),
  redirects: { '/solutions': '/#solutions' },
});

SPA, hydrated. <LandingPage brand={brand} content={content} linkComponent={Link} onBookDemo={() => setOpen(true)} /> and render <DemoDialog open={open} onClose={…} onSubmit={…} brand={brand} />.

Next.js (App Router). The components are 'use client'. For SEO, build in server code: <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: jsonLdScript(buildJsonLd({ site: origin, … })) }} />, and in app/robots.ts / app/sitemap.ts use buildRobots / buildSitemap with site: origin, or return Next's metadata objects from the same data.

What it deliberately does NOT do

  • No product copy, prices, ratings or customer counts. Every word comes from your content file.
  • No product screenshots. PreviewFrame frames the HTML preview you write (and may style with Tailwind).
  • No HTTP for the demo form. onSubmit is yours (korm submitDemoRequest, fetch, anything). It does not store consent either.
  • No Vite/Next build plugin. You keep your own landing.html, SSR entry and build; the package fills and writes the files.
  • No icon library. Pass lucide (or any) components.

License

MIT