@touchcastllc/napster-companion-api-dev 1.6.0-alpha.2 → 1.6.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,11 +50,83 @@ No bundler? Drop in the standalone build with a `<script>` tag — styles are in
50
50
  ```html
51
51
  <script src="https://unpkg.com/@touchcastllc/napster-companion-api"></script>
52
52
  <script>
53
- // Connect with a short-lived token minted by your backend.
54
- napsterCompanionApiSDK.init(connectionToken);
53
+ // The SDK mints the token itself, once microphone permission has settled —
54
+ // connection tokens are short-lived, so they must not be minted any earlier.
55
+ napsterCompanionApiSDK.init(() => fetchConnectionToken());
55
56
  </script>
56
57
  ```
57
58
 
59
+ ### Click-to-start
60
+
61
+ By default `init()` connects immediately. To show an entry point and connect on
62
+ click, opt in with `button.enabled`:
63
+
64
+ ```js
65
+ const widget = await napsterCompanionApiSDK.init(() => fetchConnectionToken(), {
66
+ button: {
67
+ enabled: true,
68
+ label: "Talk to an agent",
69
+ description: "Ask about your order",
70
+ },
71
+ });
72
+
73
+ widget.getInstance(); // null — nothing has connected yet
74
+
75
+ // Later, once the visitor clicks (or you call start() yourself):
76
+ await widget.start(); // resolves the instance, or null on failure — never rejects
77
+ widget.end(); // back to idle; the entry point reappears and can start another session
78
+ ```
79
+
80
+ `start()` never throws — a failed attempt is reported through your `onError` callback
81
+ and the entry point returns to idle, so there is nothing to catch around it.
82
+
83
+ The controller return type is selected from the config **literal**:
84
+
85
+ ```ts
86
+ // ✅ Inferred as a controller
87
+ const widget = await napsterCompanionApiSDK.init(() => fetchConnectionToken(), {
88
+ button: { enabled: true },
89
+ });
90
+ ```
91
+
92
+ Hoisting the config to a variable first widens `enabled` to `boolean`, so TypeScript
93
+ picks the instance overload — even though you still get a controller at runtime:
94
+
95
+ ```ts
96
+ // ❌ Typed as an instance, though a controller is what you get
97
+ const options = { button: { enabled: true } };
98
+ const widget = await napsterCompanionApiSDK.init(() => fetchConnectionToken(), options);
99
+ ```
100
+
101
+ `as const` keeps the literal type, so the overload resolves correctly:
102
+
103
+ ```ts
104
+ // ✅ Typed as a controller
105
+ const options = { button: { enabled: true } } as const;
106
+ const widget = await napsterCompanionApiSDK.init(() => fetchConnectionToken(), options);
107
+ ```
108
+
109
+ Already have a button in your design system? Point the SDK at it and it renders
110
+ nothing of its own — it binds the click, sets `data-np-state`, and hides the node
111
+ while the session is live:
112
+
113
+ ```js
114
+ await napsterCompanionApiSDK.init(() => fetchConnectionToken(), {
115
+ button: { enabled: true, element: "#my-cta" },
116
+ });
117
+ ```
118
+
119
+ The entry point survives every way a session can end — the visitor pressing **End**,
120
+ the inactivity timeout, a dropped connection, `instance.destroy()` — and returns to
121
+ idle so the next session is one click away.
122
+
123
+ > **Caveat: `button` and `persistence` do not compose yet.** Ending a session under
124
+ > persistence has to unwrap the page, and unwrapping is a real top-level navigation.
125
+ > That page load takes the entry point with it, so the visitor gets a fresh load of
126
+ > the page rather than the button reappearing in place — and any navigation they did
127
+ > inside the persisted frame is lost. The SDK warns when both flags are on. Use one
128
+ > or the other until this is resolved.
129
+
58
130
  For framework-specific setup, version pinning and the first working session, see
