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/pdp-product-hero

v0.1.0

Published

Anvilkit Puck-native PDP full-screen hero — title + XMAGE badge over a configurable video/image/color background, with a centered price and a configurable list of call-to-action buttons. Ideal for the top of product detail pages.

Readme

@pagix/pdp-product-hero

Puck-native PDP full-screen hero — a full-bleed background, a centred title with an optional badge, a price, and a configurable list of CTA buttons beneath it. Replicates the section-kv / .kv-container module at the top of a Huawei product detail page, with two axes the original does not have: a three-way background and an arbitrary number of buttons.

Usage

import { createPdpProductHeroConfig } from "@pagix/pdp-product-hero";

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

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

Layout

Four regions, in this DOM order:

| Region | Position | Style target | | --- | --- | --- | | Background frame | In flow; 150vw / 50vw / 49vw tall (mobile / tablet / desktop) | background | | Title + badge | Absolute, over the frame, offset from the section's top | title | | Content column | In flow, after the frame: price then CTAs | content → price · buttons | | Disclaimer | Absolute, pinned to the container's bottom corner | disclaimer |

The frame is a real in-flow element rather than an absolute layer, which is what the original does — the content column follows it, so the CTAs land below the fold line rather than floating over the video.

The title and disclaimer are position: absolute, so they sit over the background while staying in the section's reading order. On mobile the disclaimer goes full-width and centred; from md up it moves to the bottom-right corner and switches to a light grey so it keeps contrast against the bright edges of the video.

Background: three mutually exclusive payloads

backgroundType picks exactly one:

| Value | Renders | Payload prop | | --- | --- | --- | | video | <video autoPlay muted loop playsInline> + a responsive <picture> poster layer | backgroundVideo | | image | <picture> with per-band <source> | backgroundImage | | color | A flat aria-hidden block | backgroundColor |

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 backgroundType existed fall back to the video group — the original site's payload.

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

Art direction

backgroundImage and backgroundVideo carry one source per band:

| Band | Breakpoint | | --- | --- | | pc | ≥ 1024px | | pad | 768–1023px | | mobile | < 768px |

An empty narrower band falls back to the desktop source at render time, so authoring one file is enough. The <source> list is ordered widest first: a browser that ignores media on <video><source> (support is uneven) then lands on the desktop file — the sane fallback — rather than a phone crop stretched across a monitor.

Layout and asset selection use the same width breakpoints on purpose. The original converts its aspect-ratio media queries (max-aspect-ratio: 6/5, 7/10) into width queries; a width-selected asset set feeding an aspect-ratio-sized frame is exactly the mismatch that can leave a band with no matching art.

backgroundFit (cover / contain) decides whether the payload fills or letterboxes inside the frame. It is ignored by the colour block, which has no intrinsic aspect to fit.

Buttons

buttons is an array field — add, remove and reorder at will. Each item:

| Key | Type | Notes | | --- | --- | --- | | text | string | The label | | link | string | Empty renders an inert control rather than a dead link | | variant | "primary" \| "secondary" \| "ghost" | primary is the original's filled black pill | | openInNewPage | boolean | Adds target="_blank" + rel="noopener noreferrer" |

secondary and ghost draw their colour from titleColor, border included, so an inverted hero stays legible without a second colour prop.

An empty list renders the CTA row with nothing in it — the buttons style target stays present, so the group can still be selected and styled.

Props

| Prop | Type | Notes | | --- | --- | --- | | title | string | Centred <h1>; 8.33vw / 4.9vw (mobile / desktop) | | badgeText | string? | Short chip after the title, e.g. XMAGE; empty renders no chip | | badgeImageUrl | string? | Badge artwork; wins over badgeText when set | | titleColor | string | Required; "" keeps the built-in black. Also colours the outline buttons | | price | number? | Rendered as <currency> <price>; omitted from the DOM when undefined | | currency | string? | Default ¥ | | priceSuffix | string? | Trailing qualifier, e.g. 起 | | priceColor | string | Required; "" keeps the built-in black | | disclaimerText | string? | Conditional on its prop — no element when empty | | backgroundType | "video" \| "image" \| "color" | Which payload fills the frame | | backgroundImage | { pc, pad, mobile, alt } | alt empty marks the image decorative | | backgroundVideo | { pcVideoUrl, pcPosterUrl, padVideoUrl, padPosterUrl, mobileVideoUrl, mobilePosterUrl } | | | backgroundColor | string | Flat fill for backgroundType: "color"; "" shows the placeholder | | backgroundFit | "cover" \| "contain" | Default cover | | buttons | PdpProductHeroButton[] | See above | | classNames | Record<string, string> | Tailwind passthrough, one key per style target | | animation | AnimationProps | Entrance animation, applied to the root |

Why the title is text, not the original's SVG wordmark

The original renders the product name as an inline <svg> wordmark. Here it is a real <h1> with authorable text, which is what a CMS-driven PDP needs. The badge case is covered by badgeImageUrl — an uploaded SVG renders inside an <img>, so any script it carries cannot execute. That is the reason this package has no dangerouslySetInnerHTML path for author markup.

Video upload caveat

pcVideoUrl 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 images do. Paste a hosted URL for anything that must persist. Same trade-off as @pagix/video-banner and @pagix/main-product-hero.

The poster/band URLs are plain text fields for the same reason — one upload slot per video, URLs for everything else.

Style targets

root · background · title · content · price · buttons · disclaimer

All seven are stamped in every render mode, including edit mode, so a target can never be declared without a matching element.

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