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

panapanorama

v0.1.8

Published

React panorama component for building interactive 3D panorama experiences.

Readme

PanaPanorama

React panorama component for building branded, interactive 3D panorama demos.

The main idea is simple: PanaPanorama renders the panorama, and each anchor can render any React node you provide. The included anchor helpers are convenience APIs, not required styling.

Install

npm install panapanorama

Phone Testing Through ngrok

Start the Vite dev server:

npm run dev

In another terminal, start the named ngrok tunnel:

npm run dev:tunnel

This uses ngrok.yml and expects ngrok to be installed locally and available on your PATH. It also expects panapanorama.ngrok.app to be reserved in your ngrok account. If that domain is not reserved yet, either reserve it in the ngrok dashboard or use a temporary generated URL:

npm run dev:tunnel:random

Import the package styles once in your app:

import 'panapanorama/style.css'

Custom Anchors

Use createPanoramaAnchor when each demo needs its own anchor design.

import {
  PanaPanorama,
  createPanoramaAnchor,
  usePanoramaAnchorLayout,
  type PanoramaAnchor,
} from 'panapanorama'

function CarFeatureAnchor() {
  const layout = usePanoramaAnchorLayout()

  return (
    <article className="car-feature-anchor" data-placement={layout?.placement}>
      <p>Powertrain</p>
      <h2>Twin-Turbo V6</h2>
      <p>Track-ready response with daily-driver refinement.</p>
    </article>
  )
}

const anchors: PanoramaAnchor[] = [
  createPanoramaAnchor({
    id: 'powertrain',
    yaw: 1.2,
    pitch: -0.08,
    scrollOrder: 1,
    content: <CarFeatureAnchor />,
  }),
]

export function CarDemo() {
  return (
    <PanaPanorama
      source="/demo/car-panorama.jpg"
      anchors={anchors}
      initialView={{ yaw: 0, pitch: 0, zoom: 1 }}
    />
  )
}

Your component owns its markup, styles, animation, buttons, and business-specific copy. PanaPanorama only handles projection, visibility, navigation, and layout context.

Included Presets

Use these when you want the current connected-card look:

import {
  createPanoramaCardAnchor,
  createPanoramaInfoAnchor,
  PanoramaAnchoredCard,
  PanoramaAnchorRingMarker,
  PanoramaInfoAnchor,
} from 'panapanorama'
  • createPanoramaAnchor: plain custom React content.
  • createPanoramaCardAnchor: wraps custom content in the included connected-card shell.
  • createPanoramaInfoAnchor: creates the included eyebrow/title/body info card.
  • PanoramaAnchoredCard: reusable connected-card shell.
  • PanoramaAnchorRingMarker: reusable ring marker.
  • PanoramaInfoAnchor: reusable text-card preset.

The current demo uses createPanoramaInfoAnchor because it wants the included info-card preset. A different business demo can use createPanoramaAnchor and provide completely different React.

Anchor Options

All helpers return a normal PanoramaAnchor, so the same anchor configuration applies:

createPanoramaAnchor({
  id: 'wheel',
  yaw: 0.82,
  pitch: -0.24,
  placement: 'left',
  offset: { x: -16, y: 8 },
  scale: 0.9,
  marker: 'beacon',
  hiddenMarker: 'beacon',
  markerGoToAnchor: true,
  content: <WheelAnchor />,
})

Useful options:

  • yaw and pitch: where the anchor lives in the panorama.
  • scrollOrder: where it appears in stepped timeline navigation.
  • placement: where preset card shells place content relative to the marker.
  • offset: fine-tunes card position.
  • scale and markerScale: size content or marker.
  • marker, hiddenMarker, and renderHiddenMarker: marker behavior.
  • markerGoToAnchor: clicking marker navigates to that anchor.

Authoring

Enable anchor creation while building a demo:

<PanaPanorama
  source="/demo/panorama.jpg"
  anchors={anchors}
  mode="authoring"
  onAnchorCreate={(anchor) => {
    console.log(anchor)
  }}
  debug={{
    enabled: true,
    allowAnchorCreation: true,
    copyTools: true,
  }}
/>

The copied anchor coordinates can be added to your scene data, then rendered with any custom React component.

Postprocessing

Enable restrained screen-space lighting effects from the core package with a preset:

<PanaPanorama
  source="/demo/campus-night.jpg"
  postprocessing={{ preset: 'cinematic-neon' }}
  debug={{
    enabled: true,
    postprocessingControls: true,
  }}
/>

Presets include off, soft-glow, cinematic-neon, and premium-night. You can override bloom, emissive masking, shimmer, chromatic strength, and quality per scene while the panorama projection remains unchanged.

Each effect group can also oscillate its primary strength:

postprocessing={{
  preset: 'cinematic-neon',
  emissive: {
    targetColor: '#22d8ff',
    colorTolerance: 0.35,
    colorInfluence: 0.8,
  },
  bloom: {
    oscillation: {
      enabled: true,
      min: 0.22,
      max: 0.38,
      durationSeconds: 8,
    },
  },
}}

Mapping Image Coordinates

Use getPanoramaAnglesFromImagePoint when you have one x/y point from the supplied image and need anchor coordinates:

import { getPanoramaAnglesFromImagePoint } from 'panapanorama'

const { yaw, pitch } = getPanoramaAnglesFromImagePoint(
  { x: 1320, y: 420 },
  { width: 2000, height: 1000 },
  {
    sourceOptions: {
      flipX: true,
    },
  }
)

Use the same sourceOptions you pass to PanaPanorama. For more detail, mapImagePointToPanorama also returns normalized panorama coordinates, normalized output size, and whether the point is visible after source adaptation.

For repeated scene data, create anchors directly from image points:

import {
  createPanoramaAnchorsFromImagePoints,
  createPanoramaInfoAnchor,
  type PanoramaImageAnchorPoint,
  type CreatePanoramaInfoAnchorOptions,
} from 'panapanorama'

const scenes: PanoramaImageAnchorPoint<CreatePanoramaInfoAnchorOptions>[] = [
  {
    id: 'mapped-point',
    x: 1320,
    y: 420,
    eyebrow: 'Feature',
    title: 'Mapped Anchor',
    body: 'This anchor is authored from image coordinates.',
  },
]

const anchors = createPanoramaAnchorsFromImagePoints({
  imageSize: { width: 2000, height: 1000 },
  sourceOptions: { flipX: true },
  points: scenes,
  createAnchor: createPanoramaInfoAnchor,
})