npm package: @sensefolks/pricepoint
Description: Find the right price that your users are willing to pay
Author: SenseFolks
Latest version: 1.0.0

# sf-pricepoint

[![npm version](https://img.shields.io/npm/v/@sensefolks/pricepoint.svg)](https://www.npmjs.com/package/@sensefolks/pricepoint)
[![npm downloads](https://img.shields.io/npm/dm/@sensefolks/pricepoint.svg)](https://www.npmjs.com/package/@sensefolks/pricepoint)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

> Price sensitivity survey web component for Van Westendorp and Gabor-Granger pricing research. WCAG 2.1 AA accessible.

## Features

- 💰 Van Westendorp Price Sensitivity Meter + Gabor-Granger demand analysis
- 🔄 Automatic two-phase pipeline (VW → GG) based on response volume
- 💱 Multi-currency support with auto-detection from respondent locale
- ♿ WCAG 2.1 AA accessible (keyboard, screen reader, reduced motion)
- 🎨 Customizable via CSS Parts and Custom Properties
- 📱 Responsive design
- 🌐 Works with any framework (React, Vue, Angular, vanilla HTML)

## Installation

### NPM

```bash
npm install @sensefolks/pricepoint
```

### Yarn

```bash
yarn add @sensefolks/pricepoint
```

### CDN (Script Tag)

```html
<!-- Modern browsers (ES modules) -->
<script type="module" src="https://unpkg.com/@sensefolks/pricepoint/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>

<!-- Legacy browsers -->
<script nomodule src="https://unpkg.com/@sensefolks/pricepoint/dist/sf-pricepoint/sf-pricepoint.js"></script>
```

Or via jsDelivr:

```html
<script type="module" src="https://cdn.jsdelivr.net/npm/@sensefolks/pricepoint/dist/sf-pricepoint/sf-pricepoint.esm.js"></script>
```

## Quick Start

```html
<sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>
```

### React

```jsx
import '@sensefolks/pricepoint';

function App() {
  return <sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>;
}
```

### Vue

```vue
<template>
  <sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>
</template>

<script>
import '@sensefolks/pricepoint';
export default { name: 'App' };
</script>
```

### Angular

```typescript
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@sensefolks/pricepoint';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
```

```html
<!-- component.html -->
<sf-pricepoint survey-key="your-survey-uuid"></sf-pricepoint>
```

## API Reference

### Properties

| Property    | Attribute    | Type     | Default     | Description                                        |
| ----------- | ------------ | -------- | ----------- | -------------------------------------------------- |
| `surveyKey` | `survey-key` | `string` | `undefined` | Public key (UUID) of the survey to load (required) |

### Events

| Event           | Detail Type                | Description                                       |
| --------------- | -------------------------- | ------------------------------------------------- |
| `sfReady`       | `SfReadyEventDetail`       | Survey config loaded and component ready          |
| `sfSubmit`      | `SfSubmitEventDetail`      | Response successfully submitted                   |
| `sfError`       | `SfErrorEventDetail`       | Error occurred (load, submit, validation, config) |
| `sfPhaseChange` | `SfPhaseChangeEventDetail` | Phase transition (loading → vw → gg → complete)   |

#### Event Payload Shapes

```typescript
interface SfReadyEventDetail {
  surveyKey: string;
  productType: string;
  phase: string;
}

interface SfSubmitEventDetail {
  surveyKey: string;
  phase: string;
  completionTimeSeconds: number;
}

interface SfErrorEventDetail {
  surveyKey: string;
  errorType: 'load' | 'submit' | 'validation' | 'config';
  errorMessage: string;
}

interface SfPhaseChangeEventDetail {
  phase: string; // 'loading' | 'vw' | 'gg' | 'complete' | 'error'
}
```

### Configuration Schema

The survey is configured server-side. The component fetches config via the `surveyKey`.

```json
{
  "productContext": {
    "productName": "My SaaS Product",
    "productCategory": "Software",
    "industry": "Technology"
  },
  "targetMarkets": ["US", "GB"],
  "productType": "subscription",
  "billingInterval": "monthly",
  "priceRange": { "min": 10, "max": 200 },
  "currencyMode": "USD",
  "costAnalysis": {
    "annualCostPerCustomer": 50,
    "annualFixedExpenses": 100000,
    "expectedAnnualCustomers": 5000,
    "desiredProfitMargin": 30
  }
}
```

## CSS Parts

Style the component externally using `::part()`:

```css
/* Container & Layout */
sf-pricepoint::part(survey-container) {
  /* Main wrapper */
}
sf-pricepoint::part(heading) {
  /* Step headings */
}
sf-pricepoint::part(question-text) {
  /* Question text */
}
sf-pricepoint::part(progress-indicator) {
  /* Step/phase progress */
}

/* Van Westendorp Phase */
sf-pricepoint::part(vw-container) {
  /* VW phase wrapper */
}
sf-pricepoint::part(slider-track) {
  /* Slider track */
}
sf-pricepoint::part(slider-thumb) {
  /* Slider thumb */
}
sf-pricepoint::part(text-input) {
  /* Price text input fields */
}
sf-pricepoint::part(validation-message) {
  /* Validation error text */
}

/* Gabor-Granger Phase */
sf-pricepoint::part(gg-container) {
  /* GG phase wrapper */
}
sf-pricepoint::part(price-display) {
  /* Price label in GG */
}
sf-pricepoint::part(radio-group) {
  /* Purchase intent radio group */
}
sf-pricepoint::part(radio-option) {
  /* Individual radio option */
}
sf-pricepoint::part(radio-option-selected) {
  /* Selected radio option */
}

/* Navigation & Actions */
sf-pricepoint::part(nav-button) {
  /* Navigation buttons */
}
sf-pricepoint::part(submit-button) {
  /* Submit button */
}

/* States */
sf-pricepoint::part(loading-message) {
  /* Loading state text */
}
sf-pricepoint::part(error-message) {
  /* Error message text */
}
sf-pricepoint::part(error-container) {
  /* Error wrapper with retry */
}
sf-pricepoint::part(retry-button) {
  /* Retry button */
}
sf-pricepoint::part(completion-screen) {
  /* Completion state */
}

/* Accessibility */
sf-pricepoint::part(announcements) {
  /* Screen reader live region */
}
```

## CSS Custom Properties

```css
sf-pricepoint {
  --sf-primary-color: #4f46e5; /* Primary accent color */
  --sf-background-color: #ffffff; /* Background color */
  --sf-text-color: #1f2937; /* Text color */
  --sf-border-radius: 8px; /* Border radius for inputs and cards */
  --sf-font-family: system-ui, sans-serif; /* Font family */
  --sf-spacing-scale: 1; /* Spacing multiplier (1 = default) */
}
```

## Accessibility

- Full keyboard navigation (Tab, Enter, Space, Arrow keys)
- ARIA `role="slider"` with `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-label` on hybrid slider inputs
- ARIA `role="radiogroup"` on Gabor-Granger purchase intent scale
- `aria-live="polite"` region for screen reader announcements on phase/question transitions
- Visible focus indicators on all interactive elements
- Respects `prefers-reduced-motion: reduce`
- High contrast mode support via `@media (prefers-contrast: high)`

## Browser Support

- Chrome 88+
- Firefox 85+
- Safari 14+
- Edge 88+
- IE11 (with ES5 build)

## Agent Integration

This section is designed for AI code generation agents integrating the `sf-pricepoint` component.

### Machine-Readable Config Example

```json
{
  "component": "sf-pricepoint",
  "package": "@sensefolks/pricepoint",
  "attributes": [{ "name": "survey-key", "type": "string", "required": true, "description": "Public key UUID of the survey" }],
  "events": [
    { "name": "sfReady", "detail": "SfReadyEventDetail", "description": "Component loaded and ready" },
    { "name": "sfSubmit", "detail": "SfSubmitEventDetail", "description": "Response submitted" },
    { "name": "sfError", "detail": "SfErrorEventDetail", "description": "Error occurred" },
    { "name": "sfPhaseChange", "detail": "SfPhaseChangeEventDetail", "description": "Phase changed" }
  ],
  "phases": ["loading", "vw", "gg", "complete", "error"],
  "cssCustomProperties": ["--sf-primary-color", "--sf-background-color", "--sf-text-color", "--sf-border-radius", "--sf-font-family", "--sf-spacing-scale"]
}
```

### Emitted Events with Payload Shapes

| Event           | Payload Fields                                                    | When Emitted                |
| --------------- | ----------------------------------------------------------------- | --------------------------- |
| `sfReady`       | `surveyKey: string, productType: string, phase: string`           | After config fetch and init |
| `sfSubmit`      | `surveyKey: string, phase: string, completionTimeSeconds: number` | After successful POST       |
| `sfError`       | `surveyKey: string, errorType: string, errorMessage: string`      | On any error                |
| `sfPhaseChange` | `phase: string`                                                   | On phase transition         |

### Integration Checklist

1. Install the package: `npm install @sensefolks/pricepoint`
2. Import the component: `import '@sensefolks/pricepoint';`
3. Add the element: `<sf-pricepoint survey-key="YOUR_KEY"></sf-pricepoint>`
4. Listen for events: `el.addEventListener('sfReady', (e) => { ... })`
5. Apply custom styles via CSS custom properties or `::part()` selectors
6. Ensure the survey is created and configured via the API before embedding

### TypeScript Types

All types are exported from the package entry point:

```typescript
import type {
  PricepointSurveyConfig,
  PriceLadderResponse,
  CurrencyResolution,
  VWAnswers,
  SfReadyEventDetail,
  SfSubmitEventDetail,
  SfErrorEventDetail,
  SfPhaseChangeEventDetail,
} from '@sensefolks/pricepoint';
```

## Changelog

See [CHANGELOG.md](./CHANGELOG.md).

## License

MIT © [SenseFolks](https://sensefolks.com)