@skinhub/viewer 0.2.0 → 0.2.1

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.
Files changed (3) hide show
  1. package/EMBED.md +8 -6
  2. package/README.md +47 -26
  3. package/package.json +1 -1
package/EMBED.md CHANGED
@@ -522,16 +522,18 @@ Honest list, so none of it is discovered late.
522
522
  and remove it on `ready`.
523
523
  - **Click-through.** See §9.
524
524
  - **Server-side rendering.** The viewer is WebGL; there is no server-rendered fallback image.
525
- - **Render to an image.** There is no `capture()` yet. If you need a PNG per item, that is a separate
526
- service and is not built.
527
- - **Resolve an inspect link on its own.** `?i=` carries the item's *configuration* — float, seed,
528
- StatTrak, name plate, stickers, charm — but not which weapon, so a raw-iframe integration still has
525
+ - **Render to an image.** There is no `capture()`: the canvas runs without a preserved drawing buffer,
526
+ so a read from outside the render loop comes back blank. A PNG per item wants a render with no
527
+ browser tab open anyway, and that is a separate service and is not built.
528
+ - **Resolve an inspect link on its own.** `?i=` carries the item's *configuration* - float, seed,
529
+ StatTrak, name plate, stickers, charm - but not which weapon, so a raw-iframe integration still has
529
530
  to send `?weapon=`/`?paint=` (or `?hash=`) alongside it.
530
531
  **This limit is the frame's, not the product's:** `@skinhub/viewer` takes an `inspectLink` prop and
531
532
  handles the whole thing, because it decodes host-side and resolves the `defindex` against its own
532
533
  weapon table before building the URL. If you are in React, you never see this.
533
- - **Show a sticker, charm, agent or collectible on its own.** The embed renders a weapon or a glove
534
- today. The rest of our catalogue has pages on our site but is not yet embeddable.
534
+ - **Draw two subjects at once.** One frame shows one thing - a weapon or glove, a sticker, a charm, a
535
+ collectible, or an operator (§2). An operator shown as the subject holds no weapon, and the four
536
+ standalone subjects ignore `?view=`, `?i=`, `?float=`, `?slot=` and the sticker slots.
535
537
  - **Style anything.** The frame is our document. `gizmocolor` and `gizmoshadow` are the only visual
536
538
  hooks.
537
539
  - **Two viewers cheaply.** Each frame is its own WebGL context. If you need a grid of them, drop
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # @skinhub/viewer
2
2
 
3
- The SkinHub CS2 skin viewer as a React component — any weapon, knife or glove, at any wear and seed,
4
- with stickers and a charm. The 3D runs in an iframe on our origin, so installing this pulls in no
5
- `three` and no assets. React is the only peer dependency.
3
+ The SkinHub CS2 skin viewer as a React component - any weapon, knife or glove, at any wear and seed,
4
+ with stickers and a charm, or a sticker, charm, collectible or agent on its own. The 3D runs in an
5
+ iframe on our origin, so installing this pulls in no `three` and no assets. React is the only peer
6
+ dependency.
6
7
 
7
8
  ```bash
8
9
  bun add @skinhub/viewer @skinhub/cdn
@@ -21,13 +22,23 @@ export default function Page() {
21
22
  }
22
23
  ```
23
24
 
24
- Or straight from a Steam inspect link — float, seed, StatTrak, name plate, stickers and charm all come
25
+ Or straight from a Steam inspect link - float, seed, StatTrak, name plate, stickers and charm all come
25
26
  out of it:
26
27
 
27
28
  ```tsx
28
29
  <SkinViewer inspectLink={item.inspectLink} style={{ width: 640, height: 420 }} />
29
30
  ```
30
31
 
32
+ A weapon is one of five things the viewer draws, and each of the other four is its own prop. They are
33
+ drawn on their own, with no gun in the picture:
34
+
35
+ ```tsx
36
+ <SkinViewer sticker={{ id: 37, wear: 0.2 }} /> // the real holo/foil shader, not an image on a plane
37
+ <SkinViewer charm={{ id: 5 }} />
38
+ <SkinViewer collectible={{ id: 874 }} /> // a pin, coin, medal or trophy
39
+ <SkinViewer operator={{ id: 5036 }} /> // an agent, alone
40
+ ```
41
+
31
42
  Two things worth knowing up front:
32
43
 
33
44
  - **Give it a size.** It fills its container and has no intrinsic size; at 0 px you see nothing.
@@ -36,34 +47,44 @@ Two things worth knowing up front:
36
47
 
37
48
  ## Props
38
49
 
39
- One of `item` or `inspectLink` is required. Everything else is optional.
50
+ Exactly one subject is required - `item`, `inspectLink`, `sticker`, `charm`, `collectible` or
51
+ `operator`. Passing two, or none, is a type error. Everything else is optional.
40
52
 
