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

funifier-story-player

v0.1.0

Published

Player de Histórias Interativas do Funifier

Readme

funifier-story-player

Player de Histórias Interativas do Funifier

Install

npm install funifier-story-player

react e react-dom (>= 18) são peerDependencies (o app já os tem). Importe a folha de estilo uma vez na aplicação:

import "funifier-story-player/styles.css";

Quick start

import { StoryPlayer } from "funifier-story-player";
import "funifier-story-player/styles.css";

<div style={{ position: "relative", width: 360, height: 640 }}>
  <StoryPlayer bundle={bundle} onComplete={(r) => {}} />
</div>;

Casos de uso

1. Dados prontos + UI pronta

Você já tem o bundle (buscou você mesmo, ou no server do Next).

<StoryPlayer bundle={bundle} onComplete={fn} />

2. O player busca + UI pronta

Passe storyId + transport; o player busca sob demanda.

<StoryPlayer storyId="s1" transport={transport} onComplete={fn} />

3. Motor headless (UI própria)

useStoryEngine entrega estado + ações, sem UI.

const engine = useStoryEngine({ bundle }); // ou { storyId, transport }
// engine.phase, engine.currentScene, engine.chooseOption, engine.advance, ...

Fonte de dados

Forneça bundle ou transport.

| prop | tipo | descrição | | --- | --- | --- | | bundle | StoryBundle | { story, characters, scenes } — dados prontos | | transport | (storyId: string) => Promise<StoryBundle> | função que busca sob demanda | | storyId | string | id passado ao transport |

Props (root)

Além de bundle / transport / storyId (ver Fonte de dados acima):

| prop | tipo | default | descrição | | --- | --- | --- | --- | | lang | "pt" \| "es" \| "en" | "pt" | idioma dos textos da UI (botões, rótulos) | | theme | StoryTheme | paleta neutra | 3 cores + radius/font (ver Theme) | | className | string | — | classe extra no elemento raiz | | onComplete | (result: StoryCompletionResult) => void | — | história concluída (ver Callbacks) | | onExit | () => void | — | usuário saiu ou fechou a tela final | | onAspectRatio | (ratio: "16:9" \| "9:16" \| "1:1") => void | — | proporção da história (dimensionar o container) |

Configuração de exibição (capa, controles, slots) é feita por componentes-filho — ver Compound components.

Compound components

A capa e os controles são configurados por componentes-filho.

Como as props funcionam: todos os elementos são visíveis por padrão. Você esconde o que não quer passando ={false}. Passar true (ex.: <StoryPlayer.Cover play />) não muda nada — já é o padrão.

<StoryPlayer bundle={bundle} lang="es" theme={theme} onComplete={fn}>
  <StoryPlayer.Cover synopsis={false} />              {/* capa sem sinopse */}
  <StoryPlayer.Controls volume={false} skip={false} /> {/* sem volume, sem pular */}
  <StoryPlayer.Slots topLeft={<Badge />} />            {/* injeta UI própria */}
</StoryPlayer>

<StoryPlayer.Cover> — o que aparece na capa

Todas boolean, default true. Passe ={false} para esconder.

| prop | controla | | --- | --- | | title | título da história | | synopsis | sinopse | | cast | botão Elenco + avatares + nomes | | play | botão Reproduzir | | enabled | mostra a capa — false inicia a história direto |

<StoryPlayer.Cover synopsis={false} />                             // esconde a sinopse
<StoryPlayer.Cover cast={false} title={false} />                   // sinopse + play
<StoryPlayer.Cover title={false} synopsis={false} cast={false} /> // só o botão Reproduzir
<StoryPlayer.Cover enabled={false} />                             // pula a capa

<StoryPlayer.Controls> — controles durante a cena

Todas boolean, default true. Passe ={false} para esconder.

| prop | controla | | --- | --- | | title | título no topo | | playPause | botão play/pause central | | subtitle | legendas | | sceneProgress | barra de progresso da cena | | storyProgress | barra de progresso da história | | cast | botão Elenco | | skip | botão Pular fala | | volume | controle de volume da música |

<StoryPlayer.Controls volume={false} />                 // esconde o volume
<StoryPlayer.Controls subtitle={false} skip={false} />  // sem legenda nem pular

<StoryPlayer.Slots> — injeta UI própria nas bordas