59
131
  **[developers.napster.com/docs/sdks/web-sdk/installation](https://developers.napster.com/docs/sdks/web-sdk/installation)**.
60
132
 
@@ -0,0 +1,8 @@
1
+ import "./Button.css";
2
+ export interface ButtonElementOptions {
3
+ label: string;
4
+ avatarUrl?: string;
5
+ description?: string;
6
+ onClick: () => void;
7
+ }
8
+ export declare function createButtonElement(options: ButtonElementOptions): HTMLButtonElement;
@@ -0,0 +1,7 @@
1
+ export interface NavigationHold {
2
+ /** Start intercepting same-origin link clicks. No-op when persistence is off. */
3
+ hold(): void;
4
+ /** Stop intercepting. `connected` decides what happens to a held navigation. */
5
+ release(connected: boolean): void;
6
+ }
7
+ export declare function createNavigationHold(enabled: boolean, navigate?: (url: string) => void): NavigationHold;
@@ -0,0 +1,45 @@
1
+ import type { NapsterCompanionApiConfig, NapsterCompanionApiController, NapsterCompanionApiInstance } from "../types";
2
+ export interface ButtonSessionDeps {
3
+ /** The SDK-owned root the card mounts into. */
4
+ root: HTMLElement;
5
+ /** Run one session attempt: epoch, mic gate, mint, connect. Resolves when connected. */
6
+ startSession: () => Promise<NapsterCompanionApiInstance>;
7
+ /** End the live session but keep the root and trigger mounted. */
8
+ endSession: () => void;
9
+ /**
10
+ * Abandon an attempt that is still connecting: bump the session epoch so the
11
+ * in-flight gate/mint drops out at its next check, and drop whatever the
12
+ * attempt already mounted (the embed, a microphone-recovery card), keeping the
13
+ * root and trigger. Without this, end() during connect was a silent no-op and
14
+ * the session came up anyway a moment later.
15
+ */
16
+ abortAttempt: () => void;
17
+ /** Tear down the session AND the root. */
18
+ disposeSession: () => void;
19
+ /**
20
+ * Register a handler fired when the avatar reports ready. Returns an
21
+ * unsubscribe. The trigger's "active" state follows this, not start()'s
22
+ * promise: a denied microphone leaves the attempt alive behind the recovery
23
+ * card, and the entry point must stay in "connecting" until a session is
24
+ * genuinely up.
25
+ */
26
+ onAvatarReady: (handler: (isReady: boolean) => void) => () => void;
27
+ /**
28
+ * Register a handler for an attempt that failed after the gate had already
29
+ * granted — including a "Check again" retry, where no caller is awaiting
30
+ * start(). Returns an unsubscribe.
31
+ */
32
+ onSessionFailed: (handler: (error: Error) => void) => () => void;
33
+ /**
34
+ * Register a handler for a session the SDK ended on its own — the control
35
+ * bar's End button, the inactivity overlay, a server termination, a fatal
36
+ * transport error, or a host calling `instance.destroy()`. None of these go
37
+ * through `end()`, so without this channel the controller would keep a stale
38
+ * instance and a trigger stranded on "active". Returns an unsubscribe.
39
+ *
40
+ * This is a normal end, not a failure: the handler returns the entry point to
41
+ * idle and must NOT report through `onError`.
42
+ */
43
+ onSessionEnded: (handler: () => void) => () => void;
44
+ }
45
+ export declare function createButtonSession(deps: ButtonSessionDeps, config: Partial<NapsterCompanionApiConfig>): NapsterCompanionApiController;
@@ -0,0 +1,21 @@
1
+ import type { ButtonConfig } from "../types";
2
+ export type TriggerState = "idle" | "connecting" | "active";
3
+ export interface Trigger {
4
+ setState(state: TriggerState): void;
5
+ /** Release the trigger. SDK-owned nodes are removed; host-owned nodes are restored. */
6
+ destroy(): void;
7
+ }
8
+ /** Resolve a configured trigger to a live element, or fail loudly. */
9
+ export declare function resolveTriggerNode(element: HTMLElement | string): HTMLElement;
10
+ /**
11
+ * The SDK's own card, rendered into the root it already owns. The card and the
12
+ * avatar swap via the root's `data-np-state`, which createButtonSession sets —
13
+ * so all this has to do is keep the card from taking a second click.
14
+ */
15
+ export declare function createSdkTrigger(root: HTMLElement, config: ButtonConfig, onClick: () => void): Trigger;
16
+ /**
17
+ * A host-owned node. The SDK never moves, reparents or restyles it beyond
18
+ * hiding it for the duration of a session — and puts back exactly what it
19
+ * found on destroy, because the host owns this element.
20
+ */
21
+ export declare function createHostTrigger(node: HTMLElement, onClick: () => void): Trigger;
package/lib/index.css CHANGED
@@ -1 +1 @@
1
- .np_companion-live-container,.np_companion-preview-container{height:100%;margin:0;padding:0;position:absolute;transition:opacity .3s ease-in-out;width:100%}.np_companion-preview-container{opacity:1;z-index:2}.np_companion-preview-container.np_companion-fade-out{opacity:0;pointer-events:none;transition:opacity .5s ease-out}.np_companion-live-container{opacity:0;z-index:1}.np_companion-live-container.np_companion-fade-in{opacity:1;z-index:3}:root{--base-border-radius:10px;--base-gap:20px}*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box}::-webkit-input-placeholder{color:hsla(0,0%,100%,.4)}::-moz-placeholder{color:hsla(0,0%,100%,.4)}:-ms-input-placeholder{color:hsla(0,0%,100%,.4)}:-moz-placeholder{color:hsla(0,0%,100%,.4)}a,input{outline:none}.np_companion-hide{display:none!important}.np_companion-align-middle{align-items:center;display:flex;justify-content:center}.np_companion-align-between{align-items:center;display:flex;justify-content:space-between}.np_companion-align-end{align-items:center;display:flex;justify-content:flex-end}.np_companion-align-start{align-items:flex-start;display:flex;justify-content:flex-start}.np_companion__d-none{display:none!important}#np_companion-sdk-root.np_companion-container-fixed{height:500px;position:fixed;transition:top .3s ease-in-out,right .3s ease-in-out,bottom .3s ease-in-out,left .3s ease-in-out,width .3s ease-in-out,height .3s ease-in-out;width:400px;z-index:9999}#np_companion-sdk-root.np_companion-container-fixed.np_companion-view-rectangle{height:400px;width:340px}#np_companion-sdk-root.np_companion-container-fixed.np_companion-view-silhouette{height:400px}#np_companion-sdk-root.np_companion-container-fixed.np_companion-view-round{height:360px;width:250px}#np_companion-sdk-root.np_companion-container-fixed.np_companion-view-round.np_companion_disclaimer-disabled{height:320px!important}#np_companion-sdk-root.np_companion-container-fixed.np_companion-bottom-right{bottom:0;right:0}#np_companion-sdk-root.np_companion-container-fixed.np_companion-bottom-left{bottom:0;left:0}#np_companion-sdk-root.np_companion-container-fixed.np_companion-bottom-center{bottom:0;left:50%;transform:translateX(-50%)}#np_companion-sdk-root.np_companion-container-fixed.np_companion-top-right{right:0;top:0}#np_companion-sdk-root.np_companion-container-fixed.np_companion-top-left{left:0;top:0}#np_companion-sdk-root.np_companion-container-fixed.np_companion-top-center{left:50%;top:0;transform:translateX(-50%)}#np_companion-sdk-root.np_companion-container-fixed.np_companion-center{left:50%;top:50%;transform:translate(-50%,-50%)}#np_companion-sdk-root.np_companion-container-inline{aspect-ratio:4/5;height:100%;position:relative;width:100%}#np_companion-sdk-root.np_companion-container-inline.np_companion-view-round{aspect-ratio:1/1;container-type:size}.np_companion-avatar{height:100%;position:relative;width:100%}#np_companion-avatar-container{background:transparent;border-radius:var(--base-border-radius);height:100%;overflow:hidden;position:absolute;width:100%}#np_companion-avatar-container:not(.np_companion-view-silhouette){box-shadow:0 0 #0000,0 0 #0000,0 0 #0000,0 0 #0000,0 10px 15px -3px color-mix(in oklab,#00000080 100%,transparent),0 4px 6px -4px color-mix(in oklab,#00000080 100%,transparent)}.np_companion-avatar.np_companion-bg-transparent #np_companion-avatar-container.np_companion-view-silhouette{filter:drop-shadow(0 10px 15px rgba(0,0,0,.5)) drop-shadow(0 4px 6px rgba(0,0,0,.5))}.np_companion-container-fixed #np_companion-avatar-container.np_companion-view-round{border-radius:50%;height:212px;left:50%;transform:translate(-50%,12%);width:212px}.np_companion-container-fixed #np_companion-avatar-container.np_companion-view-rectangle,.np_companion-container-fixed #np_companion-avatar-container.np_companion-view-silhouette{left:50%;transform:translateX(-50%)}.np_companion-container-inline #np_companion-avatar-container.np_companion-view-round{border-radius:50%;height:min(100cqw,calc(100cqh - var(--np-controls-band, 88px)));left:50%;top:0;transform:translateX(-50%);width:min(100cqw,calc(100cqh - var(--np-controls-band, 88px)))}.np_companion-avatar-video{height:100%;inset:0;object-fit:cover;pointer-events:none;position:absolute;transition:opacity .3s ease-in-out;width:100%;z-index:10}.np_companion-avatar-video.no-opacity{opacity:0}.np_companion-avatar-canvas{background:transparent;height:100%;object-fit:cover;position:relative;width:100%;z-index:1}#np_companion-avatar-container.np_companion-loader-pulse:after,.np_companion-container-fixed #np_companion-avatar-container.np_companion-loader-pulse:after{animation:pulse 1.5s ease-in-out infinite;background-color:var(--np-loader-bg-color);border-radius:var(--base-border-radius);content:"";display:block;height:100%;left:0;opacity:.6;overflow:hidden;position:absolute;top:0;width:100%}.np_companion-avatar.np_companion-bg-transparent #np_companion-avatar-container:after{display:none}.np_companion-avatar:has(#np_companion-avatar-container.np_companion-view-round):after{display:none}#np_companion-avatar-container.np_companion-loader-pulse.loader-container.np_companion-view-round:after{animation:pulse 1.5s ease-in-out infinite;background-color:var(--np-loader-bg-color);border-radius:50%;content:"";display:block;height:100%;left:0;opacity:.6;overflow:hidden;position:absolute;top:0;width:100%}.np_companion-avatar.np_companion-bg-transparent #np_companion-avatar-container.np_companion-view-round:after{display:none}@keyframes pulse{0%,to{opacity:.5}50%{opacity:.2}}.np_companion-disclaimer-text{-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;color:#fff;font-size:10px;line-height:12px;margin:auto auto 16px;max-width:940px;opacity:.7;overflow:hidden;padding:0 16px;text-align:center;z-index:12}.np_companion-disclaimer-text a{color:#fff}.np_companion-disclaimer-text a:hover{color:#fff;text-decoration:underline}.np_companion-disclaimer-text a:focus,.np_companion-disclaimer-text a:visited{color:#fff}.np_companion-action-btn-container-wrapper{pointer-events:auto;width:100%}.np_companion-avatar-control-container{backdrop-filter:blur(19px);background:none;background-color:rgba(52,51,51,.6);border-radius:16px;bottom:0;box-shadow:0 4px 4px 0 rgba(0,0,0,.25),2px 2px 8px 0 rgba(0,0,0,.12);left:50%;max-width:90%;min-width:210px;opacity:1;position:absolute;transform:translateX(-50%);transition:opacity .4s cubic-bezier(.4,0,.2,1);z-index:99}.np_companion-avatar-control-container.np_companion-action-btn-container-collapse{opacity:0}.np_companion-avatar-control-container:not(.np_companion-avatar-control-view-round){bottom:16px}.np_companion-avatar:not(.np_companion-bg-transparent) .np_companion-loader-spinner:not(.np_companion-view-silhouette){backdrop-filter:blur(19px);background-color:color-mix(in oklab,var(--np-loader-bg-color) 100%,transparent)!important;filter:none!important;height:100%;width:100%}.np_companion-loader-spinner .loader-container{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.np_companion-loader-spinner .loader-container .loader{--_m:conic-gradient(#0000 10%,#000),linear-gradient(#000 0 0) content-box;animation:l3 1s linear infinite;aspect-ratio:1;background:var(--np-loader-color);border-radius:50%;-webkit-mask:var(--_m);mask:var(--_m);-webkit-mask-composite:source-out;mask-composite:subtract;padding:4px;width:24px}@keyframes l3{to{transform:rotate(1turn)}}.np_companion-loader-spinner.np_companion-view-round .loader-container .loader{padding:4px;width:212px}.np_companion-bg-transparent .np_companion-loader-spinner .loader-container{display:none}.np_companion-controls-container{align-items:center;background:hsla(0,0%,100%,.3);border:.5px solid #fff;border-radius:12px;bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,.267),0 2px 5px rgba(0,0,0,.267),0 3px 8px rgba(0,0,0,.267);display:flex;gap:8px;left:50%;max-width:360px;pointer-events:auto;position:absolute;transform:translateX(-50%);transform-origin:bottom center;transition:width .3s ease-in-out;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:auto;z-index:99}.np_companion-controls-container.collapsed{gap:0}.np_companion-time-running{color:#fff}.np_companion-controls-time-action-btn-container{align-items:center;background:hsla(0,0%,100%,.1);border-radius:8px;display:flex;filter:grayscale(1);flex-shrink:0;gap:8px;height:32px;padding-left:8px;padding-right:8px;transition:filter .3s ease}.np_companion-controls-mic-control,.np_companion-controls-stop-control{cursor:pointer;display:flex;margin:0 5px;overflow:hidden;transition:opacity .3s ease-in-out,width .3s ease-in-out;width:47px}.np_companion-controls-mic-control.visible,.np_companion-controls-stop-control.visible{opacity:1}.np_companion-controls-mic-control.visible{width:1em}.np_companion-controls-stop-control.visible{width:1.5em}.np_companion-controls-mic-control.hidden,.np_companion-controls-stop-control.hidden{margin:0;opacity:0;padding:0;pointer-events:none;width:0}.np_companion-controls-mic-control:hover svg path{fill:red}.np_companion-controls-stop-control{display:flex;margin:0 5px 0 0}.np_companion-controls-stop-control:hover svg .stop-circle{fill:red}.np_companion-controls-stop-control:hover svg .stop-square{fill:#fff}.np_companion-mic-dropdown{background:#2b2c32;border-radius:4px;border-radius:6px;bottom:35px;box-shadow:0 10px 15px -3px rgba(0,0,0,.2),0 4px 6px -4px rgba(0,0,0,.2),0 0 0 1px #46464d;color:#fff;font-size:14px;list-style-type:none;margin:0;max-height:200px;min-width:300px;overflow-y:auto;overflow:auto;padding:5px;pointer-events:auto;position:absolute;width:auto;z-index:1000}.np_companion-mic-dropdown li{cursor:pointer;padding:8px 12px}.np_companion-mic-dropdown li:hover{background-color:#000}.np_companion-action-btn-container-container{background:transparent;border-radius:80px;gap:8px;padding:16px 16px 12px;transition:all .3s ease-in-out}.np_companion-action-btn-container-container.np_companion_disclaimer-disabled{padding-bottom:16px}.np_companion-action-btn-container-container .np_companion-chat-question-bar-container{border-radius:16px;gap:8px}.np_companion-action-btn-container-container .np_companion-action-btn-container-left{transition:all .3s ease-in-out;width:40px}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon{border-radius:80px;cursor:pointer;height:40px;transition:all .3s ease-in-out;width:40px}.np_companion-dark-grey-bg{background:linear-gradient(0deg,#5e5e5e14 0 100%),#ffffff0f}.np_companion-grey-bg{background:linear-gradient(0deg,rgba(94,94,94,.07),rgba(94,94,94,.07)),hsla(0,0%,100%,.04)}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon svg{height:auto;width:22px}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon.muted svg.np_companion-mic-muted,.np_companion-action-btn-container-container .np_companion-action-btn-container-icon:not(.muted) svg.np_companion-mic-unmuted{display:flex}.np_companion-action-btn-container-container .np_companion-action-btn-container-right{gap:8px;transition:all .3s ease-in-out}.np_companion-action-btn-container-container .np_companion-action-btn-container-left .np_companion-action-btn-container-icon:hover,.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon.active,.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:hover{background-color:#fff!important}.np_companion-action-btn-container-container .np_companion-action-btn-container-left .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color):hover svg path{fill:#000}.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon.np_companion-action-btn-container-call-end svg{height:auto;width:16px}.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color).np_companion-action-btn-container-call-end:hover svg path{stroke:#000}.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color).active svg path,.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color).np_companion-action-btn-container-toggle:hover svg path,.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color):hover svg path{fill:#000}.np_companion-mobile-mode{bottom:15px;scale:.7}.np_companion-mobile-mode .np_companion-action-btn-container-left{opacity:1;width:40px}.np_companion-mobile-mode .np_companion-action-btn-container-right{opacity:1}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon.np_companion-talking{background:rgba(52,199,89,.25);box-shadow:0 0 0 2px rgba(52,199,89,.4);transition:background .2s ease,box-shadow .2s ease}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon.np_companion-talking svg path{fill:#34c759}.np_companion-inactive-overlay{align-items:center;bottom:0;display:flex;justify-content:center;left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:9999}.np_companion-inactive-dialog{backdrop-filter:blur(19px);background:hsla(0,0%,50%,.3);background-blend-mode:luminosity;border-radius:16px;max-width:500px;overflow:hidden;padding:24px 16px;position:relative;text-align:center;width:100%}@supports (-moz-appearance:none){.np_companion-inactive-dialog{background:rgba(32,32,34,.88);background-blend-mode:normal}}.np_companion-inactive-dialog:before{background:linear-gradient(180deg,hsla(0,0%,100%,.4),hsla(0,0%,100%,0),hsla(0,0%,100%,0),hsla(0,0%,100%,.1));border-radius:16px;content:"";inset:0;-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:destination-out;mask-composite:exclude;padding:1.473px;pointer-events:none;position:absolute}.np_companion-inactive-message{color:#fff;font-size:16px;line-height:1.5;margin-bottom:24px;margin-top:0}.np_companion-inactive-timer{display:flex;justify-content:center;margin:24px 0;pointer-events:none}.np_companion-timer-circle{align-items:center;display:flex;height:80px;justify-content:center;position:relative;width:80px}.np_companion-timer-circle svg{height:100%;transform:rotate(-90deg);width:100%}.np_companion-timer-circle path{transition:stroke-dasharray .3s ease}.np_companion-timer-number{color:#fff;font-size:24px;font-weight:500;position:absolute}.np_companion-inactive-buttons{display:flex;gap:12px;justify-content:center}.np_companion-inactive-buttons button{border:none;border-radius:8px;cursor:pointer;font-size:14px;font-weight:500;padding:12px 24px;transition:background-color .2s ease}.np_companion-end-button{background:hsla(0,0%,100%,.1);color:#fff}.np_companion-end-button:hover{background:hsla(0,0%,100%,.2)}.np_companion-continue-button{background:#3c19cd;color:#fff}.np_companion-continue-button:hover{background:#2f13a0}.np_companion-mic-permission{--np-mic-permission-accent:#be369d;--np-mic-permission-accent-hover:#a62d88;--np-mic-permission-card-bg:hsla(0,0%,4%,.8);--np-mic-permission-card-radius:27px;align-items:flex-end;display:flex;inset:0;justify-content:center;padding:8px;pointer-events:auto;position:absolute;z-index:9999}.np_companion-mic-permission-card{backdrop-filter:blur(19px);background:var(--np-mic-permission-card-bg);border-radius:var(--np-mic-permission-card-radius);box-sizing:border-box;max-width:386px;padding:15px;text-align:center;width:100%}.np_companion-mic-permission-pagination{align-items:center;display:flex;gap:4px;justify-content:center;margin-bottom:8px}.np_companion-mic-permission-pagination[hidden]{display:none}.np_companion-mic-permission-dot{background:hsla(0,0%,100%,.4);border-radius:2px;height:4px;transition:width .2s ease,background .2s ease;width:4px}.np_companion-mic-permission-dot--active{background:#fff;width:16px}.np_companion-mic-permission-heading{color:#fff;font-family:inherit;font-size:18px;font-style:normal;font-weight:700;letter-spacing:-.18px;line-height:21.6px;margin:0 0 4px;text-transform:none}.np_companion-mic-permission-subheading{color:hsla(0,0%,100%,.5);font-family:inherit;font-size:14px;font-style:normal;font-weight:400;letter-spacing:-.28px;line-height:19.6px;margin:0 0 20px;text-transform:none}.np_companion-mic-permission-em--semibold{font-weight:600}.np_companion-mic-permission-em--bold{font-weight:700}.np_companion-mic-permission-actions{align-items:stretch;display:flex;flex-direction:column;gap:8px}.np_companion-mic-permission-actions button{border:0;color:#fff;cursor:pointer;font-family:inherit;font-style:normal;font-weight:600;text-transform:none;transition:background .2s ease}.np_companion-mic-permission-actions button:disabled{cursor:progress;opacity:.6}.np_companion-mic-permission-actions button:focus-visible{outline:2px solid #fff;outline-offset:2px}.np_companion-mic-permission-primary{background:var(--np-mic-permission-accent);border-radius:999px;font-size:15px;letter-spacing:-.15px;line-height:22px;min-height:48px;padding:7px 15px}.np_companion-mic-permission-primary:hover:not(:disabled){background:var(--np-mic-permission-accent-hover)}.np_companion-mic-permission-secondary{background:transparent;font-size:14px;letter-spacing:-.14px;line-height:19.6px;min-height:32px;padding:4px}.np_companion-mic-permission-secondary:hover:not(:disabled){color:hsla(0,0%,100%,.8)}.np_companion-mic-permission-live{clip:rect(0,0,0,0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.np_site-frame{backface-visibility:hidden;border:0;height:100vh;height:100dvh;inset:0;position:fixed;transform:translateZ(0);width:100%;z-index:100000000}.np_site-progress{animation:np_site-progress-grow 6s cubic-bezier(.15,.75,.25,1) forwards;background:var(--np-persist-progress-color,#be369d);border-end-end-radius:3px;box-shadow:0 0 10px var(--np-persist-progress-color,#be369d);height:var(--np-persist-progress-height,3px);left:0;pointer-events:none;position:fixed;top:0;width:0;z-index:100000002}@keyframes np_site-progress-grow{0%{width:0}to{width:92%}}.np_site-progress--done{animation:none;opacity:0;transition:width .14s ease-out,opacity .24s ease-out .12s;width:100%}@media (prefers-reduced-motion:reduce){.np_site-progress{animation:none;width:92%}.np_site-progress--done{transition:opacity .14s linear}}
1
+ .np_companion-live-container,.np_companion-preview-container{height:100%;margin:0;padding:0;position:absolute;transition:opacity .3s ease-in-out;width:100%}.np_companion-preview-container{opacity:1;z-index:2}.np_companion-preview-container.np_companion-fade-out{opacity:0;pointer-events:none;transition:opacity .5s ease-out}.np_companion-live-container{opacity:0;z-index:1}.np_companion-live-container.np_companion-fade-in{opacity:1;z-index:3}:root{--base-border-radius:10px;--base-gap:20px}*,:after,:before{-webkit-box-sizing:border-box;box-sizing:border-box}::-webkit-input-placeholder{color:hsla(0,0%,100%,.4)}::-moz-placeholder{color:hsla(0,0%,100%,.4)}:-ms-input-placeholder{color:hsla(0,0%,100%,.4)}:-moz-placeholder{color:hsla(0,0%,100%,.4)}a,input{outline:none}.np_companion-hide{display:none!important}.np_companion-align-middle{align-items:center;display:flex;justify-content:center}.np_companion-align-between{align-items:center;display:flex;justify-content:space-between}.np_companion-align-end{align-items:center;display:flex;justify-content:flex-end}.np_companion-align-start{align-items:flex-start;display:flex;justify-content:flex-start}.np_companion__d-none{display:none!important}#np_companion-sdk-root.np_companion-container-fixed{height:500px;position:fixed;transition:top .3s ease-in-out,right .3s ease-in-out,bottom .3s ease-in-out,left .3s ease-in-out,width .3s ease-in-out,height .3s ease-in-out;width:400px;z-index:9999}#np_companion-sdk-root.np_companion-container-fixed.np_companion-view-rectangle{height:400px;width:340px}#np_companion-sdk-root.np_companion-container-fixed.np_companion-view-silhouette{height:400px}#np_companion-sdk-root.np_companion-container-fixed.np_companion-view-round{height:360px;width:250px}#np_companion-sdk-root.np_companion-container-fixed.np_companion-view-round.np_companion_disclaimer-disabled{height:320px!important}#np_companion-sdk-root.np_companion-container-fixed.np_companion-bottom-right{bottom:0;right:0}#np_companion-sdk-root.np_companion-container-fixed.np_companion-bottom-left{bottom:0;left:0}#np_companion-sdk-root.np_companion-container-fixed.np_companion-bottom-center{bottom:0;left:50%;transform:translateX(-50%)}#np_companion-sdk-root.np_companion-container-fixed.np_companion-top-right{right:0;top:0}#np_companion-sdk-root.np_companion-container-fixed.np_companion-top-left{left:0;top:0}#np_companion-sdk-root.np_companion-container-fixed.np_companion-top-center{left:50%;top:0;transform:translateX(-50%)}#np_companion-sdk-root.np_companion-container-fixed.np_companion-center{left:50%;top:50%;transform:translate(-50%,-50%)}#np_companion-sdk-root.np_companion-container-inline{aspect-ratio:4/5;height:100%;position:relative;width:100%}#np_companion-sdk-root.np_companion-container-inline.np_companion-view-round{aspect-ratio:1/1;container-type:size}.np_companion-avatar{height:100%;position:relative;width:100%}#np_companion-avatar-container{background:transparent;border-radius:var(--base-border-radius);height:100%;overflow:hidden;position:absolute;width:100%}#np_companion-avatar-container:not(.np_companion-view-silhouette){box-shadow:0 0 #0000,0 0 #0000,0 0 #0000,0 0 #0000,0 10px 15px -3px color-mix(in oklab,#00000080 100%,transparent),0 4px 6px -4px color-mix(in oklab,#00000080 100%,transparent)}.np_companion-avatar.np_companion-bg-transparent #np_companion-avatar-container.np_companion-view-silhouette{filter:drop-shadow(0 10px 15px rgba(0,0,0,.5)) drop-shadow(0 4px 6px rgba(0,0,0,.5))}.np_companion-container-fixed #np_companion-avatar-container.np_companion-view-round{border-radius:50%;height:212px;left:50%;transform:translate(-50%,12%);width:212px}.np_companion-container-fixed #np_companion-avatar-container.np_companion-view-rectangle,.np_companion-container-fixed #np_companion-avatar-container.np_companion-view-silhouette{left:50%;transform:translateX(-50%)}.np_companion-container-inline #np_companion-avatar-container.np_companion-view-round{border-radius:50%;height:min(100cqw,calc(100cqh - var(--np-controls-band, 88px)));left:50%;top:0;transform:translateX(-50%);width:min(100cqw,calc(100cqh - var(--np-controls-band, 88px)))}.np_companion-avatar-video{height:100%;inset:0;object-fit:cover;pointer-events:none;position:absolute;transition:opacity .3s ease-in-out;width:100%;z-index:10}.np_companion-avatar-video.no-opacity{opacity:0}.np_companion-avatar-canvas{background:transparent;height:100%;object-fit:cover;position:relative;width:100%;z-index:1}#np_companion-avatar-container.np_companion-loader-pulse:after,.np_companion-container-fixed #np_companion-avatar-container.np_companion-loader-pulse:after{animation:pulse 1.5s ease-in-out infinite;background-color:var(--np-loader-bg-color);border-radius:var(--base-border-radius);content:"";display:block;height:100%;left:0;opacity:.6;overflow:hidden;position:absolute;top:0;width:100%}.np_companion-avatar.np_companion-bg-transparent #np_companion-avatar-container:after{display:none}.np_companion-avatar:has(#np_companion-avatar-container.np_companion-view-round):after{display:none}#np_companion-avatar-container.np_companion-loader-pulse.loader-container.np_companion-view-round:after{animation:pulse 1.5s ease-in-out infinite;background-color:var(--np-loader-bg-color);border-radius:50%;content:"";display:block;height:100%;left:0;opacity:.6;overflow:hidden;position:absolute;top:0;width:100%}.np_companion-avatar.np_companion-bg-transparent #np_companion-avatar-container.np_companion-view-round:after{display:none}@keyframes pulse{0%,to{opacity:.5}50%{opacity:.2}}.np_companion-disclaimer-text{-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;color:#fff;font-size:10px;line-height:12px;margin:auto auto 16px;max-width:940px;opacity:.7;overflow:hidden;padding:0 16px;text-align:center;z-index:12}.np_companion-disclaimer-text a{color:#fff}.np_companion-disclaimer-text a:hover{color:#fff;text-decoration:underline}.np_companion-disclaimer-text a:focus,.np_companion-disclaimer-text a:visited{color:#fff}.np_companion-action-btn-container-wrapper{pointer-events:auto;width:100%}.np_companion-avatar-control-container{backdrop-filter:blur(19px);background:none;background-color:rgba(52,51,51,.6);border-radius:16px;bottom:0;box-shadow:0 4px 4px 0 rgba(0,0,0,.25),2px 2px 8px 0 rgba(0,0,0,.12);left:50%;max-width:90%;min-width:210px;opacity:1;position:absolute;transform:translateX(-50%);transition:opacity .4s cubic-bezier(.4,0,.2,1);z-index:99}.np_companion-avatar-control-container.np_companion-action-btn-container-collapse{opacity:0}.np_companion-avatar-control-container:not(.np_companion-avatar-control-view-round){bottom:16px}.np_companion-avatar:not(.np_companion-bg-transparent) .np_companion-loader-spinner:not(.np_companion-view-silhouette){backdrop-filter:blur(19px);background-color:color-mix(in oklab,var(--np-loader-bg-color) 100%,transparent)!important;filter:none!important;height:100%;width:100%}.np_companion-loader-spinner .loader-container{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}.np_companion-loader-spinner .loader-container .loader{--_m:conic-gradient(#0000 10%,#000),linear-gradient(#000 0 0) content-box;animation:l3 1s linear infinite;aspect-ratio:1;background:var(--np-loader-color);border-radius:50%;-webkit-mask:var(--_m);mask:var(--_m);-webkit-mask-composite:source-out;mask-composite:subtract;padding:4px;width:24px}@keyframes l3{to{transform:rotate(1turn)}}.np_companion-loader-spinner.np_companion-view-round .loader-container .loader{padding:4px;width:212px}.np_companion-bg-transparent .np_companion-loader-spinner .loader-container{display:none}.np_companion-controls-container{align-items:center;background:hsla(0,0%,100%,.3);border:.5px solid #fff;border-radius:12px;bottom:20px;box-shadow:0 1px 1px rgba(0,0,0,.267),0 2px 5px rgba(0,0,0,.267),0 3px 8px rgba(0,0,0,.267);display:flex;gap:8px;left:50%;max-width:360px;pointer-events:auto;position:absolute;transform:translateX(-50%);transform-origin:bottom center;transition:width .3s ease-in-out;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:auto;z-index:99}.np_companion-controls-container.collapsed{gap:0}.np_companion-time-running{color:#fff}.np_companion-controls-time-action-btn-container{align-items:center;background:hsla(0,0%,100%,.1);border-radius:8px;display:flex;filter:grayscale(1);flex-shrink:0;gap:8px;height:32px;padding-left:8px;padding-right:8px;transition:filter .3s ease}.np_companion-controls-mic-control,.np_companion-controls-stop-control{cursor:pointer;display:flex;margin:0 5px;overflow:hidden;transition:opacity .3s ease-in-out,width .3s ease-in-out;width:47px}.np_companion-controls-mic-control.visible,.np_companion-controls-stop-control.visible{opacity:1}.np_companion-controls-mic-control.visible{width:1em}.np_companion-controls-stop-control.visible{width:1.5em}.np_companion-controls-mic-control.hidden,.np_companion-controls-stop-control.hidden{margin:0;opacity:0;padding:0;pointer-events:none;width:0}.np_companion-controls-mic-control:hover svg path{fill:red}.np_companion-controls-stop-control{display:flex;margin:0 5px 0 0}.np_companion-controls-stop-control:hover svg .stop-circle{fill:red}.np_companion-controls-stop-control:hover svg .stop-square{fill:#fff}.np_companion-mic-dropdown{background:#2b2c32;border-radius:4px;border-radius:6px;bottom:35px;box-shadow:0 10px 15px -3px rgba(0,0,0,.2),0 4px 6px -4px rgba(0,0,0,.2),0 0 0 1px #46464d;color:#fff;font-size:14px;list-style-type:none;margin:0;max-height:200px;min-width:300px;overflow-y:auto;overflow:auto;padding:5px;pointer-events:auto;position:absolute;width:auto;z-index:1000}.np_companion-mic-dropdown li{cursor:pointer;padding:8px 12px}.np_companion-mic-dropdown li:hover{background-color:#000}.np_companion-action-btn-container-container{background:transparent;border-radius:80px;gap:8px;padding:16px 16px 12px;transition:all .3s ease-in-out}.np_companion-action-btn-container-container.np_companion_disclaimer-disabled{padding-bottom:16px}.np_companion-action-btn-container-container .np_companion-chat-question-bar-container{border-radius:16px;gap:8px}.np_companion-action-btn-container-container .np_companion-action-btn-container-left{transition:all .3s ease-in-out;width:40px}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon{border-radius:80px;cursor:pointer;height:40px;transition:all .3s ease-in-out;width:40px}.np_companion-dark-grey-bg{background:linear-gradient(0deg,#5e5e5e14 0 100%),#ffffff0f}.np_companion-grey-bg{background:linear-gradient(0deg,rgba(94,94,94,.07),rgba(94,94,94,.07)),hsla(0,0%,100%,.04)}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon svg{height:auto;width:22px}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon.muted svg.np_companion-mic-muted,.np_companion-action-btn-container-container .np_companion-action-btn-container-icon:not(.muted) svg.np_companion-mic-unmuted{display:flex}.np_companion-action-btn-container-container .np_companion-action-btn-container-right{gap:8px;transition:all .3s ease-in-out}.np_companion-action-btn-container-container .np_companion-action-btn-container-left .np_companion-action-btn-container-icon:hover,.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon.active,.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:hover{background-color:#fff!important}.np_companion-action-btn-container-container .np_companion-action-btn-container-left .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color):hover svg path{fill:#000}.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon.np_companion-action-btn-container-call-end svg{height:auto;width:16px}.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color).np_companion-action-btn-container-call-end:hover svg path{stroke:#000}.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color).active svg path,.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color).np_companion-action-btn-container-toggle:hover svg path,.np_companion-action-btn-container-container .np_companion-action-btn-container-right .np_companion-action-btn-container-icon:not(.np_companion-persist-fill-color):hover svg path{fill:#000}.np_companion-mobile-mode{bottom:15px;scale:.7}.np_companion-mobile-mode .np_companion-action-btn-container-left{opacity:1;width:40px}.np_companion-mobile-mode .np_companion-action-btn-container-right{opacity:1}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon.np_companion-talking{background:rgba(52,199,89,.25);box-shadow:0 0 0 2px rgba(52,199,89,.4);transition:background .2s ease,box-shadow .2s ease}.np_companion-action-btn-container-container .np_companion-action-btn-container-icon.np_companion-talking svg path{fill:#34c759}.np_companion-inactive-overlay{align-items:center;bottom:0;display:flex;justify-content:center;left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:9999}.np_companion-inactive-dialog{backdrop-filter:blur(19px);background:hsla(0,0%,50%,.3);background-blend-mode:luminosity;border-radius:16px;max-width:500px;overflow:hidden;padding:24px 16px;position:relative;text-align:center;width:100%}@supports (-moz-appearance:none){.np_companion-inactive-dialog{background:rgba(32,32,34,.88);background-blend-mode:normal}}.np_companion-inactive-dialog:before{background:linear-gradient(180deg,hsla(0,0%,100%,.4),hsla(0,0%,100%,0),hsla(0,0%,100%,0),hsla(0,0%,100%,.1));border-radius:16px;content:"";inset:0;-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:destination-out;mask-composite:exclude;padding:1.473px;pointer-events:none;position:absolute}.np_companion-inactive-message{color:#fff;font-size:16px;line-height:1.5;margin-bottom:24px;margin-top:0}.np_companion-inactive-timer{display:flex;justify-content:center;margin:24px 0;pointer-events:none}.np_companion-timer-circle{align-items:center;display:flex;height:80px;justify-content:center;position:relative;width:80px}.np_companion-timer-circle svg{height:100%;transform:rotate(-90deg);width:100%}.np_companion-timer-circle path{transition:stroke-dasharray .3s ease}.np_companion-timer-number{color:#fff;font-size:24px;font-weight:500;position:absolute}.np_companion-inactive-buttons{display:flex;gap:12px;justify-content:center}.np_companion-inactive-buttons button{border:none;border-radius:8px;cursor:pointer;font-size:14px;font-weight:500;padding:12px 24px;transition:background-color .2s ease}.np_companion-end-button{background:hsla(0,0%,100%,.1);color:#fff}.np_companion-end-button:hover{background:hsla(0,0%,100%,.2)}.np_companion-continue-button{background:#3c19cd;color:#fff}.np_companion-continue-button:hover{background:#2f13a0}#np_companion-sdk-root[data-np-state=active] .np_button,#np_companion-sdk-root[data-np-state=connecting] .np_button,#np_companion-sdk-root[data-np-state=idle] .np_companion-live-container{display:none}#np_companion-sdk-root[data-np-state=idle].np_companion-container-fixed{height:auto;width:auto}.np_button{align-items:center;background:#16c784;border:0;border-radius:999px;box-shadow:0 10px 30px rgba(0,0,0,.45);color:#000;cursor:pointer;display:inline-flex;font:inherit;font-size:14px;font-weight:700;gap:10px;line-height:1;padding:13px 20px}.np_button:focus-visible{outline:2px solid #063;outline-offset:2px}.np_button--pending{cursor:progress;opacity:.7}.np_button-avatar{border-radius:50%;flex:none;height:24px;object-fit:cover;width:24px}.np_button-label{white-space:nowrap}.np_button-dot{background:#063;border-radius:50%;box-shadow:0 0 0 4px rgba(22,199,132,.22);flex:none;height:9px;width:9px}.np_button--pending .np_button-dot{animation:np_button-pulse 1s ease-in-out infinite}@keyframes np_button-pulse{0%,to{opacity:1}50%{opacity:.3}}@media (prefers-reduced-motion:reduce){.np_button--pending .np_button-dot{animation:none}}.np_button-description{display:block;font-size:12px;line-height:1.35;margin-top:2px;opacity:.72}.np_button-text{align-items:flex-start;display:flex;flex-direction:column;min-width:0}.np_site-frame{backface-visibility:hidden;border:0;height:100vh;height:100dvh;inset:0;position:fixed;transform:translateZ(0);width:100%;z-index:100000000}.np_site-progress{animation:np_site-progress-grow 6s cubic-bezier(.15,.75,.25,1) forwards;background:var(--np-persist-progress-color,#be369d);border-end-end-radius:3px;box-shadow:0 0 10px var(--np-persist-progress-color,#be369d);height:var(--np-persist-progress-height,3px);left:0;pointer-events:none;position:fixed;top:0;width:0;z-index:100000002}@keyframes np_site-progress-grow{0%{width:0}to{width:92%}}.np_site-progress--done{animation:none;opacity:0;transition:width .14s ease-out,opacity .24s ease-out .12s;width:100%}@media (prefers-reduced-motion:reduce){.np_site-progress{animation:none;width:92%}.np_site-progress--done{transition:opacity .14s linear}}.np_companion-mic-permission{--np-mic-permission-accent:#be369d;--np-mic-permission-accent-hover:#a62d88;--np-mic-permission-card-bg:hsla(0,0%,4%,.8);--np-mic-permission-card-radius:27px;align-items:flex-end;display:flex;inset:0;justify-content:center;padding:8px;pointer-events:auto;position:absolute;z-index:9999}.np_companion-mic-permission-card{backdrop-filter:blur(19px);background:var(--np-mic-permission-card-bg);border-radius:var(--np-mic-permission-card-radius);box-sizing:border-box;max-width:386px;padding:15px;text-align:center;width:100%}.np_companion-mic-permission-pagination{align-items:center;display:flex;gap:4px;justify-content:center;margin-bottom:8px}.np_companion-mic-permission-pagination[hidden]{display:none}.np_companion-mic-permission-dot{background:hsla(0,0%,100%,.4);border-radius:2px;height:4px;transition:width .2s ease,background .2s ease;width:4px}.np_companion-mic-permission-dot--active{background:#fff;width:16px}.np_companion-mic-permission-heading{color:#fff;font-family:inherit;font-size:18px;font-style:normal;font-weight:700;letter-spacing:-.18px;line-height:21.6px;margin:0 0 4px;text-transform:none}.np_companion-mic-permission-subheading{color:hsla(0,0%,100%,.5);font-family:inherit;font-size:14px;font-style:normal;font-weight:400;letter-spacing:-.28px;line-height:19.6px;margin:0 0 20px;text-transform:none}.np_companion-mic-permission-em--semibold{font-weight:600}.np_companion-mic-permission-em--bold{font-weight:700}.np_companion-mic-permission-actions{align-items:stretch;display:flex;flex-direction:column;gap:8px}.np_companion-mic-permission-actions button{border:0;color:#fff;cursor:pointer;font-family:inherit;font-style:normal;font-weight:600;text-transform:none;transition:background .2s ease}.np_companion-mic-permission-actions button:disabled{cursor:progress;opacity:.6}.np_companion-mic-permission-actions button:focus-visible{outline:2px solid #fff;outline-offset:2px}.np_companion-mic-permission-primary{background:var(--np-mic-permission-accent);border-radius:999px;font-size:15px;letter-spacing:-.15px;line-height:22px;min-height:48px;padding:7px 15px}.np_companion-mic-permission-primary:hover:not(:disabled){background:var(--np-mic-permission-accent-hover)}.np_companion-mic-permission-secondary{background:transparent;font-size:14px;letter-spacing:-.14px;line-height:19.6px;min-height:32px;padding:4px}.np_companion-mic-permission-secondary:hover:not(:disabled){color:hsla(0,0%,100%,.8)}.np_companion-mic-permission-live{clip:rect(0,0,0,0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}
package/lib/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { NapsterCompanionApiSDK, NapsterCompanionApiConfig, NapsterCompanionApiInstance } from "./types";
1
+ import type { NapsterCompanionApiSDK, NapsterCompanionApiConfig, NapsterCompanionApiInstance, NapsterCompanionApiController, ButtonConfig } from "./types";
2
2
  declare global {
3
3
  interface Window {
4
4
  napsterCompanionApiSDK: NapsterCompanionApiSDK;
@@ -12,6 +12,7 @@ declare class NapsterCompanionApiSdkVanilla implements NapsterCompanionApiSDK {
12
12
  private embed;
13
13
  private micGate;
14
14
  private getTokenFn;
15
+ private sessionEpoch;
15
16
  private webrtcController;
16
17
  private screenShareController;
17
18
  private faceTrackingController;
@@ -21,43 +22,137 @@ declare class NapsterCompanionApiSdkVanilla implements NapsterCompanionApiSDK {
21
22
  private pipController;
22
23
  private edgeMcpBridge;
23
24
  private persistCtl;
25
+ private onSessionFailedHandler;
26
+ private onSessionEndedHandler;
27
+ private buttonMode;
24
28
  private lastFaceDetected;
25
29
  private userMutedDuringFaceTracking;
26
30
  private faceTrackingLastDispatchedMute;
27
31
  private muteStoreUnsubscribe;
28
32
  static getInstance(): NapsterCompanionApiSdkVanilla;
29
33
  constructor();
34
+ /**
35
+ * Per-session setup that destroyInstance() tears down but only init() itself
36
+ * used to rebuild: the feature dispatch, the features-update callback, and the
37
+ * WebMCP bridge. Must run before renderApp() — the embed reads features from
38
+ * the store, and both the embed and a persisted iframe read `this.edgeMcpBridge`.
39
+ *
40
+ * A preserveRoot teardown (button mode's endSession/failToMintToken) clears all
41
+ * three even though it keeps the root, so this also runs from the button-mode
42
+ * restore path before every session after the first — otherwise a host's
43
+ * `features` config and WebMCP tool access would silently work only once.
44
+ *
45
+ * The feature dispatch and the features-update callback re-apply on every call —
46
+ * they are cheap and genuinely per-session. The WebMCP bridge is the one guarded
47
+ * block: see the comment at its construction for why it skips instead of
48
+ * rebuilding. Deliberately asymmetric — do not "tidy" the two to match.
49
+ */
50
+ private setupSessionServices;
51
+ /**
52
+ * Wrap the page for cross-navigation persistence, if configured. Must run
53
+ * AFTER renderApp() — it needs `this.rootEl`, which renderApp() creates on
54
+ * first use. Same rebuild requirement as setupSessionServices(): a
55
+ * preserveRoot teardown clears `persistCtl` too, so this also runs from the
56
+ * button-mode restore path before every session after the first.
57
+ *
58
+ * Idempotent, but by early-returning rather than rebuilding: there is no
59
+ * teardown between init() and the FIRST start(), so `this.persistCtl` is
60
+ * still the live, correctly-wrapped-and-armed controller init() built.
61
+ * Rebuilding it here (as setupSessionServices() does for the bridge) would
62
+ * wrap the page a SECOND time in a second iframe the first controller can't
63
+ * see, arm a second on-navigate interceptor, and drop the first controller
64
+ * from `this.persistCtl` with its `teardown()` never called.
65
+ */
66
+ private setupPersistence;
30
67
  private renderApp;
68
+ /**
69
+ * One session attempt: open a new epoch, settle the microphone, mint, connect.
70
+ * Errors propagate — button mode awaits this directly so a failed attempt
71
+ * (denied mic, failed mint) reaches its own caller instead of being reported
72
+ * here.
73
+ */
74
+ private runSessionAttempt;
75
+ /**
76
+ * Immediate mode's kickoff: the same attempt as runSessionAttempt(), but
77
+ * fire-and-forget — errors are reported via onError rather than thrown,
78
+ * since renderApp() has no caller to catch them.
79
+ */
80
+ private startSession;
31
81
  /**
32
82
  * Acquire the microphone, then connect. On failure the widget stays mounted and
33
83
  * hands over to the permission-recovery flow, which drives the retry.
34
84
  */
35
85
  private startSessionWhenMicAvailable;
36
86
  /**
37
- * Called once the microphone gate grants access. On the getToken path, mints a
38
- * fresh token now (so it is always minted as late as possible) and stores it
39
- * before connecting; on the deprecated string path the token is already in the
40
- * store, so this just connects same as today.
87
+ * Called once the microphone gate grants access: mint a fresh token (as late
88
+ * as possible, since tokens live ~5s) and connect with it.
89
+ *
90
+ * `epoch` is the session this mint belongs to. It is re-checked after the
91
+ * await because getToken() is the host's code and may take arbitrarily long —
92
+ * long enough for the host to destroy and re-init underneath us.
41
93
  */
42
94
  private mintTokenAndConnect;
43
95
  /**
44
96
  * getToken() failed (or returned nothing) after the mic gate already granted access and
45
97
  * the embed is mounted and waiting. Left alone the widget would sit in its connecting UI
46
98
  * forever — there is no later retry path once the mic gate has already dismissed itself.
47
- * Report the error, then fully tear down (revealOnly: no session was ever wrapped yet).
99
+ *
100
+ * Tear down BEFORE reporting: the obvious host recovery is to call init() again from
101
+ * inside onError, and that only works once isInitialized has been cleared. destroyInstance()
102
+ * already models this ordering for its own onDestroy callback.
103
+ *
104
+ * revealOnly rather than end(): under persistence the page is already wrapped by the time
105
+ * the gate settles, so a failed mint should reveal it in place rather than navigate.
106
+ *
107
+ * `preserveRoot` (== button mode) reports through the failure channel
108
+ * (onSessionFailedHandler) instead of onError directly. Unlike a thrown rejection,
109
+ * this reaches the controller whether or not anything is awaiting the attempt —
110
+ * covering both a click still in flight AND an unawaited "Check again" retry driven
111
+ * from inside the mic-recovery card.
48
112
  */
49
113
  private failToMintToken;
114
+ /**
115
+ * Hand the freshly minted token to the transport the config declared.
116
+ *
117
+ * Only webrtc exists today and normalizeConfig() has already rejected
118
+ * anything else, so this reads as a formality — but it is the branch point a
119
+ * second transport plugs into, and keeping it here means `transport` is a
120
+ * field the connect path actually consults rather than decoration.
121
+ */
122
+ private connectSessionTransport;
50
123
  private connectWebRTC;
51
124
  private handleScreenShareToggle;
52
125
  private initializeWebRTC;
53
126
  private destroyWebRTC;
54
127
  private destroyInstance;
55
- init(getToken: () => Promise<string>, partialConfig?: Partial<NapsterCompanionApiConfig>): Promise<NapsterCompanionApiInstance>;
128
+ /**
129
+ * Initialize the SDK in click-to-start mode: resolves with a controller, and
130
+ * nothing connects until `start()` or a click on the entry point.
131
+ *
132
+ * `getToken` is called by the SDK itself once the microphone permission has
133
+ * settled — on every attempt, including a "Check again" retry — so the
134
+ * connection token is always fresh when signaling starts. A pre-minted token
135
+ * string is not accepted: minting it before the mic prompt lets it go stale
136
+ * while the user is deciding.
137
+ */
138
+ init(getToken: () => Promise<string>, config: Partial<NapsterCompanionApiConfig> & {
139
+ button: ButtonConfig & {
140
+ enabled: true;
141
+ };
142
+ }): Promise<NapsterCompanionApiController>;
143
+ /**
144
+ * Initialize the SDK and connect immediately: resolves once the session is up.
145
+ *
146
+ * `getToken` is called by the SDK itself once the microphone permission has
147
+ * settled, so the connection token is always fresh when signaling starts. A
148
+ * pre-minted token string is not accepted.
149
+ */
150
+ init(getToken: () => Promise<string>, config?: Partial<NapsterCompanionApiConfig>): Promise<NapsterCompanionApiInstance>;
56
151
  private createInstanceMethods;
57
152
  }
58
153
  declare const NapsterCompanionApiSdk: NapsterCompanionApiSdkVanilla;
59
154
  export { NapsterCompanionApiSdk };
60
- export type { NapsterCompanionApiConfig, NapsterCompanionApiInstance, PersistenceOptions, CompanionFunction, FeatureConfig, StyleObject, FaceTrackingConfig, FaceTrackingData, FaceTrackingController, MouthLandmarks, } from "./types";
155
+ export type { NapsterCompanionApiConfig, NapsterCompanionApiInstance, NapsterCompanionApiController, PersistenceOptions, ButtonConfig, CompanionFunction, FeatureConfig, StyleObject, FaceTrackingConfig, FaceTrackingData, FaceTrackingController, MouthLandmarks, } from "./types";
61
156
  export { Position } from "./types";
62
157
  export { DataChannelMessageType } from "./constants/events";
63
158
  export { checkPiPSupport, resetPiPSupportCache } from "./utils/PiPManager";