41
53
  | prop | type | default | |
42
54
  |---|---|---|---|
43
- | `item` | `{ weapon \| defindex, paintIndex, float?, seed?, statTrak?, nameTag?, legacyModel?, stickers?, charm? }` | — | the item. `paintIndex: 0` is vanilla |
44
- | `inspectLink` | `string` | — | a masked Steam inspect link, instead of `item` |
55
+ | `item` | `{ weapon \| defindex, paintIndex, float?, seed?, statTrak?, nameTag?, legacyModel?, stickers?, charm? }` | - | the item. `paintIndex: 0` is vanilla |
56
+ | `inspectLink` | `string` | - | a masked Steam inspect link, instead of `item` |
57
+ | `sticker` | `{ id, wear? }` | - | one sticker on nothing. `wear` is `0` (mint) to `1` |
58
+ | `charm` | `{ id, pattern? }` | - | one charm. `pattern` is its colour template, not a different model |
59
+ | `collectible` | `{ id }` | - | one pin, coin, medal or trophy. It has no other field |
60
+ | `operator` | `{ id, pose? }` | - | one agent alone. `pose` is a main-menu clip name, `null` for their idle |
45
61
  | `origin` | `string` | `https://skinhub.gg` | where the embed is served. Read once, on mount |
46
62
  | `view` | `'gun' \| 'hands' \| 'agent'` | `'gun'` | item alone, first-person, or held by an operator |
47
- | `agent` | `{ id, pose? }` | default T | the operator, in `hands` and `agent` |
63
+ | `agent` | `{ id, pose? }` | default T | who is *holding* the weapon, in `hands` and `agent`. `operator` is the agent shown alone |
48
64
  | `gloves` | `{ type, paintIndex, float?, seed? } \| null` | `null` | `null` is the wearer's own pair |
49
65
  | `settings` | `{ camera?, quality?, environment?, overlays? }` | see below | shallow-merged per group |
50
66
  | `interactions` | `{ orbit?, zoom?, dragStickers?, dragCharm? }` | orbit + zoom on | what the user may do |
51
67
  | `editingSlot` | `number` | `-1` | which sticker slot is open; `5` is the charm |
52
- | `onReady` | `() => void` | — | stopped loading — **a level, not an edge**: it fires again after every weapon or view change |
53
- | `onError` | `(error) => void` | — | `no-item`, `bad-inspect-link`, `unknown-weapon`, `unreachable`, `render-failed`, `protocol-mismatch`, `bad-message` |
54
- | `onChange` | `(item) => void` | — | the user dragged a sticker or the charm — **carries ids, not names**, see below |
55
- | `onResize` | `({ width, height, dpr }) => void` | — | the canvas box changed |
56
- | `onEditingSlotChange` | `(slot) => void` | — | |
57
- | `loading` / `fallback` | `ReactNode` | — | your skeleton, and your empty state on error |
68
+ | `onReady` | `() => void` | - | stopped loading - **a level, not an edge**: it fires again after every weapon or view change |
69
+ | `onError` | `(error) => void` | - | `no-item`, `bad-inspect-link`, `unknown-weapon`, `unreachable`, `render-failed`, `protocol-mismatch`, `bad-message` |
70
+ | `onChange` | `(item) => void` | - | the user dragged a sticker or the charm - **carries ids, not names**, see below |
71
+ | `onResize` | `({ width, height, dpr }) => void` | - | the canvas box changed |
72
+ | `onEditingSlotChange` | `(slot) => void` | - | |
73
+ | `loading` / `fallback` | `ReactNode` | - | your skeleton, and your empty state on error |
58
74
  | `className` / `style` / `title` | | | on the wrapper |
59
- | `handle` | `SkinViewerHandle` | — | from `useSkinViewer()` |
75
+ | `handle` | `SkinViewerHandle` | - | from `useSkinViewer()` |
60
76
 
61
77
  `settings.environment.background` defaults to `'transparent'`, so the canvas composites over your page.
62
78
  `quality.bloomSpill` ships at `0` here. `MAP_NAMES` and `WEAPON_IDS` are exported as values for
63
79
  building pickers.
64
80
 
65
- Changing `float`, `seed`, `statTrak`, `nameTag`, `stickers` or `charm` updates **in place** — no
66
- reload, no flicker. Changing the weapon, paint index, legacy variant or view shows a loading cover.
81
+ `view`, `agent`, `gloves` and `editingSlot` describe a weapon, and the other four subjects ignore them.
82
+ `settings` and `interactions` apply to all six.
83
+
84
+ Changing `float`, `seed`, `statTrak`, `nameTag`, `stickers` or `charm` updates **in place** - no
85
+ reload, no flicker. Changing the weapon, paint index, legacy variant or view shows a loading cover. On
86
+ the other four subjects `sticker.wear`, `charm.pattern` and `operator.pose` are the in-place ones; the
87
+ id, and switching which subject you pass, reloads.
67
88
 
