npm package: @juuno-sdk/iframe-app-react
Description: React SDK for building iframe-based Juuno apps (v0, Vercel, etc.)
Author: Juuno
Latest version: 1.1.1

# @juuno-sdk/iframe-app-react

React SDK for building iframe-based Juuno apps. Use this for apps built with v0, Vercel, or any React framework that will be embedded in the Juuno player via iframe.

## Installation

```bash
npm install @juuno-sdk/iframe-app-react
```

## Quick Start

```tsx
import { useJuuno } from '@juuno-sdk/iframe-app-react';

export default function MyApp() {
  const { isActive, isPaused, duration } = useJuuno();

  if (!isActive) {
    return <div>Waiting...</div>;
  }

  return <Game duration={duration} paused={isPaused} />;
}
```

## API

### `useJuuno(defaultDuration?, callbacks?)`

| Parameter         | Type             | Default | Description                             |
| ----------------- | ---------------- | ------- | --------------------------------------- |
| `defaultDuration` | `number`         | `30000` | Duration in ms when running standalone  |
| `callbacks`       | `JuunoCallbacks` | -       | Optional callbacks for lifecycle events |

**Returns:**

| Property     | Type      | Description                                     |
| ------------ | --------- | ----------------------------------------------- |
| `isActive`   | `boolean` | `true` when scene is active (playing or paused) |
| `isPaused`   | `boolean` | `true` when player is paused                    |
| `duration`   | `number`  | Scene duration in milliseconds                  |
| `resetCount` | `number`  | Increments each reset - use to restart effects  |

**State Model:**

| State   | isActive | isPaused | Description                    |
| ------- | -------- | -------- | ------------------------------ |
| Waiting | `false`  | `false`  | Scene not started or was reset |
| Playing | `true`   | `false`  | Actively running               |
| Paused  | `true`   | `true`   | Active but frozen              |

Derive `isPlaying` if needed: `const isPlaying = isActive && !isPaused`

**Callbacks:**

| Callback   | Arguments                                 | Description                |
| ---------- | ----------------------------------------- | -------------------------- |
| `onStart`  | `{ duration: number, timestamp: number }` | Scene became visible       |
| `onReset`  | -                                         | Scene hidden, should reset |
| `onPause`  | -                                         | Player paused              |
| `onResume` | -                                         | Player resumed             |

## How It Works

When your app runs inside the Juuno player:

1. App loads inside an iframe and displays a "waiting" state
2. Juuno sends a `start` signal when the scene becomes visible
3. Your app starts running with the scene `duration`
4. If the user pauses the player, Juuno sends `pause`/`resume` signals
5. Juuno sends a `reset` signal when the scene ends
6. Your app returns to the waiting state for the next cycle

When running standalone (for local development):

- `isActive` is `true` immediately
- `isPaused` is always `false`
- `duration` uses the default value

## Examples

### Basic Usage

```tsx
import { useJuuno } from '@juuno-sdk/iframe-app-react';

export default function MyApp() {
  const { isActive, isPaused, duration } = useJuuno();

  if (!isActive) {
    return <div className="loading">Waiting...</div>;
  }

  return (
    <div className="app">
      {isPaused && <div className="paused-overlay">Paused</div>}
      <Game duration={duration} paused={isPaused} />
    </div>
  );
}
```

### Using Callbacks

```tsx
import { useJuuno } from '@juuno-sdk/iframe-app-react';
import { useRef } from 'react';

export default function VideoApp() {
  const videoRef = useRef<HTMLVideoElement>(null);

  const { isActive, duration } = useJuuno(30000, {
    onStart: () => {
      videoRef.current?.play();
    },
    onReset: () => {
      if (videoRef.current) {
        videoRef.current.pause();
        videoRef.current.currentTime = 0;
      }
    },
    onPause: () => {
      videoRef.current?.pause();
    },
    onResume: () => {
      videoRef.current?.play();
    },
  });

  if (!isActive) {
    return <div>Loading video...</div>;
  }

  return <video ref={videoRef} src="/video.mp4" />;
}
```

### Using resetCount for Animations

```tsx
import { useJuuno } from '@juuno-sdk/iframe-app-react';
import { useEffect, useState } from 'react';

export default function AnimatedApp() {
  const { isActive, isPaused, resetCount, duration } = useJuuno();
  const [items, setItems] = useState<string[]>([]);

  // Restart animation when scene resets.
  useEffect(() => {
    if (!isActive) return;

    setItems([]);
    const timeouts: NodeJS.Timeout[] = [];
    const itemsToShow = ['First', 'Second', 'Third'];

    itemsToShow.forEach((item, index) => {
      const delay = (duration / itemsToShow.length) * index;
      timeouts.push(
        setTimeout(() => setItems((prev) => [...prev, item]), delay),
      );
    });

    return () => timeouts.forEach(clearTimeout);
  }, [isActive, resetCount, duration]);

  if (!isActive) return <div>Waiting...</div>;

  return (
    <ul>
      {items.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
}
```

---

## v0 Integration Prompt

Use this prompt when creating a new Juuno app with v0:

```
Add Juuno player integration to this app using @juuno-sdk/iframe-app-react.

Install: npm install @juuno-sdk/iframe-app-react

Use the useJuuno hook:

const { isActive, isPaused, duration, resetCount } = useJuuno();

Requirements:
1. Show a waiting/loading state when !isActive
2. Pause animations/timers when isPaused is true
3. Use duration to time content to fit the scene length
4. Use resetCount as a useEffect dependency to restart animations when the scene cycles
5. Derive isPlaying if needed: const isPlaying = isActive && !isPaused

State model:
- isActive: false, isPaused: false → Waiting (show loading state)
- isActive: true, isPaused: false → Playing (run animations)
- isActive: true, isPaused: true → Paused (freeze state, don't reset)
```

## License

MIT