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

@pagix/main-product-hero

v0.1.0

Published

Anvilkit Puck-native PLP main product hero — single product showcase with title, price, learn-more and buy buttons, plus a large product image below. Supports white/black background variants.

Downloads

33

Readme

@pagix/main-product-hero

Puck-native PLP main product hero — one product, centred title / price / two CTA pills, with a large media frame below. Single column by design; there is no layout variant.

Usage

import { createMainProductHeroConfig } from "@pagix/main-product-hero";

const config = createMainProductHeroConfig({ locale: "zh" });

| Export | Purpose | | --- | --- | | componentConfig | The Puck ComponentConfig (English default) | | mainProductHeroConfig | Convenience alias of componentConfig | | createMainProductHeroConfig(options?) | Locale-aware factory ({ locale?, messages? }) | | createComponentConfig(options?) | Alias of the factory | | defaultProps / fields / metadata | Package contract | | MainProductHero | The RSC-safe component | | colorField / imageUrlField / videoUrlField | Package-private Puck field factories |

Props

| Prop | Type | Notes | | --- | --- | --- | | title | string | Centred heading. 5.33vw on desktop, 24–32px on mobile | | price | number? | Omitted from the DOM when undefined | | currency | string? | Default | | priceSuffix | string? | Default and up — the original site's 「起」 | | learnMoreText / learnMoreLink | string? | Primary (filled) pill | | buyText / buyLink | string? | Secondary (outline) pill, carries the arrow icon | | mediaType | "image" \| "video" \| "color" | Which payload fills the frame — a three-way exclusive choice | | image | { src, alt } | One image file | | video | { videoUrl, autoplay, muted, loop } | One video file; flags default to true | | mediaColor | string | Flat fill for mediaType: "color"; "" shows the placeholder | | mediaFit | "cover" \| "contain" | Fills the frame. Ignored by the colour block | | mediaWidth | number? | Picture-area width in px. 0 keeps the built-in responsive width | | mediaHeight | number? | Picture-area height in px. 0 keeps the built-in responsive height | | cardWidth | number? | Card (whole component) width in px. 0 keeps the built-in w-full | | cardHeight | number? | Card height in px. 0 leaves it content-sized | | cardRadius | number? | Card corner radius in px. 0 leaves it square | | cardAlign | "left" \| "center" \| "right" | Card placement; default center | | bgVariant | "white" \| "black" | Swaps the whole palette, buttons included | | background / foreground | string | Required; "" keeps the variant's colour |

Media type

mediaType picks exactly one of three payloads for the frame:

| Value | Renders | | --- | --- | | image | <img> — one source | | video | <video> — one source, playsInline, preload="metadata" | | color | A flat colour block, aria-hidden, no content inside |

Each option carries exactly one upload slot — one image, one video, no poster and no per-band source set. A regression test (gives each of the three media options exactly one upload slot) pins that.

All three groups stay declared in fields; resolveFields only changes which one the inspector shows, so switching type and back keeps whatever was authored under the others. Documents saved before mediaType existed fall back to the image group.

An empty payload in any of the three renders a placeholder block rather than nothing, so a newly dropped node stays selectable and keeps its media style target.

learnMoreLink wraps the image and the video; the colour block is decoration, not a product link target, and stays bare.

Sizing: two independent levels

There are two boxes, and they are sized separately on purpose:

| Level | Props | What it is | | --- | --- | --- | | Card | cardWidth · cardHeight · cardRadius · cardAlign | The whole component — the outer <section> (the root style target). Sizes the block: title, CTAs and picture area together | | Picture area | mediaWidth · mediaHeight | The box the uploaded image or video is shown in — the media style target inside the card |

A 900px card with a 480×270 picture renders as exactly that; neither takes the other's values. A narrower picture area stays centred, because the frame sits in the column's flex-col items-center — there is deliberately no alignment prop at this level.

The uploaded <img> / <video> stay h-full w-full inside the frame, so resizing the picture area resizes the picture rather than leaving a gap inside a fixed frame. mediaFit still decides whether the file is cropped (cover) or letterboxed (contain) in whatever box it ends up with.

Card geometry

The card is the whole component — the outer <section>, i.e. the root style target. cardWidth / cardHeight / cardRadius therefore size the entire block, title, CTA pills and media frame included; the media frame inside is sized separately by mediaWidth / mediaHeight.

They are emitted as inline styles on the root, so an authored value wins over both the built-in classes and any root rule the appearance compiler emits (those are class-level).

0 is the "keep the built-in value" sentinel for the three numeric props, matching plp-product-gallery's slideWidth / slideHeight / slideRadius. Nothing authored means nothing is emitted, so an untouched node renders exactly as before.

Two consequences of the card being a real box:

  • The root is already overflow-hidden, so cardRadius clips the whole subtree. A cardHeight shorter than the content therefore clips it rather than growing — the height is a box, not a minimum.
  • cardWidth carries max-width: 100%. w-full is already container-bounded, an authored px width is not, and the root is overflow-hidden — without the cap a wide card would be silently clipped on a phone instead of shrinking.

cardAlign is a free-side margin: auto (mr-auto / mx-auto / ml-auto). It only has an effect once cardWidth narrows the card below its container; with no authored width the card still fills the container and the class is inert rather than wrong.

These four are the only card geometry exposed as props. The root style target still carries the rest of the §6.1 vocabulary (background, border, margin, padding, …); alignment is not expressible there.

Style targets

root · content · media

Image

A single <img>. The frame is fluid (100% wide on mobile, 1000×500 on desktop) and mediaFit decides whether the file is cropped (cover) or letterboxed (contain) inside it, so one source covers every band.

SPEC deviation: the original spec called for three responsive sources (PC / Pad / Mobile). The author asked for one upload per media option, so the <picture> set was dropped in favour of a single fluid <img>.

Video upload caveat

videoUrl accepts a pasted URL or a file upload. An upload becomes a session-scoped URL.createObjectURL — it previews immediately but is gone after a reload, since video bytes are far too large to inline as a data URL the way image does. Paste a hosted URL for anything that must persist. Same trade-off as @pagix/video-banner.

Styling

styles.css imports @anvilkit/tailwind-config/component and scans the @anvilkit/ui Button source (the CTA pills render through it). The @source path is five levels up — packages/runtime/ui/src/button.tsx.

License

MIT