68
89
  ### Copying the item back out
69
90
 
@@ -79,7 +100,7 @@ the decoded `@skinhub/cdn` placement if you want to write database rows instead.
79
100
  ### `onChange` gives you ids, not names
80
101
 
81
102
  The embed has never seen the sticker catalogue, so the item it hands back has `stickers: [{ id: 5032,
82
- … }]` and no name or image. **`setItem(changed)` will therefore blank those out of your own UI** — a
103
+ … }]` and no name or image. **`setItem(changed)` will therefore blank those out of your own UI** - a
83
104
  silent data loss in your state, with nothing to catch. Merge it into what you already hold instead:
84
105
 
85
106
  ```tsx
@@ -104,7 +125,7 @@ instance while developing:
104
125
  <SkinViewer item={item} origin="http://localhost:3000" />
105
126
  ```
106
127
 
107
- It is read once, at mount — it is the one prop that could only be applied by reloading the frame.
128
+ It is read once, at mount - it is the one prop that could only be applied by reloading the frame.
108
129
 
109
130
  ## useSkinViewer()
110
131
 
@@ -123,31 +144,31 @@ viewer.reload()
123
144
 
124
145
  **A CORS error fetching the catalogue.** `cdn.skinhub.gg` does not send
125
146
  `access-control-allow-origin`, so a **browser** `fetch` for `data/skins.json` is blocked by the
126
- browser before your code sees it — it surfaces as a CORS failure rather than a status, so you cannot
147
+ browser before your code sees it - it surfaces as a CORS failure rather than a status, so you cannot
127
148
  even catch it. Call `@skinhub/cdn` from your **server** (a route handler, `getServerSideProps`, an RSC)
128
149
  and pass the rows down. The files are 4–6 MB each, so you want them server-side regardless.
129
150
 
130
151
  **Nothing renders.** If the container is 0 px in one dimension you get an empty box; the component
131
152
  warns about that in development. Otherwise `onError` reports `unreachable` after 15 seconds, naming the
132
- URL it could not reach — that means the embed never answered, so check the origin is reachable from
153
+ URL it could not reach - that means the embed never answered, so check the origin is reachable from
133
154
  this browser and that your page's `Content-Security-Policy` allows framing it (`frame-src`).
134
155
 
135
156
  **`onReady` fired but the picture is still covered.** `onReady` means "stopped loading" for the current
136
157
  identity, and it fires again after every weapon or view change. Treat it as a level, not an edge.
137
158
 
138
159
  **`protocol-mismatch`.** This package and the embed disagree about the wire. There is no back-compat
139
- window by design: update `@skinhub/viewer`, or reload the embed if it is the stale one — the error
160
+ window by design: update `@skinhub/viewer`, or reload the embed if it is the stale one - the error
140
161
  message says which.
141
162
 
142
163
  ## More
143
164
 
144
- - **[Quick-start app](https://github.com/SkinHubgg/skinhub-quick-start)** — a working skin picker built
165
+ - **[Quick-start app](https://github.com/SkinHubgg/viewer-quick-start)** - a working skin picker built
145
166
  on this and `@skinhub/cdn`, ending in an inspect link. The full example.
146
- - **[EMBED.md](./EMBED.md)** — the raw `<iframe>` URL and `postMessage` contract, for stacks that are
167
+ - **[EMBED.md](./EMBED.md)** - the raw `<iframe>` URL and `postMessage` contract, for stacks that are
147
168
  not React. A PHP, Rails or plain-HTML page can interpolate an item into an `src` and be done; that
148
169
  path is first-class, not a fallback.
149
- - **[@skinhub/cdn](https://github.com/SkinHubgg/skinhub-cdn)** — the CS2 catalogue this pairs with:
170
+ - **[@skinhub/cdn](https://github.com/SkinHubgg/skinhub-cdn)** - the CS2 catalogue this pairs with:
150
171
  skins, stickers, gloves, agents, and the inspect-link codec.
151
- - [Development and releasing](./CONTRIBUTING.md) — how the wire is kept in step with the embed.
172
+ - [Development and releasing](./CONTRIBUTING.md) - how the wire is kept in step with the embed.
152
173
 
153
174
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skinhub/viewer",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "React wrapper for the SkinHub CS2 skin viewer embed — props in, postMessage out. No three.js, no renderer, no asset bundle; React is the only peer.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",