@kasabeh/a3d-sdk 0.1.0
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/LICENSE +3 -0
- package/README.md +128 -0
- package/a3d/client.d.ts +56 -0
- package/a3d/index.d.ts +141 -0
- package/hooks/useBidRequest.d.ts +14 -0
- package/index.cjs +2 -0
- package/index.cjs.map +1 -0
- package/index.mjs +463 -0
- package/index.mjs.map +1 -0
- package/lib/openrtb.d.ts +36 -0
- package/package.json +64 -0
- package/types/openrtb.d.ts +156 -0
package/LICENSE
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# @kasabeh/a3d-sdk
|
|
2
|
+
|
|
3
|
+
Native 3D advertising SDK for Three.js / React Three Fiber.
|
|
4
|
+
|
|
5
|
+
Add native 3D ads to your Three.js or React Three Fiber scene in under 5
|
|
6
|
+
minutes. The SDK runs an OpenRTB 2.6 auction (`ext.threeds`), loads the
|
|
7
|
+
winning GLB creative, bills the impression via a single-use token when the
|
|
8
|
+
model actually renders, and tracks viewability aligned with the IAB/MRC
|
|
9
|
+
Intrinsic In-Game (IIG) 2.0 guidelines.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @kasabeh/a3d-sdk three @react-three/fiber @react-three/drei
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start (React Three Fiber)
|
|
18
|
+
|
|
19
|
+
```jsx
|
|
20
|
+
import { Canvas } from '@react-three/fiber'
|
|
21
|
+
import { A3DAds } from '@kasabeh/a3d-sdk'
|
|
22
|
+
|
|
23
|
+
function App() {
|
|
24
|
+
return (
|
|
25
|
+
<Canvas>
|
|
26
|
+
<MyScene />
|
|
27
|
+
<A3DAds
|
|
28
|
+
siteId="your-site-id"
|
|
29
|
+
endpoint="https://api.a3d.io"
|
|
30
|
+
placements={[
|
|
31
|
+
{
|
|
32
|
+
id: 'hero-product',
|
|
33
|
+
position: [0, 1, 0],
|
|
34
|
+
sceneType: 'showroom',
|
|
35
|
+
surfaceType: 'pedestal',
|
|
36
|
+
maxDimensions: { width: 0.5, height: 1.0, depth: 0.5 },
|
|
37
|
+
},
|
|
38
|
+
]}
|
|
39
|
+
/>
|
|
40
|
+
</Canvas>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Manual control (hook)
|
|
46
|
+
|
|
47
|
+
```jsx
|
|
48
|
+
import {
|
|
49
|
+
useA3DAds, A3DModel, A3DSponsoredTag, A3DViewability, openAdClick,
|
|
50
|
+
} from '@kasabeh/a3d-sdk'
|
|
51
|
+
|
|
52
|
+
function MyAdSlot({ placement }) {
|
|
53
|
+
const { resolvedAd, loading, modelUrl } = useA3DAds(placement, undefined, {
|
|
54
|
+
endpoint: 'https://api.a3d.io',
|
|
55
|
+
fallbackModelUrl: '/my-house-ad.glb',
|
|
56
|
+
})
|
|
57
|
+
if (loading || !modelUrl) return null
|
|
58
|
+
return (
|
|
59
|
+
<group position={placement.position}>
|
|
60
|
+
<A3DModel
|
|
61
|
+
url={modelUrl}
|
|
62
|
+
ad={resolvedAd}
|
|
63
|
+
onClick={resolvedAd ? () => openAdClick(resolvedAd) : undefined}
|
|
64
|
+
/>
|
|
65
|
+
<A3DViewability placement={placement} ad={resolvedAd} />
|
|
66
|
+
<A3DSponsoredTag offset={0.5} />
|
|
67
|
+
</group>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Vanilla JS (no React)
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
import { A3DClient } from '@kasabeh/a3d-sdk'
|
|
76
|
+
|
|
77
|
+
const a3d = new A3DClient({ siteId: 'your-site-id', endpoint: 'https://api.a3d.io' })
|
|
78
|
+
const ads = await a3d.requestBids(placements)
|
|
79
|
+
for (const [placementId, ad] of ads) {
|
|
80
|
+
const model = await a3d.loadModel(ad.modelUrl)
|
|
81
|
+
scene.add(model)
|
|
82
|
+
a3d.fireImpression(placementId, ad)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Exports
|
|
87
|
+
|
|
88
|
+
| Export | Kind | Description |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| `<A3DAds />` | component | Drop-in: bid, load, render, sponsored tag, billing + IIG viewability |
|
|
91
|
+
| `useA3DAds(placement, site?, opts?)` | hook | Auction for one placement; returns `{ resolvedAd, loading, modelUrl, refetch }` |
|
|
92
|
+
| `<A3DModel />` | component | Load + auto-fit a GLB; bills on render when given `ad` |
|
|
93
|
+
| `<A3DSponsoredTag />` | component | Billboarded "Sponsored" badge |
|
|
94
|
+
| `<A3DViewability />` | component | IIG viewability + engagement tracker |
|
|
95
|
+
| `openAdClick(ad)` | function | Tracked click through the ad server redirect |
|
|
96
|
+
| `fireBillableImpression(ad, endpoint?)` | function | Manually bill a rendered creative |
|
|
97
|
+
| `fireEvent(payload)` | function | Record a custom event (rotate, zoom, hover) |
|
|
98
|
+
| `A3DClient` | class | Imperative client for plain Three.js |
|
|
99
|
+
|
|
100
|
+
## Placement shape
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
{
|
|
104
|
+
id: string
|
|
105
|
+
position: [number, number, number]
|
|
106
|
+
sceneType: string // kitchen | office | gallery | showroom | ...
|
|
107
|
+
surfaceType: string // table | shelf | pedestal | floor | ...
|
|
108
|
+
maxDimensions: { width: number; height: number; depth: number } // meters
|
|
109
|
+
tags?: string[]
|
|
110
|
+
interaction?: string[] // view | rotate | zoom | hover | pickup
|
|
111
|
+
placementType?: 'static' | 'floating' | 'interactive'
|
|
112
|
+
bidfloor?: number // minimum CPM in USD
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Peer dependencies
|
|
117
|
+
|
|
118
|
+
The SDK does **not** bundle these — your scene already has them:
|
|
119
|
+
|
|
120
|
+
- `react` >= 18
|
|
121
|
+
- `react-dom` >= 18
|
|
122
|
+
- `three` >= 0.160
|
|
123
|
+
- `@react-three/fiber` >= 8
|
|
124
|
+
- `@react-three/drei` >= 9
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
package/a3d/client.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { PlacementForBid } from '../lib/openrtb';
|
|
2
|
+
import { AdEventPayload, ResolvedAd, SiteInfo } from '../types/openrtb';
|
|
3
|
+
/**
|
|
4
|
+
* A3DClient — the imperative (vanilla Three.js) surface of the SDK, for
|
|
5
|
+
* publishers not using React. Mirrors the flow the React components automate:
|
|
6
|
+
*
|
|
7
|
+
* const a3d = new A3DClient({ siteId: 'site-1', endpoint: 'https://api.a3d.io' })
|
|
8
|
+
* const ads = await a3d.requestBids(placements)
|
|
9
|
+
* const model = await a3d.loadModel(ads.get('slot-1').modelUrl)
|
|
10
|
+
* scene.add(model)
|
|
11
|
+
* a3d.fireImpression('slot-1', ads.get('slot-1'))
|
|
12
|
+
*/
|
|
13
|
+
import * as THREE from 'three';
|
|
14
|
+
export interface A3DClientOptions {
|
|
15
|
+
/** Your A3D site identifier. */
|
|
16
|
+
siteId?: string;
|
|
17
|
+
/** Ad server origin (e.g. https://api.a3d.io). Defaults to same-origin. */
|
|
18
|
+
endpoint?: string;
|
|
19
|
+
/** Extra site context sent in the bid request. */
|
|
20
|
+
site?: Partial<SiteInfo>;
|
|
21
|
+
}
|
|
22
|
+
export declare class A3DClient {
|
|
23
|
+
private readonly endpoint;
|
|
24
|
+
private readonly site;
|
|
25
|
+
private readonly loader;
|
|
26
|
+
private readonly pending;
|
|
27
|
+
private destroyed;
|
|
28
|
+
constructor(options?: A3DClientOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Send an OpenRTB bid request for the given placements and return the
|
|
31
|
+
* winning ads keyed by placement ID. Placements with no fill are absent.
|
|
32
|
+
*/
|
|
33
|
+
requestBids(placements: PlacementForBid[]): Promise<Map<string, ResolvedAd>>;
|
|
34
|
+
/**
|
|
35
|
+
* Load a GLB/GLTF creative and normalize it: largest dimension scaled to
|
|
36
|
+
* `fit` meters, base resting on the local origin — position the returned
|
|
37
|
+
* group at your placement anchor and it sits on the surface.
|
|
38
|
+
*/
|
|
39
|
+
loadModel(url: string, fit?: number): Promise<THREE.Group>;
|
|
40
|
+
/**
|
|
41
|
+
* Bill the impression for a rendered creative (deduped per placement per
|
|
42
|
+
* ad). Call it AFTER loadModel resolves and the model is in your scene —
|
|
43
|
+
* this fires the OpenRTB billing notice (bid.burl), counted server-side.
|
|
44
|
+
*/
|
|
45
|
+
fireImpression(_placementId: string, ad: ResolvedAd): void;
|
|
46
|
+
/** Record a custom event (viewable, click, rotate, zoom, hover). */
|
|
47
|
+
fireEvent(payload: AdEventPayload): void;
|
|
48
|
+
/**
|
|
49
|
+
* Handle a creative click: opens the server's tracked redirect when present
|
|
50
|
+
* (the server records the click), otherwise records it client-side and opens
|
|
51
|
+
* the advertiser URL directly.
|
|
52
|
+
*/
|
|
53
|
+
openClick(ad: ResolvedAd): void;
|
|
54
|
+
/** Abort in-flight bid requests and disable the client. */
|
|
55
|
+
destroy(): void;
|
|
56
|
+
}
|
package/a3d/index.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { BidRequestOptions } from '../hooks/useBidRequest';
|
|
2
|
+
import { PlacementForBid } from '../lib/openrtb';
|
|
3
|
+
import { AdEventPayload, ResolvedAd, SiteInfo } from '../types/openrtb';
|
|
4
|
+
export type { PlacementForBid } from '../lib/openrtb';
|
|
5
|
+
export type A3DPlacement = PlacementForBid;
|
|
6
|
+
export type { ResolvedAd } from '../types/openrtb';
|
|
7
|
+
export { A3DClient } from './client';
|
|
8
|
+
export type { A3DClientOptions } from './client';
|
|
9
|
+
export { fireBillableImpression } from '../hooks/useBidRequest';
|
|
10
|
+
export interface UseA3DAdsOptions extends BidRequestOptions {
|
|
11
|
+
/**
|
|
12
|
+
* House-ad GLB served when the auction returns no fill. Must be a URL that
|
|
13
|
+
* resolves on YOUR site (there is no universal default — without one,
|
|
14
|
+
* `modelUrl` is null on no-fill and you should render nothing).
|
|
15
|
+
*/
|
|
16
|
+
fallbackModelUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Runs the OpenRTB auction for one placement and returns the winning creative.
|
|
20
|
+
* Render `modelUrl` with <A3DModel ad={resolvedAd}> — billing fires on render.
|
|
21
|
+
*
|
|
22
|
+
* const { resolvedAd, loading, modelUrl } = useA3DAds(placement)
|
|
23
|
+
*/
|
|
24
|
+
export declare function useA3DAds(placement: PlacementForBid, siteInfo?: Partial<SiteInfo>, options?: UseA3DAdsOptions): {
|
|
25
|
+
resolvedAd: ResolvedAd | null;
|
|
26
|
+
loading: boolean;
|
|
27
|
+
error: string | null;
|
|
28
|
+
refetch: () => Promise<void>;
|
|
29
|
+
modelUrl: string | null;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Loads a GLB and normalizes its (arbitrary) native scale to a consistent
|
|
33
|
+
* on-surface size, resting the model on its base at the local origin.
|
|
34
|
+
* Render it inside a `<group position={placement.position}>`.
|
|
35
|
+
*
|
|
36
|
+
* Pass the winning `ad` and the billing notice (bid.burl) fires exactly once,
|
|
37
|
+
* after the creative has actually loaded and rendered — winning an auction
|
|
38
|
+
* alone is never billed.
|
|
39
|
+
*
|
|
40
|
+
* <A3DModel url={modelUrl} ad={resolvedAd} scale={0.8} />
|
|
41
|
+
*/
|
|
42
|
+
export declare function A3DModel({ url, ad, endpoint, scale, fit, hoverAnimation, onClick, onRendered, }: {
|
|
43
|
+
url: string;
|
|
44
|
+
/** The resolved ad this model renders — enables render-time billing. */
|
|
45
|
+
ad?: ResolvedAd | null;
|
|
46
|
+
/** Ad server origin for the billing fallback path. */
|
|
47
|
+
endpoint?: string;
|
|
48
|
+
/** Multiplier applied on top of the auto-fit normalization. */
|
|
49
|
+
scale?: number;
|
|
50
|
+
/** Target size (meters) of the model's largest dimension after fitting. */
|
|
51
|
+
fit?: number;
|
|
52
|
+
/** Float + slow-spin while the pointer hovers the model. */
|
|
53
|
+
hoverAnimation?: boolean;
|
|
54
|
+
/** Fired when the creative is clicked (e.g. open the click-through URL). */
|
|
55
|
+
onClick?: () => void;
|
|
56
|
+
/** Fired once when the creative has loaded and the impression was billed. */
|
|
57
|
+
onRendered?: (ad: ResolvedAd) => void;
|
|
58
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
59
|
+
/**
|
|
60
|
+
* A camera-facing "Sponsored" badge. Place it as a sibling of <A3DModel>
|
|
61
|
+
* inside the placement group; `offset` is its height (meters) above the base.
|
|
62
|
+
*
|
|
63
|
+
* <A3DSponsoredTag offset={0.5} />
|
|
64
|
+
*/
|
|
65
|
+
export declare function A3DSponsoredTag({ offset }: {
|
|
66
|
+
offset?: number;
|
|
67
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
68
|
+
export interface A3DViewabilityProps {
|
|
69
|
+
/** The placement being measured (position + maxDimensions define the slot box). */
|
|
70
|
+
placement: PlacementForBid;
|
|
71
|
+
/** The ad the auction resolved for this placement (null → nothing to track). */
|
|
72
|
+
ad: ResolvedAd | null;
|
|
73
|
+
/** Camera distance (meters) that counts as engagement proximity. Default: 8. */
|
|
74
|
+
proximityThreshold?: number;
|
|
75
|
+
/** Continuous on-screen time (ms) before the view is viewable. Default: 1000 (IIG). */
|
|
76
|
+
minDwellMs?: number;
|
|
77
|
+
/** Fraction of the viewport the slot must cover. Default: 0.015 (IIG ≥1.5%). */
|
|
78
|
+
coverageThreshold?: number;
|
|
79
|
+
/** Ad server origin override, matching the component that ran the auction. */
|
|
80
|
+
endpoint?: string;
|
|
81
|
+
/** Camera entered the proximity radius. */
|
|
82
|
+
onEnter?: (placementId: string) => void;
|
|
83
|
+
/** Camera left the radius; durationMs is the completed dwell time. */
|
|
84
|
+
onExit?: (placementId: string, durationMs: number) => void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Invisible tracker aligned with the IAB/MRC Intrinsic In-Game (IIG)
|
|
88
|
+
* Measurement Guidelines 2.0: the placement's slot box must cover ≥1.5% of
|
|
89
|
+
* the viewport, unoccluded (raycast), for ≥1 continuous second — then one
|
|
90
|
+
* `viewable` event fires per ad.
|
|
91
|
+
*
|
|
92
|
+
* Separately, camera proximity dwell is tracked as the richer *engagement*
|
|
93
|
+
* metric: on exit (or page unload) an `engagement` event carries the total
|
|
94
|
+
* dwell in engagement_time_ms.
|
|
95
|
+
*/
|
|
96
|
+
export declare function A3DViewability({ placement, ad, proximityThreshold, minDwellMs, coverageThreshold, endpoint, onEnter, onExit, }: A3DViewabilityProps): null;
|
|
97
|
+
/**
|
|
98
|
+
* Record a custom event (click, rotate, zoom, hover). Impression/viewable
|
|
99
|
+
* events are fired automatically by the SDK.
|
|
100
|
+
*
|
|
101
|
+
* fireEvent({ event_type: 'rotate', campaign_id, placement_id, bid_price })
|
|
102
|
+
*/
|
|
103
|
+
export declare function fireEvent(payload: AdEventPayload, endpoint?: string): void;
|
|
104
|
+
/**
|
|
105
|
+
* Handle a creative click. Preferred path: open the ad server's tracked
|
|
106
|
+
* redirect (`clickThroughUrl`) — the server records the click and 302s to the
|
|
107
|
+
* advertiser. Fallback (older servers): fire the click event from the client
|
|
108
|
+
* and open the advertiser URL directly. No-op for the house ad (no URL).
|
|
109
|
+
*/
|
|
110
|
+
export declare function openAdClick(ad: ResolvedAd, endpoint?: string): void;
|
|
111
|
+
export interface A3DAdsProps {
|
|
112
|
+
/** Your A3D site identifier. */
|
|
113
|
+
siteId?: string;
|
|
114
|
+
/** Ad server origin; overrides the build-time VITE_API_BASE default. */
|
|
115
|
+
endpoint?: string;
|
|
116
|
+
placements: PlacementForBid[];
|
|
117
|
+
/**
|
|
118
|
+
* House-ad GLB rendered when the auction returns no fill. Must resolve on
|
|
119
|
+
* YOUR site. Without it, unfilled placements render nothing.
|
|
120
|
+
*/
|
|
121
|
+
fallbackModelUrl?: string;
|
|
122
|
+
/** Show the "Sponsored" badge above each creative. Default: true. */
|
|
123
|
+
showSponsoredTag?: boolean;
|
|
124
|
+
/** Badge height (meters) above the model base. Default: 0.5. */
|
|
125
|
+
sponsoredTagOffset?: number;
|
|
126
|
+
/** Float + slow-spin creatives on hover. Default: true. */
|
|
127
|
+
hoverAnimation?: boolean;
|
|
128
|
+
/** Camera distance (meters) that counts as engagement proximity. Default: 8. */
|
|
129
|
+
proximityThreshold?: number;
|
|
130
|
+
/** A creative resolved from the auction (before it renders). */
|
|
131
|
+
onAdLoaded?: (placementId: string, ad: ResolvedAd) => void;
|
|
132
|
+
/** The creative rendered and the impression was billed. */
|
|
133
|
+
onImpression?: (placementId: string, ad: ResolvedAd) => void;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Drop <A3DAds> inside your <Canvas> and it handles the whole lifecycle:
|
|
137
|
+
* bid request on mount, dynamic GLB loading, sponsored tags, render-time
|
|
138
|
+
* billing, and IIG-aligned viewability. Each placement renders at its own
|
|
139
|
+
* `position`.
|
|
140
|
+
*/
|
|
141
|
+
export declare function A3DAds({ placements, fallbackModelUrl, showSponsoredTag, sponsoredTagOffset, hoverAnimation, proximityThreshold, siteId, endpoint, onAdLoaded, onImpression, }: A3DAdsProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BidRequestExtras, PlacementForBid } from '../lib/openrtb';
|
|
2
|
+
import { ResolvedAd, SiteInfo, AdEventPayload } from '../types/openrtb';
|
|
3
|
+
export declare function fireServerEvent(payload: AdEventPayload, endpoint?: string): void;
|
|
4
|
+
export declare function fireBillableImpression(ad: ResolvedAd, endpoint?: string): void;
|
|
5
|
+
export interface BidRequestOptions extends BidRequestExtras {
|
|
6
|
+
/** Ad server origin; overrides the build-time VITE_API_BASE default. */
|
|
7
|
+
endpoint?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function useBidRequest(placements: PlacementForBid[], siteInfo?: Partial<SiteInfo>, options?: BidRequestOptions): {
|
|
10
|
+
resolvedAds: Map<string, ResolvedAd>;
|
|
11
|
+
loading: boolean;
|
|
12
|
+
error: string | null;
|
|
13
|
+
refetch: () => Promise<void>;
|
|
14
|
+
};
|
package/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
(function(d,c){typeof exports=="object"&&typeof module<"u"?c(exports,require("react/jsx-runtime"),require("react"),require("@react-three/fiber"),require("@react-three/drei"),require("three"),require("three/examples/jsm/loaders/GLTFLoader.js"),require("three/examples/jsm/loaders/DRACOLoader.js")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","@react-three/fiber","@react-three/drei","three","three/examples/jsm/loaders/GLTFLoader.js","three/examples/jsm/loaders/DRACOLoader.js"],c):(d=typeof globalThis<"u"?globalThis:d||self,c(d.A3DSDK={},d.jsxRuntime,d.React,d.ReactThreeFiber,d.Drei,d.THREE,d.GLTFLoader_js,d.DRACOLoader_js))})(this,(function(d,c,s,C,B,te,ne,re){"use strict";function ie(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const n in t)if(n!=="default"){const o=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(e,n,o.get?o:{enumerable:!0,get:()=>t[n]})}}return e.default=t,Object.freeze(e)}const w=ie(te),oe="";function P(t){return(t??oe).replace(/\/+$/,"")}function z(){return crypto.randomUUID()}function $(t,e,n){const o=t.map(i=>({id:i.id,bidfloor:i.bidfloor??1,bidfloorcur:"USD",secure:1,ext:{threeds:{max_width:i.maxDimensions.width,max_height:i.maxDimensions.height,max_depth:i.maxDimensions.depth,scene_type:i.sceneType,surface_type:i.surfaceType,tags:i.tags,placement_type:i.placementType??"static",interaction:i.interaction,anchor:i.position}}})),r=/Mobi|Android|iPhone|iPad/i.test(navigator.userAgent);return{id:z(),imp:o,site:{name:e?.name??"3D DSP Landing",domain:e?.domain??window.location.hostname,page:e?.page??window.location.href,cat:e?.cat??["IAB1"],...e?.id?{id:e.id,publisher:{id:e.id}}:{}},device:{ua:navigator.userAgent,w:window.innerWidth,h:window.innerHeight,language:navigator.language,devicetype:r?1:2},at:1,cur:["USD"],source:{tid:z(),schain:{complete:1,ver:"1.0",nodes:[{asi:"a3d.io",sid:e?.id??"demo",hp:1}]}},regs:n?.regs??{coppa:0},...n?.test?{test:1}:{},tmax:500}}function se(t,e,n,o,r){return t.replace(/\$\{AUCTION_PRICE\}/g,String(o)).replace(/\$\{AUCTION_ID\}/g,e).replace(/\$\{AUCTION_IMP_ID\}/g,n).replace(/\$\{AUCTION_CURRENCY\}/g,r)}function F(t){const e=new Map,n=t.cur??"USD";for(const o of t.seatbid??[])for(const r of o.bid){const i=r.ext.threeds;e.set(r.impid,{placementId:r.impid,modelUrl:i.model_url,fileType:i.file_type,clickUrl:i.click_url,clickThroughUrl:i.click_through_url??null,billingUrl:r.burl?se(r.burl,t.id,r.impid,r.price,n):null,campaignId:r.adid,creativeId:r.crid,bidPrice:r.price,campaignName:i.campaign_name,thumbnailUrl:i.thumbnail_url,modelDimensions:i.model_dimensions})}return e}function j(t,e){const n=JSON.stringify(t),o=P(e),r=`${o}/api/event`;o===""&&navigator.sendBeacon(r,new Blob([n],{type:"application/json"}))||fetch(r,{method:"POST",headers:{"Content-Type":"application/json"},body:n,keepalive:!0}).catch(()=>{})}const N=new Set;function L(t,e){const n=`${t.placementId}:${t.campaignId}:${t.creativeId}`;if(N.has(n))return;N.add(n);const o=()=>j({event_type:"impression",campaign_id:t.campaignId,placement_id:t.placementId,bid_price:t.bidPrice},e);if(!t.billingUrl){o();return}fetch(t.billingUrl,{keepalive:!0}).then(r=>{r.ok||o()}).catch(o)}function V(t,e,n){const[o,r]=s.useState(new Map),[i,l]=s.useState(!0),[y,g]=s.useState(null),f=JSON.stringify(t),h=JSON.stringify(e??null),b=JSON.stringify(n??null),v=s.useRef({placements:t,siteInfo:e,options:n});v.current={placements:t,siteInfo:e,options:n};const D=s.useCallback(async()=>{const{placements:a,siteInfo:u,options:m}=v.current;if(a.length===0){l(!1);return}l(!0),g(null);try{const _=$(a,u,m),p=await fetch(`${P(m?.endpoint)}/api/bid`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(_)});if(!p.ok)throw new Error(`Bid request failed: ${p.status}`);const A=await p.json();r(F(A))}catch(_){console.warn("[useBidRequest] Bid request failed, using defaults:",_),g(_ instanceof Error?_.message:"Bid request failed"),r(new Map)}finally{l(!1)}},[f,h,b]);return s.useEffect(()=>{D()},[D]),{resolvedAds:o,loading:i,error:y,refetch:D}}class ce{endpoint;site;loader=new ne.GLTFLoader;pending=new Set;destroyed=!1;constructor(e={}){this.endpoint=P(e.endpoint),this.site=e.siteId?{id:e.siteId,...e.site}:e.site;const n=new re.DRACOLoader;n.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/"),this.loader.setDRACOLoader(n)}async requestBids(e){if(this.destroyed)throw new Error("A3DClient has been destroyed");if(e.length===0)return new Map;const n=new AbortController;this.pending.add(n);try{const o=await fetch(`${this.endpoint}/api/bid`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify($(e,this.site)),signal:n.signal});if(!o.ok)throw new Error(`Bid request failed: ${o.status}`);const r=await o.json();return F(r)}finally{this.pending.delete(n)}}async loadModel(e,n=.5){if(this.destroyed)throw new Error("A3DClient has been destroyed");const r=(await this.loader.loadAsync(e)).scene;r.traverse(b=>{b.isMesh&&(b.castShadow=!0,b.receiveShadow=!0)});const i=new w.Box3().setFromObject(r),l=new w.Vector3,y=new w.Vector3;i.getSize(l),i.getCenter(y);const g=Math.max(l.x,l.y,l.z)||1,f=n/g;r.scale.setScalar(f),r.position.set(-y.x*f,-i.min.y*f,-y.z*f);const h=new w.Group;return h.add(r),h}fireImpression(e,n){this.destroyed||L(n,this.endpoint)}fireEvent(e){this.destroyed||j(e,this.endpoint)}openClick(e){if(e.clickThroughUrl){window.open(e.clickThroughUrl,"_blank","noopener,noreferrer");return}this.fireEvent({event_type:"click",campaign_id:e.campaignId,placement_id:e.placementId,bid_price:e.bidPrice}),e.clickUrl&&window.open(e.clickUrl,"_blank","noopener,noreferrer")}destroy(){this.destroyed=!0;for(const e of this.pending)e.abort();this.pending.clear()}}function ae(t,e,n){const o=s.useMemo(()=>[t],[t]),{resolvedAds:r,loading:i,error:l,refetch:y}=V(o,e,n),g=r.get(t.id)??null;return{resolvedAd:g,loading:i,error:l,refetch:y,modelUrl:g?.modelUrl??n?.fallbackModelUrl??null}}function G({url:t,ad:e,endpoint:n,scale:o=1,fit:r=.5,hoverAnimation:i=!1,onClick:l,onRendered:y}){const{scene:g}=B.useGLTF(t),f=s.useRef(null),[h,b]=s.useState(!1),v=i||!!l;s.useEffect(()=>{e&&(L(e,n),y?.(e))},[e?.placementId,e?.campaignId,e?.creativeId]),s.useEffect(()=>{if(!(!l||!h))return document.body.style.cursor="pointer",()=>{document.body.style.cursor="auto"}},[l,h]);const D=s.useMemo(()=>{const a=g.clone(!0);a.traverse(S=>{if(S.isMesh){const x=S;x.material=Array.isArray(x.material)?x.material.map(U=>U.clone()):x.material.clone(),x.castShadow=!0,x.receiveShadow=!0}});const u=new w.Box3().setFromObject(a),m=new w.Vector3,_=new w.Vector3;u.getSize(m),u.getCenter(_);const p=Math.max(m.x,m.y,m.z)||1,A=r*o/p;return a.scale.setScalar(A),a.position.set(-_.x*A,-u.min.y*A,-_.z*A),a},[g,o,r]);return C.useFrame((a,u)=>{if(!f.current)return;const m=i&&h?.08:0;f.current.position.y+=(m-f.current.position.y)*u*5,i&&h&&(f.current.rotation.y+=u*.5)}),c.jsx("group",{ref:f,onClick:l?a=>{a.stopPropagation(),l()}:void 0,onPointerOver:v?()=>b(!0):void 0,onPointerOut:v?()=>b(!1):void 0,children:c.jsx("primitive",{object:D})})}function J({offset:t=.5}){const e=s.useRef(null);return C.useFrame(n=>{e.current&&(e.current.position.y=t+Math.sin(n.clock.elapsedTime*2)*.03)}),c.jsx("group",{ref:e,position:[0,t,0],children:c.jsxs(B.Billboard,{follow:!0,lockX:!1,lockY:!1,lockZ:!1,children:[c.jsxs("mesh",{children:[c.jsx("planeGeometry",{args:[.6,.16]}),c.jsx("meshBasicMaterial",{color:"#000",transparent:!0,opacity:.9})]}),c.jsxs("mesh",{position:[0,0,-.001],children:[c.jsx("planeGeometry",{args:[.62,.18]}),c.jsx("meshBasicMaterial",{color:"#333",transparent:!0,opacity:.8})]}),c.jsxs("mesh",{position:[-.2,0,.002],children:[c.jsx("circleGeometry",{args:[.02,16]}),c.jsx("meshBasicMaterial",{color:"#10b981"})]}),c.jsxs("mesh",{position:[-.2,0,.001],children:[c.jsx("circleGeometry",{args:[.035,16]}),c.jsx("meshBasicMaterial",{color:"#10b981",transparent:!0,opacity:.3})]}),c.jsx(B.Text,{position:[.05,0,.002],fontSize:.07,color:"#fff",anchorX:"center",anchorY:"middle",children:"Sponsored"})]})})}function Y({placement:t,ad:e,proximityThreshold:n=8,minDwellMs:o=1e3,coverageThreshold:r=.015,endpoint:i,onEnter:l,onExit:y}){const g=s.useMemo(()=>{const[p,A,S]=t.position,{width:x,height:U,depth:M}=t.maxDimensions,T=new w.Box3(new w.Vector3(p-x/2,A,S-M/2),new w.Vector3(p+x/2,A+U,S+M/2)),O=T.getCenter(new w.Vector3),E=T.getSize(new w.Vector3).length()/2;return{anchor:new w.Vector3(p,A,S),box:T,center:O,radius:E}},[t.position,t.maxDimensions]),f=s.useRef(!1),h=s.useRef(null),b=s.useRef(!1),v=s.useRef(0),D=s.useRef(!1),a=s.useRef(0),u=s.useRef(null);s.useEffect(()=>{b.current=!1,v.current=0},[e?.campaignId,e?.creativeId]);const m=p=>{!e||p<250||j({event_type:"engagement",campaign_id:e.campaignId,placement_id:t.id,bid_price:e.bidPrice,engagement_time_ms:Math.round(p)},i)},_=s.useRef(m);return s.useEffect(()=>{_.current=m}),s.useEffect(()=>{const p=()=>{h.current!==null&&(_.current(performance.now()-h.current),h.current=null)};return window.addEventListener("pagehide",p),()=>{window.removeEventListener("pagehide",p),p()}},[]),C.useFrame((p,A)=>{const S=p.camera;if(e&&!b.current){let M=1/0,T=1/0,O=-1/0,E=-1/0,X=!1;const{min:R,max:q}=g.box,I=new w.Vector3;for(let k=0;k<8;k++)I.set(k&1?q.x:R.x,k&2?q.y:R.y,k&4?q.z:R.z),I.project(S),I.z<1&&(X=!0),M=Math.min(M,I.x),O=Math.max(O,I.x),T=Math.min(T,I.y),E=Math.max(E,I.y);const de=Math.max(0,Math.min(O,1)-Math.max(M,-1)),ue=Math.max(0,Math.min(E,1)-Math.max(T,-1)),fe=de*ue/4,W=p.clock.elapsedTime*1e3;if(W-a.current>150){a.current=W,u.current??=new w.Raycaster;const k=u.current,Z=g.center.clone().sub(S.position),Q=Z.length();k.set(S.position,Z.normalize()),k.far=Q;const ee=k.intersectObjects(p.scene.children,!0);D.current=ee.length>0&&ee[0].distance<Q-g.radius}const pe=X&&fe>=r&&!D.current;v.current=pe?v.current+A*1e3:0,v.current>=o&&(b.current=!0,j({event_type:"viewable",campaign_id:e.campaignId,placement_id:t.id,bid_price:e.bidPrice},i))}const U=S.position.distanceTo(g.anchor)<n;if(U&&!f.current)f.current=!0,h.current=performance.now(),l?.(t.id);else if(!U&&f.current&&(f.current=!1,h.current!==null)){const M=performance.now()-h.current;m(M),y?.(t.id,M),h.current=null}}),null}function H(t,e){j(t,e)}function K(t,e){if(t.clickThroughUrl){window.open(t.clickThroughUrl,"_blank","noopener,noreferrer");return}H({event_type:"click",campaign_id:t.campaignId,placement_id:t.placementId,bid_price:t.bidPrice},e),t.clickUrl&&window.open(t.clickUrl,"_blank","noopener,noreferrer")}function le({placements:t,fallbackModelUrl:e,showSponsoredTag:n=!0,sponsoredTagOffset:o=.5,hoverAnimation:r=!0,proximityThreshold:i=8,siteId:l,endpoint:y,onAdLoaded:g,onImpression:f}){const h=s.useMemo(()=>l?{id:l}:void 0,[l]),b=s.useMemo(()=>({endpoint:y}),[y]),{resolvedAds:v}=V(t,h,b),D=s.useRef(new Set);return s.useEffect(()=>{for(const[a,u]of v){const m=`${a}:${u.campaignId}:${u.creativeId}`;D.current.has(m)||(D.current.add(m),g?.(a,u))}},[v,g]),c.jsx(c.Fragment,{children:t.map(a=>{const u=v.get(a.id)??null,m=u?.modelUrl??e;return m?c.jsxs("group",{position:a.position,children:[c.jsx(G,{url:m,ad:u,endpoint:y,hoverAnimation:r,onClick:u?()=>K(u,y):void 0,onRendered:f?_=>f(a.id,_):void 0}),n&&c.jsx(J,{offset:o}),c.jsx(Y,{placement:a,ad:u,proximityThreshold:i,endpoint:y})]},a.id):null})})}d.A3DAds=le,d.A3DClient=ce,d.A3DModel=G,d.A3DSponsoredTag=J,d.A3DViewability=Y,d.fireBillableImpression=L,d.fireEvent=H,d.openAdClick=K,d.useA3DAds=ae,Object.defineProperty(d,Symbol.toStringTag,{value:"Module"})}));
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
package/index.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/lib/openrtb.ts","../src/hooks/useBidRequest.ts","../src/a3d/client.ts","../src/a3d/index.tsx"],"sourcesContent":["import type { BidRequest, BidResponse, Imp, Regs, ResolvedAd, SiteInfo } from '@/types/openrtb'\n\n/**\n * Base URL for the OpenRTB ad server.\n * - Dev: leave VITE_API_BASE unset → '' → relative '/api/*' hits the Vite proxy.\n * - Prod: set VITE_API_BASE to the deployed backend origin (e.g. https://a3d-api.example.com).\n */\nexport const API_BASE = import.meta.env.VITE_API_BASE ?? ''\n\n/**\n * Resolve the ad-server base URL for a request. A runtime `endpoint` (e.g. the\n * `endpoint` prop on <A3DAds>) wins over the build-time VITE_API_BASE default.\n * Trailing slashes are stripped so callers can pass either form.\n */\nexport function resolveApiBase(endpoint?: string): string {\n const base = endpoint ?? API_BASE\n return base.replace(/\\/+$/, '')\n}\n\nexport interface PlacementForBid {\n id: string\n position: [number, number, number]\n sceneType: string\n surfaceType: string\n tags: string[]\n maxDimensions: { width: number; height: number; depth: number }\n interaction: string[]\n placementType?: 'static' | 'floating' | 'interactive'\n bidfloor?: number\n}\n\nexport interface BidRequestExtras {\n /** Regulatory/consent signals (GPP string, GDPR/COPPA flags) passed through untouched. */\n regs?: Regs\n /** Mark the request as non-billable test traffic. */\n test?: boolean\n}\n\nfunction generateUUID(): string {\n return crypto.randomUUID()\n}\n\nexport function buildBidRequest(\n placements: PlacementForBid[],\n siteInfo?: Partial<SiteInfo>,\n extras?: BidRequestExtras,\n): BidRequest {\n const imps: Imp[] = placements.map((p) => ({\n id: p.id,\n bidfloor: p.bidfloor ?? 1.0,\n bidfloorcur: 'USD',\n secure: 1,\n ext: {\n threeds: {\n max_width: p.maxDimensions.width,\n max_height: p.maxDimensions.height,\n max_depth: p.maxDimensions.depth,\n scene_type: p.sceneType,\n surface_type: p.surfaceType,\n tags: p.tags,\n placement_type: p.placementType ?? 'static',\n interaction: p.interaction,\n anchor: p.position,\n },\n },\n }))\n\n const isMobile = /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent)\n\n return {\n id: generateUUID(),\n imp: imps,\n site: {\n name: siteInfo?.name ?? '3D DSP Landing',\n domain: siteInfo?.domain ?? window.location.hostname,\n page: siteInfo?.page ?? window.location.href,\n cat: siteInfo?.cat ?? ['IAB1'],\n ...(siteInfo?.id ? { id: siteInfo.id, publisher: { id: siteInfo.id } } : {}),\n },\n device: {\n ua: navigator.userAgent,\n w: window.innerWidth,\n h: window.innerHeight,\n language: navigator.language,\n devicetype: isMobile ? 1 : 2,\n },\n // First-price auction, USD, and a supply chain of exactly one hop (us).\n at: 1,\n cur: ['USD'],\n source: {\n tid: generateUUID(),\n schain: {\n complete: 1,\n ver: '1.0',\n nodes: [{ asi: 'a3d.io', sid: siteInfo?.id ?? 'demo', hp: 1 }],\n },\n },\n regs: extras?.regs ?? { coppa: 0 },\n ...(extras?.test ? { test: 1 as const } : {}),\n tmax: 500,\n }\n}\n\n/** Substitute the OpenRTB notice-URL macros the server left for us. */\nfunction substituteMacros(\n url: string,\n auctionId: string,\n impId: string,\n price: number,\n currency: string,\n): string {\n return url\n .replace(/\\$\\{AUCTION_PRICE\\}/g, String(price))\n .replace(/\\$\\{AUCTION_ID\\}/g, auctionId)\n .replace(/\\$\\{AUCTION_IMP_ID\\}/g, impId)\n .replace(/\\$\\{AUCTION_CURRENCY\\}/g, currency)\n}\n\nexport function parseBidResponse(response: BidResponse): Map<string, ResolvedAd> {\n const resolved = new Map<string, ResolvedAd>()\n const currency = response.cur ?? 'USD'\n\n // No fill: seatbid is absent and nbr carries the no-bid reason.\n for (const seatbid of response.seatbid ?? []) {\n for (const bid of seatbid.bid) {\n const threeds = bid.ext.threeds\n resolved.set(bid.impid, {\n placementId: bid.impid,\n modelUrl: threeds.model_url,\n fileType: threeds.file_type,\n clickUrl: threeds.click_url,\n clickThroughUrl: threeds.click_through_url ?? null,\n billingUrl: bid.burl\n ? substituteMacros(bid.burl, response.id, bid.impid, bid.price, currency)\n : null,\n campaignId: bid.adid,\n creativeId: bid.crid,\n bidPrice: bid.price,\n campaignName: threeds.campaign_name,\n thumbnailUrl: threeds.thumbnail_url,\n modelDimensions: threeds.model_dimensions,\n })\n }\n }\n\n return resolved\n}\n","import { useState, useEffect, useCallback, useRef } from 'react'\nimport { buildBidRequest, parseBidResponse, resolveApiBase } from '@/lib/openrtb'\nimport type { BidRequestExtras, PlacementForBid } from '@/lib/openrtb'\nimport type { BidResponse, ResolvedAd, SiteInfo, AdEventPayload } from '@/types/openrtb'\n\nexport function fireServerEvent(payload: AdEventPayload, endpoint?: string): void {\n const body = JSON.stringify(payload)\n const apiBase = resolveApiBase(endpoint)\n // Use sendBeacon for reliability (fires even on page unload)\n // sendBeacon can't target a cross-origin URL without CORS preflight semantics,\n // so only use it for same-origin (empty base); otherwise fall back to fetch.\n const url = `${apiBase}/api/event`\n const sent = apiBase === ''\n && navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }))\n if (!sent) {\n fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body, keepalive: true }).catch(() => {})\n }\n}\n\n/**\n * Fire the billing notice for a rendered creative — once per\n * placement/campaign/creative. Prefers the OpenRTB billing URL (bid.burl,\n * recorded server-side against a single-use token); falls back to the legacy\n * client-side impression event if the burl is missing or unreachable.\n *\n * Call this only when the creative has actually rendered (A3DModel does it\n * for you when given the `ad` prop) — never on auction win alone.\n */\nconst billed = new Set<string>()\nexport function fireBillableImpression(ad: ResolvedAd, endpoint?: string): void {\n const key = `${ad.placementId}:${ad.campaignId}:${ad.creativeId}`\n if (billed.has(key)) return\n billed.add(key)\n\n const fallback = () =>\n fireServerEvent({\n event_type: 'impression',\n campaign_id: ad.campaignId,\n placement_id: ad.placementId,\n bid_price: ad.bidPrice,\n }, endpoint)\n\n if (!ad.billingUrl) {\n fallback()\n return\n }\n fetch(ad.billingUrl, { keepalive: true })\n .then((res) => {\n if (!res.ok) fallback()\n })\n .catch(fallback)\n}\n\nexport interface BidRequestOptions extends BidRequestExtras {\n /** Ad server origin; overrides the build-time VITE_API_BASE default. */\n endpoint?: string\n}\n\nexport function useBidRequest(\n placements: PlacementForBid[],\n siteInfo?: Partial<SiteInfo>,\n options?: BidRequestOptions,\n) {\n const [resolvedAds, setResolvedAds] = useState<Map<string, ResolvedAd>>(new Map())\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n\n // Consumers routinely inline the placements array / siteInfo object, giving\n // them a fresh identity every render. Re-run the auction only when their\n // *content* changes, otherwise an inline literal causes an infinite\n // fetch → setState → render → fetch loop.\n const placementsKey = JSON.stringify(placements)\n const siteKey = JSON.stringify(siteInfo ?? null)\n const optionsKey = JSON.stringify(options ?? null)\n const latest = useRef({ placements, siteInfo, options })\n latest.current = { placements, siteInfo, options }\n\n const fetchBids = useCallback(async () => {\n const { placements, siteInfo, options } = latest.current\n if (placements.length === 0) {\n setLoading(false)\n return\n }\n\n setLoading(true)\n setError(null)\n\n try {\n const bidRequest = buildBidRequest(placements, siteInfo, options)\n const res = await fetch(`${resolveApiBase(options?.endpoint)}/api/bid`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(bidRequest),\n })\n\n if (!res.ok) {\n throw new Error(`Bid request failed: ${res.status}`)\n }\n\n const bidResponse: BidResponse = await res.json()\n // Billing intentionally does NOT happen here: winning an auction is not\n // an impression. A3DModel fires the billing notice when the creative\n // actually renders (fireBillableImpression).\n setResolvedAds(parseBidResponse(bidResponse))\n } catch (err) {\n console.warn('[useBidRequest] Bid request failed, using defaults:', err)\n setError(err instanceof Error ? err.message : 'Bid request failed')\n setResolvedAds(new Map())\n } finally {\n setLoading(false)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [placementsKey, siteKey, optionsKey])\n\n useEffect(() => {\n fetchBids()\n }, [fetchBids])\n\n return { resolvedAds, loading, error, refetch: fetchBids }\n}\n","/**\n * A3DClient — the imperative (vanilla Three.js) surface of the SDK, for\n * publishers not using React. Mirrors the flow the React components automate:\n *\n * const a3d = new A3DClient({ siteId: 'site-1', endpoint: 'https://api.a3d.io' })\n * const ads = await a3d.requestBids(placements)\n * const model = await a3d.loadModel(ads.get('slot-1').modelUrl)\n * scene.add(model)\n * a3d.fireImpression('slot-1', ads.get('slot-1'))\n */\nimport * as THREE from 'three'\nimport { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'\nimport { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'\nimport { buildBidRequest, parseBidResponse, resolveApiBase } from '@/lib/openrtb'\nimport type { PlacementForBid } from '@/lib/openrtb'\nimport type { AdEventPayload, BidResponse, ResolvedAd, SiteInfo } from '@/types/openrtb'\nimport { fireServerEvent, fireBillableImpression } from '@/hooks/useBidRequest'\n\nexport interface A3DClientOptions {\n /** Your A3D site identifier. */\n siteId?: string\n /** Ad server origin (e.g. https://api.a3d.io). Defaults to same-origin. */\n endpoint?: string\n /** Extra site context sent in the bid request. */\n site?: Partial<SiteInfo>\n}\n\nexport class A3DClient {\n private readonly endpoint: string\n private readonly site: Partial<SiteInfo> | undefined\n private readonly loader = new GLTFLoader()\n private readonly pending = new Set<AbortController>()\n private destroyed = false\n\n constructor(options: A3DClientOptions = {}) {\n this.endpoint = resolveApiBase(options.endpoint)\n this.site = options.siteId ? { id: options.siteId, ...options.site } : options.site\n // Creatives may be draco-compressed (ad-tech creative weight limits) —\n // same decoder setup drei's useGLTF applies on the React side.\n const draco = new DRACOLoader()\n draco.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.7/')\n this.loader.setDRACOLoader(draco)\n }\n\n /**\n * Send an OpenRTB bid request for the given placements and return the\n * winning ads keyed by placement ID. Placements with no fill are absent.\n */\n async requestBids(placements: PlacementForBid[]): Promise<Map<string, ResolvedAd>> {\n if (this.destroyed) throw new Error('A3DClient has been destroyed')\n if (placements.length === 0) return new Map()\n\n const controller = new AbortController()\n this.pending.add(controller)\n try {\n const res = await fetch(`${this.endpoint}/api/bid`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(buildBidRequest(placements, this.site)),\n signal: controller.signal,\n })\n if (!res.ok) throw new Error(`Bid request failed: ${res.status}`)\n const bidResponse: BidResponse = await res.json()\n return parseBidResponse(bidResponse)\n } finally {\n this.pending.delete(controller)\n }\n }\n\n /**\n * Load a GLB/GLTF creative and normalize it: largest dimension scaled to\n * `fit` meters, base resting on the local origin — position the returned\n * group at your placement anchor and it sits on the surface.\n */\n async loadModel(url: string, fit = 0.5): Promise<THREE.Group> {\n if (this.destroyed) throw new Error('A3DClient has been destroyed')\n const gltf = await this.loader.loadAsync(url)\n const model = gltf.scene\n model.traverse((child) => {\n if ((child as THREE.Mesh).isMesh) {\n child.castShadow = true\n child.receiveShadow = true\n }\n })\n\n const box = new THREE.Box3().setFromObject(model)\n const size = new THREE.Vector3()\n const center = new THREE.Vector3()\n box.getSize(size)\n box.getCenter(center)\n const maxDim = Math.max(size.x, size.y, size.z) || 1\n const f = fit / maxDim\n model.scale.setScalar(f)\n model.position.set(-center.x * f, -box.min.y * f, -center.z * f)\n\n const group = new THREE.Group()\n group.add(model)\n return group\n }\n\n /**\n * Bill the impression for a rendered creative (deduped per placement per\n * ad). Call it AFTER loadModel resolves and the model is in your scene —\n * this fires the OpenRTB billing notice (bid.burl), counted server-side.\n */\n fireImpression(_placementId: string, ad: ResolvedAd): void {\n if (this.destroyed) return\n fireBillableImpression(ad, this.endpoint)\n }\n\n /** Record a custom event (viewable, click, rotate, zoom, hover). */\n fireEvent(payload: AdEventPayload): void {\n if (this.destroyed) return\n fireServerEvent(payload, this.endpoint)\n }\n\n /**\n * Handle a creative click: opens the server's tracked redirect when present\n * (the server records the click), otherwise records it client-side and opens\n * the advertiser URL directly.\n */\n openClick(ad: ResolvedAd): void {\n if (ad.clickThroughUrl) {\n window.open(ad.clickThroughUrl, '_blank', 'noopener,noreferrer')\n return\n }\n this.fireEvent({\n event_type: 'click',\n campaign_id: ad.campaignId,\n placement_id: ad.placementId,\n bid_price: ad.bidPrice,\n })\n if (ad.clickUrl) window.open(ad.clickUrl, '_blank', 'noopener,noreferrer')\n }\n\n /** Abort in-flight bid requests and disable the client. */\n destroy(): void {\n this.destroyed = true\n for (const controller of this.pending) controller.abort()\n this.pending.clear()\n }\n}\n","/**\n * @a3d/sdk — the publisher-facing Surface documented at /docs.\n *\n * This is the SAME code our own landing page runs, so the docs are never\n * out of sync with reality. The heavy lifting (OpenRTB bid request, impression\n * tracking) lives in the internal engine (`@/hooks/useBidRequest`,\n * `@/lib/openrtb`); this module is the thin, documented surface on top of it:\n *\n * import { A3DAds, useA3DAds, A3DModel, A3DSponsoredTag, fireEvent } from '@a3d/sdk'\n */\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { useFrame } from '@react-three/fiber'\nimport type { ThreeEvent } from '@react-three/fiber'\nimport { useGLTF, Billboard, Text } from '@react-three/drei'\nimport * as THREE from 'three'\nimport { useBidRequest, fireServerEvent, fireBillableImpression } from '@/hooks/useBidRequest'\nimport type { BidRequestOptions } from '@/hooks/useBidRequest'\nimport type { PlacementForBid } from '@/lib/openrtb'\nimport type { AdEventPayload, ResolvedAd, SiteInfo } from '@/types/openrtb'\n\n// Re-export the placement contract under the documented SDK name.\nexport type { PlacementForBid } from '@/lib/openrtb'\nexport type A3DPlacement = PlacementForBid\nexport type { ResolvedAd } from '@/types/openrtb'\nexport { A3DClient } from './client'\nexport type { A3DClientOptions } from './client'\nexport { fireBillableImpression } from '@/hooks/useBidRequest'\n\n/* ------------------------------------------------------------------ */\n/* useA3DAds — request a bid for a single placement */\n/* ------------------------------------------------------------------ */\n\nexport interface UseA3DAdsOptions extends BidRequestOptions {\n /**\n * House-ad GLB served when the auction returns no fill. Must be a URL that\n * resolves on YOUR site (there is no universal default — without one,\n * `modelUrl` is null on no-fill and you should render nothing).\n */\n fallbackModelUrl?: string\n}\n\n/**\n * Runs the OpenRTB auction for one placement and returns the winning creative.\n * Render `modelUrl` with <A3DModel ad={resolvedAd}> — billing fires on render.\n *\n * const { resolvedAd, loading, modelUrl } = useA3DAds(placement)\n */\nexport function useA3DAds(\n placement: PlacementForBid,\n siteInfo?: Partial<SiteInfo>,\n options?: UseA3DAdsOptions,\n) {\n const placements = useMemo(() => [placement], [placement])\n const { resolvedAds, loading, error, refetch } = useBidRequest(placements, siteInfo, options)\n const resolvedAd = resolvedAds.get(placement.id) ?? null\n return {\n resolvedAd,\n loading,\n error,\n refetch,\n // Winning creative, else the configured house ad, else null (render nothing).\n modelUrl: resolvedAd?.modelUrl ?? options?.fallbackModelUrl ?? null,\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* A3DModel — load + auto-fit a GLB creative onto a surface */\n/* ------------------------------------------------------------------ */\n\n/**\n * Loads a GLB and normalizes its (arbitrary) native scale to a consistent\n * on-surface size, resting the model on its base at the local origin.\n * Render it inside a `<group position={placement.position}>`.\n *\n * Pass the winning `ad` and the billing notice (bid.burl) fires exactly once,\n * after the creative has actually loaded and rendered — winning an auction\n * alone is never billed.\n *\n * <A3DModel url={modelUrl} ad={resolvedAd} scale={0.8} />\n */\nexport function A3DModel({\n url,\n ad,\n endpoint,\n scale = 1,\n fit = 0.5,\n hoverAnimation = false,\n onClick,\n onRendered,\n}: {\n url: string\n /** The resolved ad this model renders — enables render-time billing. */\n ad?: ResolvedAd | null\n /** Ad server origin for the billing fallback path. */\n endpoint?: string\n /** Multiplier applied on top of the auto-fit normalization. */\n scale?: number\n /** Target size (meters) of the model's largest dimension after fitting. */\n fit?: number\n /** Float + slow-spin while the pointer hovers the model. */\n hoverAnimation?: boolean\n /** Fired when the creative is clicked (e.g. open the click-through URL). */\n onClick?: () => void\n /** Fired once when the creative has loaded and the impression was billed. */\n onRendered?: (ad: ResolvedAd) => void\n}) {\n const { scene } = useGLTF(url)\n const groupRef = useRef<THREE.Group>(null)\n const [hovered, setHovered] = useState(false)\n const interactive = hoverAnimation || !!onClick\n\n // useGLTF suspends until the asset is loaded, so reaching this effect means\n // the creative really is on screen — the correct billing moment (burl).\n useEffect(() => {\n if (!ad) return\n fireBillableImpression(ad, endpoint)\n onRendered?.(ad)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [ad?.placementId, ad?.campaignId, ad?.creativeId])\n\n // Show a pointer cursor over a clickable creative.\n useEffect(() => {\n if (!onClick || !hovered) return\n document.body.style.cursor = 'pointer'\n return () => {\n document.body.style.cursor = 'auto'\n }\n }, [onClick, hovered])\n\n const clonedScene = useMemo(() => {\n const cloned = scene.clone(true)\n cloned.traverse((child: THREE.Object3D) => {\n if ((child as THREE.Mesh).isMesh) {\n const mesh = child as THREE.Mesh\n mesh.material = Array.isArray(mesh.material)\n ? mesh.material.map((m: THREE.Material) => m.clone())\n : mesh.material.clone()\n mesh.castShadow = true\n mesh.receiveShadow = true\n }\n })\n const box = new THREE.Box3().setFromObject(cloned)\n const size = new THREE.Vector3()\n const center = new THREE.Vector3()\n box.getSize(size)\n box.getCenter(center)\n const maxDim = Math.max(size.x, size.y, size.z) || 1\n const f = (fit * scale) / maxDim\n cloned.scale.setScalar(f)\n cloned.position.set(-center.x * f, -box.min.y * f, -center.z * f)\n return cloned\n }, [scene, scale, fit])\n\n useFrame((_, delta) => {\n if (!groupRef.current) return\n const targetY = hoverAnimation && hovered ? 0.08 : 0\n groupRef.current.position.y += (targetY - groupRef.current.position.y) * delta * 5\n if (hoverAnimation && hovered) groupRef.current.rotation.y += delta * 0.5\n })\n\n return (\n <group\n ref={groupRef}\n onClick={\n onClick\n ? (e: ThreeEvent<MouseEvent>) => {\n e.stopPropagation()\n onClick()\n }\n : undefined\n }\n onPointerOver={interactive ? () => setHovered(true) : undefined}\n onPointerOut={interactive ? () => setHovered(false) : undefined}\n >\n <primitive object={clonedScene} />\n </group>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* A3DSponsoredTag — billboarded \"Sponsored\" badge above a creative */\n/* ------------------------------------------------------------------ */\n\n/**\n * A camera-facing \"Sponsored\" badge. Place it as a sibling of <A3DModel>\n * inside the placement group; `offset` is its height (meters) above the base.\n *\n * <A3DSponsoredTag offset={0.5} />\n */\nexport function A3DSponsoredTag({ offset = 0.5 }: { offset?: number }) {\n const meshRef = useRef<THREE.Group>(null)\n\n useFrame((state) => {\n if (meshRef.current) {\n meshRef.current.position.y = offset + Math.sin(state.clock.elapsedTime * 2) * 0.03\n }\n })\n\n return (\n <group ref={meshRef} position={[0, offset, 0]}>\n <Billboard follow lockX={false} lockY={false} lockZ={false}>\n <mesh>\n <planeGeometry args={[0.6, 0.16]} />\n <meshBasicMaterial color=\"#000\" transparent opacity={0.9} />\n </mesh>\n <mesh position={[0, 0, -0.001]}>\n <planeGeometry args={[0.62, 0.18]} />\n <meshBasicMaterial color=\"#333\" transparent opacity={0.8} />\n </mesh>\n <mesh position={[-0.2, 0, 0.002]}>\n <circleGeometry args={[0.02, 16]} />\n <meshBasicMaterial color=\"#10b981\" />\n </mesh>\n <mesh position={[-0.2, 0, 0.001]}>\n <circleGeometry args={[0.035, 16]} />\n <meshBasicMaterial color=\"#10b981\" transparent opacity={0.3} />\n </mesh>\n <Text position={[0.05, 0, 0.002]} fontSize={0.07} color=\"#fff\" anchorX=\"center\" anchorY=\"middle\">\n Sponsored\n </Text>\n </Billboard>\n </group>\n )\n}\n\n/* ------------------------------------------------------------------ */\n/* A3DViewability — camera-proximity viewable + engagement tracking */\n/* ------------------------------------------------------------------ */\n\nexport interface A3DViewabilityProps {\n /** The placement being measured (position + maxDimensions define the slot box). */\n placement: PlacementForBid\n /** The ad the auction resolved for this placement (null → nothing to track). */\n ad: ResolvedAd | null\n /** Camera distance (meters) that counts as engagement proximity. Default: 8. */\n proximityThreshold?: number\n /** Continuous on-screen time (ms) before the view is viewable. Default: 1000 (IIG). */\n minDwellMs?: number\n /** Fraction of the viewport the slot must cover. Default: 0.015 (IIG ≥1.5%). */\n coverageThreshold?: number\n /** Ad server origin override, matching the component that ran the auction. */\n endpoint?: string\n /** Camera entered the proximity radius. */\n onEnter?: (placementId: string) => void\n /** Camera left the radius; durationMs is the completed dwell time. */\n onExit?: (placementId: string, durationMs: number) => void\n}\n\n/**\n * Invisible tracker aligned with the IAB/MRC Intrinsic In-Game (IIG)\n * Measurement Guidelines 2.0: the placement's slot box must cover ≥1.5% of\n * the viewport, unoccluded (raycast), for ≥1 continuous second — then one\n * `viewable` event fires per ad.\n *\n * Separately, camera proximity dwell is tracked as the richer *engagement*\n * metric: on exit (or page unload) an `engagement` event carries the total\n * dwell in engagement_time_ms.\n */\nexport function A3DViewability({\n placement,\n ad,\n proximityThreshold = 8,\n minDwellMs = 1000,\n coverageThreshold = 0.015,\n endpoint,\n onEnter,\n onExit,\n}: A3DViewabilityProps) {\n const geo = useMemo(() => {\n const [x, y, z] = placement.position\n const { width, height, depth } = placement.maxDimensions\n const box = new THREE.Box3(\n new THREE.Vector3(x - width / 2, y, z - depth / 2),\n new THREE.Vector3(x + width / 2, y + height, z + depth / 2),\n )\n const center = box.getCenter(new THREE.Vector3())\n const radius = box.getSize(new THREE.Vector3()).length() / 2\n return { anchor: new THREE.Vector3(x, y, z), box, center, radius }\n }, [placement.position, placement.maxDimensions])\n\n const inside = useRef(false)\n const enteredAt = useRef<number | null>(null)\n const viewableFired = useRef(false)\n const visibleStreakMs = useRef(0)\n const occluded = useRef(false)\n const lastOcclusionCheck = useRef(0)\n // In a ref (not useMemo): it's mutated every check, outside React's render model.\n const raycasterRef = useRef<THREE.Raycaster | null>(null)\n\n // A new ad in the same slot gets its own viewable event.\n useEffect(() => {\n viewableFired.current = false\n visibleStreakMs.current = 0\n }, [ad?.campaignId, ad?.creativeId])\n\n const fireEngagement = (durationMs: number) => {\n if (!ad || durationMs < 250) return\n fireServerEvent({\n event_type: 'engagement',\n campaign_id: ad.campaignId,\n placement_id: placement.id,\n bid_price: ad.bidPrice,\n engagement_time_ms: Math.round(durationMs),\n }, endpoint)\n }\n // Latest-closure ref for the pagehide flush (updated post-render; the\n // useFrame callback below always closes over the current render already).\n const fireEngagementRef = useRef(fireEngagement)\n useEffect(() => {\n fireEngagementRef.current = fireEngagement\n })\n\n // Flush an in-progress dwell if the user closes/navigates mid-view.\n useEffect(() => {\n const flush = () => {\n if (enteredAt.current !== null) {\n fireEngagementRef.current(performance.now() - enteredAt.current)\n enteredAt.current = null\n }\n }\n window.addEventListener('pagehide', flush)\n return () => {\n window.removeEventListener('pagehide', flush)\n flush()\n }\n }, [])\n\n useFrame((state, delta) => {\n const camera = state.camera\n\n /* ---- IIG viewability: coverage + occlusion + 1s continuous ---- */\n if (ad && !viewableFired.current) {\n // Project the slot box's corners to NDC and measure screen coverage.\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity\n let inFront = false\n const { min, max } = geo.box\n const corner = new THREE.Vector3()\n for (let i = 0; i < 8; i++) {\n corner.set(i & 1 ? max.x : min.x, i & 2 ? max.y : min.y, i & 4 ? max.z : min.z)\n corner.project(camera)\n if (corner.z < 1) inFront = true\n minX = Math.min(minX, corner.x); maxX = Math.max(maxX, corner.x)\n minY = Math.min(minY, corner.y); maxY = Math.max(maxY, corner.y)\n }\n // Visible-rect fraction of the viewport (NDC spans 2×2 = 4).\n const w = Math.max(0, Math.min(maxX, 1) - Math.max(minX, -1))\n const h = Math.max(0, Math.min(maxY, 1) - Math.max(minY, -1))\n const coverage = (w * h) / 4\n\n // Occlusion raycast (throttled — scene-wide raycasts aren't free).\n const now = state.clock.elapsedTime * 1000\n if (now - lastOcclusionCheck.current > 150) {\n lastOcclusionCheck.current = now\n raycasterRef.current ??= new THREE.Raycaster()\n const raycaster = raycasterRef.current\n const toCenter = geo.center.clone().sub(camera.position)\n const camDist = toCenter.length()\n raycaster.set(camera.position, toCenter.normalize())\n raycaster.far = camDist\n const hits = raycaster.intersectObjects(state.scene.children, true)\n // Something strictly in front of the slot box blocks it. Hits inside\n // the box (the creative itself) don't count as occlusion.\n occluded.current = hits.length > 0 && hits[0].distance < camDist - geo.radius\n }\n\n const visible = inFront && coverage >= coverageThreshold && !occluded.current\n visibleStreakMs.current = visible ? visibleStreakMs.current + delta * 1000 : 0\n\n if (visibleStreakMs.current >= minDwellMs) {\n viewableFired.current = true\n fireServerEvent({\n event_type: 'viewable',\n campaign_id: ad.campaignId,\n placement_id: placement.id,\n bid_price: ad.bidPrice,\n }, endpoint)\n }\n }\n\n /* ---- Proximity dwell: the engagement metric ---- */\n const distance = camera.position.distanceTo(geo.anchor)\n const isNear = distance < proximityThreshold\n\n if (isNear && !inside.current) {\n inside.current = true\n enteredAt.current = performance.now()\n onEnter?.(placement.id)\n } else if (!isNear && inside.current) {\n inside.current = false\n if (enteredAt.current !== null) {\n const durationMs = performance.now() - enteredAt.current\n fireEngagement(durationMs)\n onExit?.(placement.id, durationMs)\n enteredAt.current = null\n }\n }\n })\n\n return null\n}\n\n/* ------------------------------------------------------------------ */\n/* fireEvent — record a custom engagement event */\n/* ------------------------------------------------------------------ */\n\n/**\n * Record a custom event (click, rotate, zoom, hover). Impression/viewable\n * events are fired automatically by the SDK.\n *\n * fireEvent({ event_type: 'rotate', campaign_id, placement_id, bid_price })\n */\nexport function fireEvent(payload: AdEventPayload, endpoint?: string): void {\n fireServerEvent(payload, endpoint)\n}\n\n/**\n * Handle a creative click. Preferred path: open the ad server's tracked\n * redirect (`clickThroughUrl`) — the server records the click and 302s to the\n * advertiser. Fallback (older servers): fire the click event from the client\n * and open the advertiser URL directly. No-op for the house ad (no URL).\n */\nexport function openAdClick(ad: ResolvedAd, endpoint?: string): void {\n if (ad.clickThroughUrl) {\n window.open(ad.clickThroughUrl, '_blank', 'noopener,noreferrer')\n return\n }\n fireEvent({\n event_type: 'click',\n campaign_id: ad.campaignId,\n placement_id: ad.placementId,\n bid_price: ad.bidPrice,\n }, endpoint)\n if (ad.clickUrl) {\n window.open(ad.clickUrl, '_blank', 'noopener,noreferrer')\n }\n}\n\n/* ------------------------------------------------------------------ */\n/* A3DAds — drop-in component that handles the full lifecycle */\n/* ------------------------------------------------------------------ */\n\nexport interface A3DAdsProps {\n /** Your A3D site identifier. */\n siteId?: string\n /** Ad server origin; overrides the build-time VITE_API_BASE default. */\n endpoint?: string\n placements: PlacementForBid[]\n /**\n * House-ad GLB rendered when the auction returns no fill. Must resolve on\n * YOUR site. Without it, unfilled placements render nothing.\n */\n fallbackModelUrl?: string\n /** Show the \"Sponsored\" badge above each creative. Default: true. */\n showSponsoredTag?: boolean\n /** Badge height (meters) above the model base. Default: 0.5. */\n sponsoredTagOffset?: number\n /** Float + slow-spin creatives on hover. Default: true. */\n hoverAnimation?: boolean\n /** Camera distance (meters) that counts as engagement proximity. Default: 8. */\n proximityThreshold?: number\n /** A creative resolved from the auction (before it renders). */\n onAdLoaded?: (placementId: string, ad: ResolvedAd) => void\n /** The creative rendered and the impression was billed. */\n onImpression?: (placementId: string, ad: ResolvedAd) => void\n}\n\n/**\n * Drop <A3DAds> inside your <Canvas> and it handles the whole lifecycle:\n * bid request on mount, dynamic GLB loading, sponsored tags, render-time\n * billing, and IIG-aligned viewability. Each placement renders at its own\n * `position`.\n */\nexport function A3DAds({\n placements,\n fallbackModelUrl,\n showSponsoredTag = true,\n sponsoredTagOffset = 0.5,\n hoverAnimation = true,\n proximityThreshold = 8,\n siteId,\n endpoint,\n onAdLoaded,\n onImpression,\n}: A3DAdsProps) {\n const siteInfo = useMemo<Partial<SiteInfo> | undefined>(\n () => (siteId ? { id: siteId } : undefined),\n [siteId],\n )\n const options = useMemo(() => ({ endpoint }), [endpoint])\n const { resolvedAds } = useBidRequest(placements, siteInfo, options)\n const notified = useRef<Set<string>>(new Set())\n\n useEffect(() => {\n for (const [placementId, ad] of resolvedAds) {\n // One callback per placement per resolved ad, not one per re-render.\n const key = `${placementId}:${ad.campaignId}:${ad.creativeId}`\n if (notified.current.has(key)) continue\n notified.current.add(key)\n onAdLoaded?.(placementId, ad)\n }\n }, [resolvedAds, onAdLoaded])\n\n return (\n <>\n {placements.map((p) => {\n const ad = resolvedAds.get(p.id) ?? null\n const url = ad?.modelUrl ?? fallbackModelUrl\n if (!url) return null\n return (\n <group key={p.id} position={p.position}>\n <A3DModel\n url={url}\n ad={ad}\n endpoint={endpoint}\n hoverAnimation={hoverAnimation}\n onClick={ad ? () => openAdClick(ad, endpoint) : undefined}\n onRendered={onImpression ? (rendered) => onImpression(p.id, rendered) : undefined}\n />\n {showSponsoredTag && <A3DSponsoredTag offset={sponsoredTagOffset} />}\n <A3DViewability\n placement={p}\n ad={ad}\n proximityThreshold={proximityThreshold}\n endpoint={endpoint}\n />\n </group>\n )\n })}\n </>\n )\n}\n"],"names":["API_BASE","resolveApiBase","endpoint","generateUUID","buildBidRequest","placements","siteInfo","extras","imps","p","isMobile","substituteMacros","url","auctionId","impId","price","currency","parseBidResponse","response","resolved","seatbid","bid","threeds","fireServerEvent","payload","body","apiBase","billed","fireBillableImpression","ad","key","fallback","res","useBidRequest","options","resolvedAds","setResolvedAds","useState","loading","setLoading","error","setError","placementsKey","siteKey","optionsKey","latest","useRef","fetchBids","useCallback","bidRequest","bidResponse","err","useEffect","A3DClient","GLTFLoader","draco","DRACOLoader","controller","fit","model","child","box","THREE","size","center","maxDim","group","_placementId","useA3DAds","placement","useMemo","refetch","resolvedAd","A3DModel","scale","hoverAnimation","onClick","onRendered","scene","useGLTF","groupRef","hovered","setHovered","interactive","clonedScene","cloned","mesh","m","f","useFrame","_","delta","targetY","jsx","e","A3DSponsoredTag","offset","meshRef","state","jsxs","Billboard","Text","A3DViewability","proximityThreshold","minDwellMs","coverageThreshold","onEnter","onExit","geo","x","y","z","width","height","depth","radius","inside","enteredAt","viewableFired","visibleStreakMs","occluded","lastOcclusionCheck","raycasterRef","fireEngagement","durationMs","fireEngagementRef","flush","camera","minX","minY","maxX","maxY","inFront","min","max","corner","i","w","h","coverage","now","raycaster","toCenter","camDist","hits","visible","isNear","fireEvent","openAdClick","A3DAds","fallbackModelUrl","showSponsoredTag","sponsoredTagOffset","siteId","onAdLoaded","onImpression","notified","placementId","Fragment","rendered"],"mappings":"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"}
|