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

@dloizides/marketing-astro-kit

v1.2.1

Published

Reusable, themeable Astro marketing sections for product sites — a Free-vs-Pro pricing comparison table, a trust / credibility section (security cards, customer-logo + testimonial placeholders, discreet built-by), a client-side "try it" demo shell, and an

Downloads

459

Readme

@dloizides/marketing-astro-kit

Reusable, themeable Astro marketing sections for product sites — the credibility + conversion markers an executive scans for before they trust a SaaS:

| Component | What it is | |---|---| | PricingComparison.astro | A clean Free-vs-Pro(-vs-Business…) comparison table — tier columns with price + CTA header, grouped feature rows (check / cross / short value), an optional footnote and an optional one-time "callout" card (e.g. a concierge/service tier). | | TrustSection.astro | A trust / credibility block — security & compliance cards with inline icons, a customer-logo row (real + clearly-labeled placeholders), testimonial cards (clearly-labeled placeholders), an optional founding-partner panel, an honest founder line, a sample-deliverable showcase, and a discreet built-by line. | | PrimaryCta.astro | A themeable primary call-to-action (link OR spam-safe contact) with an optional secondary CTA. Pair with buildPrimaryCta so ONE SIGNUP_URL constant drives whether it's a real signup link or lead-capture. | | DemoFrame.astro | A themeable, client-side "try it" demo shell for static sites (no backend): eyebrow/title/intro, a row of clickable example chips, a per-example result panel (bespoke product body via a named result-<id> slot), a "sample data" disclaimer, and a primary CTA. Tiny inline vanilla JS toggles which result shows; first example visible without JS. | | GuideCard.astro | A themeable card for a guides / SEO content-hub listing — one guide's tags, title, description and date. Pair with guideFrontmatterSchema on an Astro content collection. |

Config-driven primary CTA (PrimaryCta + buildPrimaryCta)

Instead of hand-coding "is this a link to the app or a lead-capture mailto?" per page (with "flip to APP_URL at GA" TODO comments), hold ONE SIGNUP_URL constant and let the CTA switch itself:

// src/data/site.ts
export const SIGNUP_URL: string | null = null; // null → app not public yet → lead-capture
// src/data/marketing.ts
import { buildPrimaryCta } from '@marketing-kit/schema';
import { SIGNUP_URL } from './site';

export const primaryCta = buildPrimaryCta({
  signupUrl: SIGNUP_URL,          // set → real link ("Start free"); null → contact
  linkLabel: 'Start free',
  contactLabel: 'Request early access',
  contact: { local: 'hello', domain: 'example.com' },
  analyticsEvent: 'cta_primary',
  analyticsData: { location: 'hero' },
});
---
import PrimaryCta from '@marketing-kit/PrimaryCta.astro';
import { primaryCta, secondaryCta } from '../data/marketing';
---
<PrimaryCta cta={primaryCta} secondary={secondaryCta} />              <!-- on light -->
<PrimaryCta cta={primaryCta} secondary={secondaryCta} tone="dark" align="center" />  <!-- on a dark band -->

Flip SIGNUP_URL from null to a real URL (e.g. at GA) and every primary CTA becomes a real "Start free" link — no template change. Contact CTAs still rely on the host page's [data-contact] reveal handler.

Founding-partner / founder / sample-deliverable (TrustSection extras)

All three TrustSectionData fields are optional and render behind guards — omitting them = the previous behaviour. The sample-deliverable accepts an image, OR a product can inject bespoke mock markup via a named slot while the eyebrow/title/caption chrome stays shared:

<TrustSection data={trust} id="trust">
  <Fragment slot="sample-deliverable">
    <!-- your bespoke mock report / screenshot markup -->
  </Fragment>
</TrustSection>

Both are data-driven (one typed object per section, see src/schema.ts) and themed entirely through the host site's CSS custom properties — drop them into any Astro marketing site and they inherit its palette automatically. No images, no runtime JS beyond the host's existing scroll-reveal + contact-reveal hooks → Lighthouse-light.

Extracted from kefi-marketing as the 2nd genuine use (per the "extract on the second use" rule); the Astro marketing sites otherwise share no code.

Client-side "try it" demo (DemoFrame)

Static marketing sites (nginx, no backend) can still offer an interactive demo — DemoFrame renders the shared chrome around curated, clearly-labelled sample data that runs entirely in the browser. The frame owns the eyebrow/title/intro, the clickable example chips, a "sample data" disclaimer and the primary CTA; each product injects its own bespoke result body per example through a named slot (result-<example.id>), because that markup mirrors the product's real output:

// src/data/marketing.ts
import type { DemoFrameData } from '@marketing-kit/schema';
export const demo: DemoFrameData = {
  eyebrow: 'See it in action',
  title: 'Screen a wallet — watch the verdict',
  examples: [
    { id: 'flagged', label: '0x…illustrative', sublabel: 'Sanctioned' },
    { id: 'clean',   label: 'Clean wallet',     sublabel: 'No matches' },
  ],
  disclaimer: 'Sample results on example wallets. Create a free account to screen any address.',
  cta: primaryCta, // a MarketingCta driving to signup
};
---
import DemoFrame from '@marketing-kit/DemoFrame.astro';
import { demo } from '../data/marketing';
---
<DemoFrame data={demo} id="demo">
  <!-- One panel per example, keyed by data-demo-result (matching an example id).
       Mark the FIRST panel data-active so it shows without JS. -->
  <div data-demo-result="flagged" data-active
       id="demo-panel-flagged" role="tabpanel" aria-labelledby="demo-chip-flagged">
    <!-- bespoke high-risk result mock -->
  </div>
  <div data-demo-result="clean"
       id="demo-panel-clean" role="tabpanel" aria-labelledby="demo-chip-clean">
    <!-- bespoke low-risk result mock -->
  </div>
</DemoFrame>

Astro forbids a dynamic slot[name], so panels are routed through the default slot and keyed by a data-demo-result attribute (matching an example id) rather than named slots. The frame's chip ids are <id>-chip-<exampleId> and it wires aria-controls to <id>-panel-<exampleId>, so give each host panel id="<id>-panel-<exampleId>" + aria-labelledby="<id>-chip-<exampleId>". The first panel (data-active) is visible without JS — the inline script only moves the active panel on chip click (and arrow-key roving for the tablist). Keep the result copy honest: real, public example data or an obviously-illustrative placeholder, never a fabricated claim.

SEO guides / content hub (GuideCard + guideFrontmatterSchema)

A shared pattern for a product's guides flywheel: the kit supplies the Zod frontmatter schema (plug into an Astro content collection) and the card for the hub; the routes, article layout and JSON-LD stay product-owned.

// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { guideFrontmatterSchema } from '@marketing-kit/schema';

const guides = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/guides' }),
  schema: guideFrontmatterSchema,
});
export const collections = { guides };
---
// src/pages/guides/index.astro
import { getCollection } from 'astro:content';
import GuideCard from '@marketing-kit/GuideCard.astro';
const guides = (await getCollection('guides', ({ data }) => !data.draft))
  .sort((a, b) => +b.data.publishDate - +a.data.publishDate);
---
{guides.map((g) => <GuideCard data={{ ...g.data, href: `/guides/${g.id}/` }} />)}

guideFrontmatterSchema fields: title, description, slug?, publishDate, updatedDate?, tags, ogImage?, canonical?, draft?. It is built on astro/zod, so it is the same zod instance Astro's content layer uses.

Theming contract

The components read these CSS vars off the host :root (each has a sensible fallback, so a site missing a token still renders):

--brand, --brand-deep, --accent, --ink, --paper, --bg, --bg-tint, --muted, --hairline, --radius-md, --radius-lg, --shadow, plus the Oswald (headings) and Inter (body) font families. This is the same token set kefi-marketing, kefi-landings and the scaffold-landing output already define.

Reveal-on-scroll: the components tag elements with data-reveal (matching the kefi convention). If the host has a data-reveal observer, the sections animate in; if not, they're just visible. Spam-safe contact CTAs (kind: 'contact') render data-contact/data-local/data-domain and rely on the host page's existing [data-contact] mailto-reveal handler.

Adopt in an Astro marketing site (katalogos, erevna, …)

Because these sites aren't a formal npm workspace, wire the kit in with a Vite alias (single-source, no publish needed). Once the package is published to npm you can instead npm i @dloizides/marketing-astro-kit and import by its real name — the import specifiers are identical.

1. Alias the kit in astro.config.mjs:

import { fileURLToPath } from 'node:url';

export default defineConfig({
  // …
  vite: {
    resolve: {
      alias: {
        '@marketing-kit': fileURLToPath(
          new URL('../NpmPackages/packages/marketing-astro-kit/src', import.meta.url),
        ),
      },
    },
  },
});

2. Add your product's data in src/data/marketing.ts (copy the shape from kefi-marketing/src/data/marketing.ts):

import type { PricingComparisonData, TrustSectionData } from '@marketing-kit/schema';

export const pricing: PricingComparisonData = { /* your real tiers + gates */ };
export const trust: TrustSectionData = { /* your compliance + placeholders */ };

Use your product's actual plan gates — don't invent features. Cells are true/false (check/cross) or a short string ("Unlimited", "€0").

3. Render in your page, replacing any hand-rolled pricing/trust markup:

---
import PricingComparison from '@marketing-kit/PricingComparison.astro';
import TrustSection from '@marketing-kit/TrustSection.astro';
import { pricing, trust } from '../data/marketing';
---
<TrustSection data={trust} id="trust" />
<PricingComparison data={pricing} id="pricing" />

4. Ensure the theming contract — confirm your site's :root defines the tokens above (or accept the fallbacks), and that the page has a [data-contact] handler if you use a contact CTA.

That's the whole adoption: alias + one data file + two imports. No copy-paste of the markup or styles.

TypeScript editor support (optional)

For in-editor types, mirror the alias in tsconfig.json:

{ "compilerOptions": { "paths": { "@marketing-kit/*": ["../NpmPackages/packages/marketing-astro-kit/src/*"] } } }

The Astro build (Rollup) only needs the Vite alias — types are not required for astro build to succeed.