Cada prop recebe um ReactNode (só aparece se você passar algo).

| prop | posição | | --- | --- | | topLeft | canto superior esquerdo | | topCenter | topo centralizado | | topRight | canto superior direito | | bottomLeft | canto inferior esquerdo | | bottomRight | canto inferior direito | | overlay | camada livre sobre tudo |

<StoryPlayer.Slots topLeft={<span>Módulo I</span>} />

Theme

<StoryPlayer
  bundle={bundle}
  theme={{
    primary: "#6d5ae6",
    secondary: "#94a3b8",
    tertiary: "#22d3ee",
    radius: 14,
    font: '"Poppins", sans-serif',
  }}
/>

| campo | aplica em | | --- | --- | | primary | CTA, progresso, palavra ativa do karaokê | | secondary | controles de apoio, nome do personagem | | tertiary | hotspot, respiração, acentos | | radius | number (px) ou string CSS | | font | família de fonte |

Callbacks

onComplete(result) recebe StoryCompletionResult:

| campo | tipo | | --- | --- | | story_id | string | | score | number | | passing_score | number \| undefined | | passed | boolean \| undefined | | end_label | string \| undefined | | decisions_taken | StoryDecisionTaken[] | | variables | Record<string, unknown> |

Transports Funifier (./funifier)

Helpers que buscam a história no Funifier. Você injeta a URL base e o token/apikey — nada disso fica embutido no pacote.

No .env do seu app:

REACT_APP_API_URL=https://your-instance.funifier.com/v3

Em Next, o prefixo é NEXT_PUBLIC_ (ex.: NEXT_PUBLIC_API_URL).

Autenticado (usuário logado)

O getToken devolve o token do player. Normalmente você lê o token de um contexto/estado global do seu app (auth/sessão):

import { StoryPlayer } from "funifier-story-player";
import { funifierTransport } from "funifier-story-player/funifier";
import { useAuth } from "context/AuthContext"; // seu contexto global

function Story() {
  const { token } = useAuth(); // token da sessão, vindo do contexto global

  const transport = funifierTransport({
    service: process.env.REACT_APP_API_URL!,
    getToken: () => token,
  });

  return <StoryPlayer storyId="s1" transport={transport} />;
}

Anônimo (sem login)

import { funifierPublicTransport } from "funifier-story-player/funifier";

const transport = funifierPublicTransport({
  service: process.env.REACT_APP_API_URL!,
  apiKey: process.env.REACT_APP_APIKEY!, // identificador público do tenant
});

<StoryPlayer storyId="s1" transport={transport} />;

Registrar a conclusão (opcional)

Se a história tem uma ação de conclusão configurada, dispare-a no onComplete (o pacote não faz isso sozinho):

onComplete={(result) =>
  fireStoryOnComplete({
    service: process.env.REACT_APP_API_URL!,
    getToken: () => token,
    story: bundle.story,
    result,
  })
}

Exports

| export | o que faz | | --- | --- | | funifierTransport({ service, getToken }) | busca a história autenticado (token do player) | | funifierPublicTransport({ service, apiKey }) | busca a história anônimo (apikey pública) | | fireStoryOnComplete({ service, getToken, story, result }) | dispara a ação de conclusão da história (se houver): monta os atributos e faz o POST | | buildCompletionAttributes(story, result) | só monta { actionId, attributes } a partir das variáveis — pra você fazer o POST você mesmo | | funifierActionLog({ service, getToken, actionId, attributes? }) | POST de action log genérico |

Backend (endpoint anônimo, ação de conclusão): docs/backend-setup.md.

Headless

import { useStoryEngine } from "funifier-story-player";

const engine = useStoryEngine({ bundle });

Exports

StoryPlayer (.Cover, .Controls, .Slots) · useStoryEngine · useTts · getStrings · useStrings · STORY_STRINGS. Subpath ./funifier: transports + action log. Tipos: StoryBundle, Story, StoryScene, StoryCharacter, StoryCompletionResult, StoryTheme, StoryCoverConfig, StoryControlsConfig, StorySlots, StoryLang.

Security

  • Zero segredo/env/token embutido; sem telemetria.
  • Auth injetada pelo consumidor; o pacote não lê process.env.
  • Zero dependência de runtime além de react/react-dom (peer).
  • CSS escopado sob .fstory.

License

Proprietary — Funifier. Ver LICENSE.