@skinhub/viewer 0.1.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 (48) hide show
  1. package/EMBED.md +442 -0
  2. package/README.md +153 -0
  3. package/dist/SkinViewer.d.ts +18 -0
  4. package/dist/SkinViewer.d.ts.map +1 -0
  5. package/dist/SkinViewer.js +404 -0
  6. package/dist/SkinViewer.js.map +1 -0
  7. package/dist/index.d.ts +53 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +51 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/item.d.ts +118 -0
  12. package/dist/item.d.ts.map +1 -0
  13. package/dist/item.js +319 -0
  14. package/dist/item.js.map +1 -0
  15. package/dist/link.d.ts +30 -0
  16. package/dist/link.d.ts.map +1 -0
  17. package/dist/link.js +18 -0
  18. package/dist/link.js.map +1 -0
  19. package/dist/protocol.d.ts +231 -0
  20. package/dist/protocol.d.ts.map +1 -0
  21. package/dist/protocol.js +128 -0
  22. package/dist/protocol.js.map +1 -0
  23. package/dist/state.d.ts +107 -0
  24. package/dist/state.d.ts.map +1 -0
  25. package/dist/state.js +351 -0
  26. package/dist/state.js.map +1 -0
  27. package/dist/types.d.ts +573 -0
  28. package/dist/types.d.ts.map +1 -0
  29. package/dist/types.js +106 -0
  30. package/dist/types.js.map +1 -0
  31. package/dist/useSkinViewer.d.ts +3 -0
  32. package/dist/useSkinViewer.d.ts.map +1 -0
  33. package/dist/useSkinViewer.js +66 -0
  34. package/dist/useSkinViewer.js.map +1 -0
  35. package/dist/weapons.d.ts +109 -0
  36. package/dist/weapons.d.ts.map +1 -0
  37. package/dist/weapons.js +260 -0
  38. package/dist/weapons.js.map +1 -0
  39. package/package.json +65 -0
  40. package/src/SkinViewer.tsx +465 -0
  41. package/src/index.ts +88 -0
  42. package/src/item.ts +373 -0
  43. package/src/link.ts +33 -0
  44. package/src/protocol.ts +241 -0
  45. package/src/state.ts +389 -0
  46. package/src/types.ts +672 -0
  47. package/src/useSkinViewer.ts +80 -0
  48. package/src/weapons.ts +284 -0
package/dist/types.js ADDED
@@ -0,0 +1,106 @@
1
+ /**
2
+ * *** `@skinhub/viewer` - THE PUBLIC PROP CONTRACT. ***
3
+ *
4
+ * ═════════════════════════════════════════════════════════════════════════════════════════════
5
+ * *** WHAT THIS PACKAGE IS, AND THE ONE SENTENCE THAT DECIDES EVERY QUESTION BELOW. ***
6
+ *
7
+ * The renderer is a page on our origin (`/frame`). This package renders an `<iframe>` at it and
8
+ * speaks its message protocol. Owner, 2026-08-15: *"the package is just a lightweight helper that
9
+ * translates the postMessages thing to actual props and state that the user can control."*
10
+ *
11
+ * *** SO THERE IS NO `three` HERE AND THERE NEVER WILL BE. *** The design rule is *"there is no raw
12
+ * npm renderer, ever."* An integrator installing this installs React bindings and a URL builder. The
13
+ * whole of the 3D - the models, the shaders, the compositing, the 19.7 GB asset export - stays on our
14
+ * side of the frame, which is what makes the logo enforceable and what removes every peer-dependency
15
+ * question about three.js at once.
16
+ *
17
+ * ═════════════════════════════════════════════════════════════════════════════════════════════
18
+ * *** THE RULE THIS FILE IS WRITTEN UNDER: nothing here may require knowing how SkinHub is built. ***
19
+ *
20
+ * Every name is either a word the CS2 economy already uses (`paintIndex`, `float`, `seed`,
21
+ * `defindex`, `statTrak`) or a word any 3D component uses (`view`, `camera`, `quality`, `bloom`).
22
+ * Nothing is named after a route, a store, a shader parameter or a file in our tree. A prop that only
23
+ * makes sense once you have read our source is a bug in this file.
24
+ *
25
+ * *** AND ONE COROLLARY WORTH STATING, because it is the difference between a wrapper and a leak: ***
26
+ * this file does NOT re-export `/frame`'s own vocabulary. The wire says `weaponType` and
27
+ * `PlacementSlots` in the game's field names (`sticker_id`, `offset_x`); an integrator says `weapon`
28
+ * and `{ id, offsetX }`. `item.ts` is the only place the two meet, and it is not exported for the
29
+ * public surface's sake - it is exported because a marketplace holding an inspect link needs the
30
+ * bridge in its own server code too.
31
+ */
32
+ import { LINK } from './link.js';
33
+ /**
34
+ * *** WHICH ITEM FIELDS UPDATE IN PLACE, WRITTEN DOWN AS A VALUE. ***
35
+ *
36
+ * The owner's requirement, verbatim: changing `float`, `seed`, `statTrak`, `nameTag`, the stickers or
37
+ * the charm *"must NOT reload the viewer - that's a must to make it feel just like in our website."*
38
+ * Changing the weapon or the paint kit *"shows nothing until the model has loaded, behind the loading
39
+ * card"*.
40
+ *
41
+ * This list is the FIRST half. It is here, as a value, because {@link SkinViewerProps.loading} is
42
+ * raised off its complement - see `SkinViewer.tsx`'s `isIdentityChange` - and because a contract an
43
+ * integrator relies on should be readable without opening the renderer.
44
+ */
45
+ export const CHEAP_FIELDS = ['float', 'seed', 'statTrak', 'nameTag', 'stickers', 'charm'];
46
+ /* ═════════════════════════════════════════════════════════════════════════════════════════════
47
+ * SETTINGS
48
+ *
49
+ * *** FOUR GROUPS, EACH SHALLOW-MERGED OVER OUR DEFAULTS. *** decided
50
+ * yes: `settings={{ quality: { bloom: 0 } }}` keeps the camera, the environment and the other four
51
+ * quality fields. Every default is stated on the field, so a partial object is predictable rather
52
+ * than something you discover by removing keys until the picture changes.
53
+ *
54
+ * *** AND THE MERGE HAPPENS IN THE FRAME, NOT HERE, *** which is the only reason it can be trusted:
55
+ * `/frame` applies a patch through the same `applyFramePatch` that the URL goes through, so a value
56
+ * this package has never heard of and a value it sets are written to the same field by the same code.
57
+ * A second merge on this side would be a second set of defaults free to drift from the real ones.
58
+ * ═══════════════════════════════════════════════════════════════════════════════════════════ */
59
+ /**
60
+ * *** THE MAPS, AS A CLOSED UNION. *** The owner asked for this directly: `background` is
61
+ * *"'transparent' or a real map id, not a free string"*.
62
+ *
63
+ * WHAT THAT COSTS, said out loud rather than discovered: this list is a COPY of the app's own, so a
64
+ * map added to the export after this package was published is not typeable until the package is
65
+ * upgraded. That is the deliberate trade - the rule is *"no back-compat
66
+ * boilerplate, upgrade when you need to"* - and the failure is legible either way: the frame reports
67
+ * an unknown map in its `problems` and keeps the lighting it had.
68
+ */
69
+ export const MAP_NAMES = [
70
+ 'Ancient',
71
+ 'Anubis',
72
+ 'Baggage',
73
+ 'Cache',
74
+ 'Dust II',
75
+ 'Inferno',
76
+ 'Italy',
77
+ 'Mirage',
78
+ 'Nuke',
79
+ 'Office',
80
+ 'Overpass',
81
+ 'Train',
82
+ 'Vertigo',
83
+ 'Warehouse',
84
+ ];
85
+ /*
86
+ * *** THERE IS NO `capture()`, AND THAT IS A MEASURED DECISION RATHER THAN A MISSING FEATURE. ***
87
+ *
88
+ * Asked for as *"yes IF IT IS CHEAP, 100% optional"*. It is not cheap. The renderer's
89
+ * `<Canvas>` runs with `preserveDrawingBuffer: false`, so a `toDataURL` taken from outside the render
90
+ * loop returns a blank image; shipping a real one means the renderer draws and reads inside a single
91
+ * frame, which is a change to a shared file in the app rather than anything this package can do. The
92
+ * frame's protocol has no `capture` verb for the same reason.
93
+ *
94
+ * *** AND NOTE WHAT IT WOULD NOT SOLVE EVEN IF IT WERE FREE: *** thumbnails for a listing grid and
95
+ * preview images for shared links both need a render that happens without a browser tab open. That is
96
+ * a server-side product and is explicitly not in scope.
97
+ */
98
+ /*
99
+ * *** AND THERE IS NO `apiKey`. NOT NOW, NOT LATER. ***
100
+ *
101
+ * Decided: *"No, never. Anyone can copy an embed URL anyway, so a key
102
+ * buys nothing."* The viewer is free for everyone. It is recorded here, in the type surface, because
103
+ * the cost of adding one later is a breaking change for every integrator - so the absence has to be a
104
+ * decision somebody can read, not an omission somebody might fix.
105
+ */
106
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,OAAO,EAAE,IAAI,EAAmB,MAAM,WAAW,CAAA;AAsGjD;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,CAAU,CAAA;AA8ElG;;;;;;;;;;;;iGAYiG;AAEjG;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACxB,SAAS;IACT,QAAQ;IACR,SAAS;IACT,OAAO;IACP,SAAS;IACT,SAAS;IACT,OAAO;IACP,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,UAAU;IACV,OAAO;IACP,SAAS;IACT,WAAW;CACF,CAAA;AAiYV;;;;;;;;;;;;GAYG;AAEH;;;;;;;GAOG"}
@@ -0,0 +1,3 @@
1
+ import type { SkinViewerHandle } from './types.js';
2
+ export declare const useSkinViewer: () => SkinViewerHandle;
3
+ //# sourceMappingURL=useSkinViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSkinViewer.d.ts","sourceRoot":"","sources":["../src/useSkinViewer.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAIlD,eAAO,MAAM,aAAa,QAAO,gBAiDhC,CAAA"}
@@ -0,0 +1,66 @@
1
+ 'use client';
2
+ /**
3
+ * *** `useSkinViewer()` - PROPS FOR STATE, A HOOK FOR VERBS. ***
4
+ *
5
+ * The owner floated this shape and it is the right one. Everything the viewer SHOWS is a prop, because
6
+ * that is what React is good at. The two or three things that are not values - "load it again", "what
7
+ * is it doing right now" - cannot be props without inventing a boolean somebody has to toggle back,
8
+ * so they are here.
9
+ *
10
+ * const viewer = useSkinViewer()
11
+ * <SkinViewer item={item} handle={viewer} loading={<Skeleton />} />
12
+ * <button onClick={viewer.reload} disabled={viewer.status === 'connecting'}>Reload</button>
13
+ *
14
+ * *** THE HOOK IS OPTIONAL AND THE COMPONENT IS COMPLETE WITHOUT IT. *** `onReady`, `onError` and
15
+ * `onChange` cover every integration that does not need a verb, and a component that only worked when
16
+ * a hook was also mounted would be a component with a hidden second half.
17
+ *
18
+ * *** `reload` IS STABLE; THE HANDLE OBJECT IS NOT. *** The object is rebuilt when `status`, `error` or
19
+ * `problems` move, because that is how a React value re-renders the tree that reads it. Put
20
+ * `viewer.reload` in a dependency array, not `viewer`.
21
+ */
22
+ import { useMemo, useRef, useState } from 'react';
23
+ import { LINK } from './link.js';
24
+ const INITIAL = { status: 'connecting', error: null, problems: [] };
25
+ export const useSkinViewer = () => {
26
+ const [snapshot, setSnapshot] = useState(INITIAL);
27
+ /*
28
+ * ONE LINK OBJECT FOR THE LIFE OF THE HOOK. `useRef` with a lazy fill rather than `useMemo`, because
29
+ * a memo is a cache and React is allowed to throw a cache away; this is identity, and the component
30
+ * writes to it.
31
+ */
32
+ const link = useRef(null);
33
+ if (link.current === null)
34
+ link.current = {
35
+ reload: () => { },
36
+ /*
37
+ * *** THE EQUALITY BAIL IS LOAD-BEARING, NOT AN OPTIMISATION. *** The component publishes from
38
+ * an effect that runs on every render; a `setState` that always produced a new object would
39
+ * re-render this hook's owner, which re-renders the component, which runs the effect, which
40
+ * publishes again - forever.
41
+ *
42
+ * *** AND THE ERROR IS COMPARED BY VALUE, NOT IDENTITY, WHICH IS THE WHOLE REASON THIS IS NOT
43
+ * A ONE-LINER. *** A subject failure (`no-item`, a link that did not decode) is DERIVED from
44
+ * this render's props, so it is a fresh object every render even when nothing has changed.
45
+ * Comparing it by identity is exactly the loop above.
46
+ *
47
+ * `problems` is compared by identity on purpose: it is the array the frame sent, and a new one
48
+ * means a new `hello`.
49
+ */
50
+ publish: next => setSnapshot(current => current.status === next.status &&
51
+ current.problems === next.problems &&
52
+ current.error?.code === next.error?.code &&
53
+ current.error?.message === next.error?.message
54
+ ? current
55
+ : next),
56
+ };
57
+ const reload = useRef(() => link.current?.reload()).current;
58
+ return useMemo(() => ({
59
+ reload,
60
+ status: snapshot.status,
61
+ error: snapshot.error,
62
+ problems: snapshot.problems,
63
+ [LINK]: link.current,
64
+ }), [reload, snapshot]);
65
+ };
66
+ //# sourceMappingURL=useSkinViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSkinViewer.js","sourceRoot":"","sources":["../src/useSkinViewer.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AAEjD,OAAO,EAAE,IAAI,EAAwC,MAAM,WAAW,CAAA;AAGtE,MAAM,OAAO,GAAmB,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAA;AAEnF,MAAM,CAAC,MAAM,aAAa,GAAG,GAAqB,EAAE;IACnD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAiB,OAAO,CAAC,CAAA;IAEjE;;;;OAIG;IACH,MAAM,IAAI,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAA;IAC5C,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;QACxB,IAAI,CAAC,OAAO,GAAG;YACd,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;YAChB;;;;;;;;;;;;;eAaG;YACH,OAAO,EAAE,IAAI,CAAC,EAAE,CACf,WAAW,CAAC,OAAO,CAAC,EAAE,CACrB,OAAO,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;gBAC9B,OAAO,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;gBAClC,OAAO,CAAC,KAAK,EAAE,IAAI,KAAK,IAAI,CAAC,KAAK,EAAE,IAAI;gBACxC,OAAO,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,KAAK,EAAE,OAAO;gBAC7C,CAAC,CAAC,OAAO;gBACT,CAAC,CAAC,IAAI,CACP;SACF,CAAA;IAEF,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,OAAO,CAAA;IAE3D,OAAO,OAAO,CACb,GAAG,EAAE,CAAC,CAAC;QACN,MAAM;QACN,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;QACrB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,OAAqB;KAClC,CAAC,EACF,CAAC,MAAM,EAAE,QAAQ,CAAC,CAClB,CAAA;AACF,CAAC,CAAA"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * WHICH WEAPON, said two ways — and the table that makes them the same question.
3
+ *
4
+ * An integrator holds one of two things. Either a row out of `@skinhub/cdn`'s `skins.json`, whose
5
+ * `weapon.id` is `'weapon_ak47'`, or a decoded inspect link, whose `defindex` is `7`. Both are the
6
+ * AK-47. `<SkinViewer>` takes either — {@link WeaponId} on the `weapon` prop, or the number on
7
+ * `defindex` — and this is the only place the two are reconciled.
8
+ *
9
+ * ─────────────────────────────────────────────────────────────────────────────────────────────
10
+ * WHY THIS TABLE IS CHECKED IN AND NOT FETCHED, when the whole premise of `@skinhub/cdn` is that
11
+ * data derived from the CDN has to be derived at runtime.
12
+ *
13
+ * That rule is about data that CHANGES — a new finish, a new sticker capsule, a new agent. This is
14
+ * not that. A defindex is Valve's item definition index: `weapon_ak47` has been 7 since 2013 and
15
+ * cannot become anything else without breaking every inventory in the game. The table is 63 rows and
16
+ * it is derived from the export itself (`data/skins.json`, `weapon.weapon_id` → `weapon.id`,
17
+ * generated 2026-08-15, verified: 63 distinct defindexes, no defindex mapping to two DIFFERENT
18
+ * weapons — the 20 knives each carry a second `sfui_wpnhud_*` alias for their vanilla row, which
19
+ * `getWeaponModelPath` already resolves to the same model).
20
+ *
21
+ * The alternative is fetching a 4.4 MB `skins.json` to answer "what is 7", which would make the
22
+ * inspect-link path — the one the product exists for — cost four megabytes before the first frame.
23
+ *
24
+ * WHAT GOING STALE LOOKS LIKE, stated rather than discovered: Valve ships a new weapon, an integrator
25
+ * passes its defindex, and {@link weaponIdForDefindex} returns `undefined`. `<SkinViewer>` then
26
+ * reports `unknown-weapon` through `onError` and renders its error state, naming the number it could
27
+ * not resolve. That is a legible failure with an obvious fix (pass `weapon` instead, or upgrade), not
28
+ * a blank canvas.
29
+ */
30
+ /**
31
+ * Every weapon the viewer can render, as the CS2 econ item id.
32
+ *
33
+ * This is `skin.weapon.id` in `@skinhub/cdn`'s `skins.json` rows, verbatim — so
34
+ * `<SkinViewer weapon={row.weapon.id} paintIndex={…} />` typechecks against a catalogue row with no
35
+ * conversion. Type it into an editor and autocomplete lists all 71.
36
+ */
37
+ export declare const WEAPON_IDS: readonly ["weapon_cz75a", "weapon_deagle", "weapon_elite", "weapon_fiveseven", "weapon_glock", "weapon_hkp2000", "weapon_p250", "weapon_revolver", "weapon_tec9", "weapon_usp_silencer", "weapon_bizon", "weapon_mac10", "weapon_mp5sd", "weapon_mp7", "weapon_mp9", "weapon_p90", "weapon_ump45", "weapon_ak47", "weapon_aug", "weapon_famas", "weapon_galilar", "weapon_m4a1", "weapon_m4a1_silencer", "weapon_sg556", "weapon_awp", "weapon_g3sg1", "weapon_scar20", "weapon_ssg08", "weapon_m249", "weapon_mag7", "weapon_negev", "weapon_nova", "weapon_sawedoff", "weapon_xm1014", "weapon_taser", "weapon_bayonet", "weapon_knife_butterfly", "weapon_knife_canis", "weapon_knife_cord", "weapon_knife_css", "weapon_knife_falchion", "weapon_knife_flip", "weapon_knife_gut", "weapon_knife_gypsy_jackknife", "weapon_knife_karambit", "weapon_knife_kukri", "weapon_knife_m9_bayonet", "weapon_knife_outdoor", "weapon_knife_push", "weapon_knife_skeleton", "weapon_knife_stiletto", "weapon_knife_survival_bowie", "weapon_knife_tactical", "weapon_knife_ursus", "weapon_knife_widowmaker", "leather_handwraps", "motorcycle_gloves", "slick_gloves", "specialist_gloves", "sporty_gloves", "studded_bloodhound_gloves", "studded_brokenfang_gloves", "studded_hydra_gloves"];
38
+ export type KnownWeaponId = (typeof WEAPON_IDS)[number];
39
+ /**
40
+ * `'weapon_ak47'`.
41
+ *
42
+ * The `(string & {})` arm is deliberate and is not a widening mistake: it keeps autocomplete listing
43
+ * the 71 known ids while still ACCEPTING an id this build has never heard of, so a new weapon in a
44
+ * fresh export renders the day it ships rather than the day the package is upgraded. An unknown id
45
+ * that the asset export also does not know resolves to no model, which surfaces as `unknown-weapon`.
46
+ */
47
+ export type WeaponId = KnownWeaponId | (string & {});
48
+ /**
49
+ * Item definition index → econ item id. See the file header for why this is checked in.
50
+ *
51
+ * The 20 knives are listed under their `weapon_*` id rather than the `sfui_wpnhud_*` alias that also
52
+ * appears on their vanilla row; both resolve to the same GLB.
53
+ */
54
+ export declare const WEAPON_ID_BY_DEFINDEX: Readonly<Record<number, KnownWeaponId>>;
55
+ /**
56
+ * ─────────────────────────────────────────────────────────────────────────────────────────────
57
+ * *** THE HUD ALIASES — the difference between a knife and an empty canvas. ***
58
+ *
59
+ * `@skinhub/cdn`'s `skins.json` carries TWO ids for every knife. The painted rows say
60
+ * `weapon.id === 'weapon_bayonet'`; the ONE vanilla row per knife says
61
+ * `weapon.id === 'sfui_wpnhud_knifebayonet'` — the HUD string, not the item name. Both share
62
+ * `weapon.weapon_id === 500`. There are 83 distinct `weapon.id` values across 63 defindexes, and the
63
+ * 20 extras are exactly this.
64
+ *
65
+ * So an integrator doing the most natural thing there is —
66
+ *
67
+ * const row = findSkin(skins, { defindex, paintindex })
68
+ * <SkinViewer weaponId={row.weapon.id} … />
69
+ *
70
+ * — hands this component a HUD string for all twenty vanilla knives and nothing else.
71
+ *
72
+ * *** THE ALIAS IS ACCEPTED AND FOLDED, NOT REJECTED. *** The mapping is 1:1 and unambiguous, the
73
+ * renderer's own model table already resolves the same twenty strings, and failing on a value the data
74
+ * package hands you from its documented API would only mean the two packages disagree about what a
75
+ * Bayonet is called. Folding it here additionally makes {@link defindexForWeaponId} answer for those
76
+ * rows, so an inspect link built back out of a vanilla knife carries 500 rather than 0.
77
+ *
78
+ * An id that is neither a known weapon nor a known alias still fails VISIBLY — `<SkinViewer>` reports
79
+ * `unknown-weapon` and renders its error state naming the string it could not resolve. Rendering
80
+ * nothing, silently, is the one outcome this table exists to prevent.
81
+ *
82
+ * Deliberately NOT in {@link WEAPON_IDS}: these are accepted, not recommended, and an editor should
83
+ * not offer `sfui_wpnhud_knifekaram` to somebody typing a weapon id.
84
+ */
85
+ export declare const WEAPON_ID_ALIASES: Readonly<Record<string, KnownWeaponId>>;
86
+ /**
87
+ * Fold a HUD alias onto the item id. Everything else passes through untouched, including ids this
88
+ * build has never heard of — see {@link WeaponId} for why an unknown id is not an error here.
89
+ */
90
+ export declare const normalizeWeaponId: (weapon: WeaponId) => WeaponId;
91
+ /** True for an id this build can name. `false` is what makes `<SkinViewer>` report `unknown-weapon`. */
92
+ export declare const isKnownWeaponId: (weapon: WeaponId) => boolean;
93
+ /**
94
+ * `7` → `'weapon_ak47'`, or `undefined` for a defindex this build does not know.
95
+ *
96
+ * NEVER RETURNS AN ALIAS — the table is keyed on `weapon_id`, which is the whole reason to build it
97
+ * that way rather than off the first `weapon.id` a scan happens to hit.
98
+ */
99
+ export declare const weaponIdForDefindex: (defindex: number) => KnownWeaponId | undefined;
100
+ /**
101
+ * `'weapon_ak47'` → `7`, or `undefined`.
102
+ *
103
+ * The inverse, for building an inspect link back out of what the viewer is showing — see
104
+ * {@link toPlacement}, exported from the package root. Resolves HUD aliases, so a vanilla Bayonet round-trips to 500 rather than to 0.
105
+ */
106
+ export declare const defindexForWeaponId: (weapon: WeaponId) => number | undefined;
107
+ /** Gloves take a different renderer and carry no stickers, charm, counter or name plate. */
108
+ export declare const isGloveId: (weapon: WeaponId) => boolean;
109
+ //# sourceMappingURL=weapons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weapons.d.ts","sourceRoot":"","sources":["../src/weapons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,2tCA0Eb,CAAA;AAEV,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAEvD;;;;;;;GAOG;AACH,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAgEzE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAqBrE,CAAA;AAID;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,QAAQ,KAAG,QAA+C,CAAA;AAEpG,wGAAwG;AACxG,eAAO,MAAM,eAAe,GAAI,QAAQ,QAAQ,KAAG,OAA0D,CAAA;AAM7G;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,MAAM,KAAG,aAAa,GAAG,SAA4C,CAAA;AAEnH;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,GAAI,QAAQ,QAAQ,KAAG,MAAM,GAAG,SACf,CAAA;AAEjD,4FAA4F;AAC5F,eAAO,MAAM,SAAS,GAAI,QAAQ,QAAQ,KAAG,OAAuE,CAAA"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * WHICH WEAPON, said two ways — and the table that makes them the same question.
3
+ *
4
+ * An integrator holds one of two things. Either a row out of `@skinhub/cdn`'s `skins.json`, whose
5
+ * `weapon.id` is `'weapon_ak47'`, or a decoded inspect link, whose `defindex` is `7`. Both are the
6
+ * AK-47. `<SkinViewer>` takes either — {@link WeaponId} on the `weapon` prop, or the number on
7
+ * `defindex` — and this is the only place the two are reconciled.
8
+ *
9
+ * ─────────────────────────────────────────────────────────────────────────────────────────────
10
+ * WHY THIS TABLE IS CHECKED IN AND NOT FETCHED, when the whole premise of `@skinhub/cdn` is that
11
+ * data derived from the CDN has to be derived at runtime.
12
+ *
13
+ * That rule is about data that CHANGES — a new finish, a new sticker capsule, a new agent. This is
14
+ * not that. A defindex is Valve's item definition index: `weapon_ak47` has been 7 since 2013 and
15
+ * cannot become anything else without breaking every inventory in the game. The table is 63 rows and
16
+ * it is derived from the export itself (`data/skins.json`, `weapon.weapon_id` → `weapon.id`,
17
+ * generated 2026-08-15, verified: 63 distinct defindexes, no defindex mapping to two DIFFERENT
18
+ * weapons — the 20 knives each carry a second `sfui_wpnhud_*` alias for their vanilla row, which
19
+ * `getWeaponModelPath` already resolves to the same model).
20
+ *
21
+ * The alternative is fetching a 4.4 MB `skins.json` to answer "what is 7", which would make the
22
+ * inspect-link path — the one the product exists for — cost four megabytes before the first frame.
23
+ *
24
+ * WHAT GOING STALE LOOKS LIKE, stated rather than discovered: Valve ships a new weapon, an integrator
25
+ * passes its defindex, and {@link weaponIdForDefindex} returns `undefined`. `<SkinViewer>` then
26
+ * reports `unknown-weapon` through `onError` and renders its error state, naming the number it could
27
+ * not resolve. That is a legible failure with an obvious fix (pass `weapon` instead, or upgrade), not
28
+ * a blank canvas.
29
+ */
30
+ /**
31
+ * Every weapon the viewer can render, as the CS2 econ item id.
32
+ *
33
+ * This is `skin.weapon.id` in `@skinhub/cdn`'s `skins.json` rows, verbatim — so
34
+ * `<SkinViewer weapon={row.weapon.id} paintIndex={…} />` typechecks against a catalogue row with no
35
+ * conversion. Type it into an editor and autocomplete lists all 71.
36
+ */
37
+ export const WEAPON_IDS = [
38
+ // Pistols
39
+ 'weapon_cz75a',
40
+ 'weapon_deagle',
41
+ 'weapon_elite',
42
+ 'weapon_fiveseven',
43
+ 'weapon_glock',
44
+ 'weapon_hkp2000',
45
+ 'weapon_p250',
46
+ 'weapon_revolver',
47
+ 'weapon_tec9',
48
+ 'weapon_usp_silencer',
49
+ // SMGs
50
+ 'weapon_bizon',
51
+ 'weapon_mac10',
52
+ 'weapon_mp5sd',
53
+ 'weapon_mp7',
54
+ 'weapon_mp9',
55
+ 'weapon_p90',
56
+ 'weapon_ump45',
57
+ // Rifles
58
+ 'weapon_ak47',
59
+ 'weapon_aug',
60
+ 'weapon_famas',
61
+ 'weapon_galilar',
62
+ 'weapon_m4a1',
63
+ 'weapon_m4a1_silencer',
64
+ 'weapon_sg556',
65
+ // Snipers
66
+ 'weapon_awp',
67
+ 'weapon_g3sg1',
68
+ 'weapon_scar20',
69
+ 'weapon_ssg08',
70
+ // Heavy
71
+ 'weapon_m249',
72
+ 'weapon_mag7',
73
+ 'weapon_negev',
74
+ 'weapon_nova',
75
+ 'weapon_sawedoff',
76
+ 'weapon_xm1014',
77
+ // Other
78
+ 'weapon_taser',
79
+ // Knives
80
+ 'weapon_bayonet',
81
+ 'weapon_knife_butterfly',
82
+ 'weapon_knife_canis',
83
+ 'weapon_knife_cord',
84
+ 'weapon_knife_css',
85
+ 'weapon_knife_falchion',
86
+ 'weapon_knife_flip',
87
+ 'weapon_knife_gut',
88
+ 'weapon_knife_gypsy_jackknife',
89
+ 'weapon_knife_karambit',
90
+ 'weapon_knife_kukri',
91
+ 'weapon_knife_m9_bayonet',
92
+ 'weapon_knife_outdoor',
93
+ 'weapon_knife_push',
94
+ 'weapon_knife_skeleton',
95
+ 'weapon_knife_stiletto',
96
+ 'weapon_knife_survival_bowie',
97
+ 'weapon_knife_tactical',
98
+ 'weapon_knife_ursus',
99
+ 'weapon_knife_widowmaker',
100
+ // Gloves. A pair of gloves is the SUBJECT here — the thing on screen, framed and orbitable.
101
+ // Putting a pair ON an operator in the `operator`/`firstPerson` views is not supported; see
102
+ // `SkinViewerProps.operator`.
103
+ 'leather_handwraps',
104
+ 'motorcycle_gloves',
105
+ 'slick_gloves',
106
+ 'specialist_gloves',
107
+ 'sporty_gloves',
108
+ 'studded_bloodhound_gloves',
109
+ 'studded_brokenfang_gloves',
110
+ 'studded_hydra_gloves',
111
+ ];
112
+ /**
113
+ * Item definition index → econ item id. See the file header for why this is checked in.
114
+ *
115
+ * The 20 knives are listed under their `weapon_*` id rather than the `sfui_wpnhud_*` alias that also
116
+ * appears on their vanilla row; both resolve to the same GLB.
117
+ */
118
+ export const WEAPON_ID_BY_DEFINDEX = {
119
+ 1: 'weapon_deagle',
120
+ 2: 'weapon_elite',
121
+ 3: 'weapon_fiveseven',
122
+ 4: 'weapon_glock',
123
+ 7: 'weapon_ak47',
124
+ 8: 'weapon_aug',
125
+ 9: 'weapon_awp',
126
+ 10: 'weapon_famas',
127
+ 11: 'weapon_g3sg1',
128
+ 13: 'weapon_galilar',
129
+ 14: 'weapon_m249',
130
+ 16: 'weapon_m4a1',
131
+ 17: 'weapon_mac10',
132
+ 19: 'weapon_p90',
133
+ 23: 'weapon_mp5sd',
134
+ 24: 'weapon_ump45',
135
+ 25: 'weapon_xm1014',
136
+ 26: 'weapon_bizon',
137
+ 27: 'weapon_mag7',
138
+ 28: 'weapon_negev',
139
+ 29: 'weapon_sawedoff',
140
+ 30: 'weapon_tec9',
141
+ 31: 'weapon_taser',
142
+ 32: 'weapon_hkp2000',
143
+ 33: 'weapon_mp7',
144
+ 34: 'weapon_mp9',
145
+ 35: 'weapon_nova',
146
+ 36: 'weapon_p250',
147
+ 38: 'weapon_scar20',
148
+ 39: 'weapon_sg556',
149
+ 40: 'weapon_ssg08',
150
+ 60: 'weapon_m4a1_silencer',
151
+ 61: 'weapon_usp_silencer',
152
+ 63: 'weapon_cz75a',
153
+ 64: 'weapon_revolver',
154
+ 500: 'weapon_bayonet',
155
+ 503: 'weapon_knife_css',
156
+ 505: 'weapon_knife_flip',
157
+ 506: 'weapon_knife_gut',
158
+ 507: 'weapon_knife_karambit',
159
+ 508: 'weapon_knife_m9_bayonet',
160
+ 509: 'weapon_knife_tactical',
161
+ 512: 'weapon_knife_falchion',
162
+ 514: 'weapon_knife_survival_bowie',
163
+ 515: 'weapon_knife_butterfly',
164
+ 516: 'weapon_knife_push',
165
+ 517: 'weapon_knife_cord',
166
+ 518: 'weapon_knife_canis',
167
+ 519: 'weapon_knife_ursus',
168
+ 520: 'weapon_knife_gypsy_jackknife',
169
+ 521: 'weapon_knife_outdoor',
170
+ 522: 'weapon_knife_stiletto',
171
+ 523: 'weapon_knife_widowmaker',
172
+ 525: 'weapon_knife_skeleton',
173
+ 526: 'weapon_knife_kukri',
174
+ 4725: 'studded_brokenfang_gloves',
175
+ 5027: 'studded_bloodhound_gloves',
176
+ 5030: 'sporty_gloves',
177
+ 5031: 'slick_gloves',
178
+ 5032: 'leather_handwraps',
179
+ 5033: 'motorcycle_gloves',
180
+ 5034: 'specialist_gloves',
181
+ 5035: 'studded_hydra_gloves',
182
+ };
183
+ /**
184
+ * ─────────────────────────────────────────────────────────────────────────────────────────────
185
+ * *** THE HUD ALIASES — the difference between a knife and an empty canvas. ***
186
+ *
187
+ * `@skinhub/cdn`'s `skins.json` carries TWO ids for every knife. The painted rows say
188
+ * `weapon.id === 'weapon_bayonet'`; the ONE vanilla row per knife says
189
+ * `weapon.id === 'sfui_wpnhud_knifebayonet'` — the HUD string, not the item name. Both share
190
+ * `weapon.weapon_id === 500`. There are 83 distinct `weapon.id` values across 63 defindexes, and the
191
+ * 20 extras are exactly this.
192
+ *
193
+ * So an integrator doing the most natural thing there is —
194
+ *
195
+ * const row = findSkin(skins, { defindex, paintindex })
196
+ * <SkinViewer weaponId={row.weapon.id} … />
197
+ *
198
+ * — hands this component a HUD string for all twenty vanilla knives and nothing else.
199
+ *
200
+ * *** THE ALIAS IS ACCEPTED AND FOLDED, NOT REJECTED. *** The mapping is 1:1 and unambiguous, the
201
+ * renderer's own model table already resolves the same twenty strings, and failing on a value the data
202
+ * package hands you from its documented API would only mean the two packages disagree about what a
203
+ * Bayonet is called. Folding it here additionally makes {@link defindexForWeaponId} answer for those
204
+ * rows, so an inspect link built back out of a vanilla knife carries 500 rather than 0.
205
+ *
206
+ * An id that is neither a known weapon nor a known alias still fails VISIBLY — `<SkinViewer>` reports
207
+ * `unknown-weapon` and renders its error state naming the string it could not resolve. Rendering
208
+ * nothing, silently, is the one outcome this table exists to prevent.
209
+ *
210
+ * Deliberately NOT in {@link WEAPON_IDS}: these are accepted, not recommended, and an editor should
211
+ * not offer `sfui_wpnhud_knifekaram` to somebody typing a weapon id.
212
+ */
213
+ export const WEAPON_ID_ALIASES = {
214
+ sfui_wpnhud_knifebayonet: 'weapon_bayonet',
215
+ sfui_wpnhud_knife_butterfly: 'weapon_knife_butterfly',
216
+ sfui_wpnhud_knife_canis: 'weapon_knife_canis',
217
+ sfui_wpnhud_knife_cord: 'weapon_knife_cord',
218
+ sfui_wpnhud_knifecss: 'weapon_knife_css',
219
+ sfui_wpnhud_knife_falchion_advanced: 'weapon_knife_falchion',
220
+ sfui_wpnhud_knifeflip: 'weapon_knife_flip',
221
+ sfui_wpnhud_knifegut: 'weapon_knife_gut',
222
+ sfui_wpnhud_knife_gypsy_jackknife: 'weapon_knife_gypsy_jackknife',
223
+ sfui_wpnhud_knifekaram: 'weapon_knife_karambit',
224
+ sfui_wpnhud_knife_kukri: 'weapon_knife_kukri',
225
+ sfui_wpnhud_knifem9: 'weapon_knife_m9_bayonet',
226
+ sfui_wpnhud_knife_outdoor: 'weapon_knife_outdoor',
227
+ sfui_wpnhud_knife_push: 'weapon_knife_push',
228
+ sfui_wpnhud_knife_skeleton: 'weapon_knife_skeleton',
229
+ sfui_wpnhud_knife_stiletto: 'weapon_knife_stiletto',
230
+ sfui_wpnhud_knife_survival_bowie: 'weapon_knife_survival_bowie',
231
+ sfui_wpnhud_knifetactical: 'weapon_knife_tactical',
232
+ sfui_wpnhud_knife_ursus: 'weapon_knife_ursus',
233
+ sfui_wpnhud_knife_widowmaker: 'weapon_knife_widowmaker',
234
+ };
235
+ const KNOWN_WEAPON_IDS = new Set(WEAPON_IDS);
236
+ /**
237
+ * Fold a HUD alias onto the item id. Everything else passes through untouched, including ids this
238
+ * build has never heard of — see {@link WeaponId} for why an unknown id is not an error here.
239
+ */
240
+ export const normalizeWeaponId = (weapon) => WEAPON_ID_ALIASES[weapon] ?? weapon;
241
+ /** True for an id this build can name. `false` is what makes `<SkinViewer>` report `unknown-weapon`. */
242
+ export const isKnownWeaponId = (weapon) => KNOWN_WEAPON_IDS.has(normalizeWeaponId(weapon));
243
+ const DEFINDEX_BY_WEAPON_ID = Object.fromEntries(Object.entries(WEAPON_ID_BY_DEFINDEX).map(([defindex, id]) => [id, Number(defindex)]));
244
+ /**
245
+ * `7` → `'weapon_ak47'`, or `undefined` for a defindex this build does not know.
246
+ *
247
+ * NEVER RETURNS AN ALIAS — the table is keyed on `weapon_id`, which is the whole reason to build it
248
+ * that way rather than off the first `weapon.id` a scan happens to hit.
249
+ */
250
+ export const weaponIdForDefindex = (defindex) => WEAPON_ID_BY_DEFINDEX[defindex];
251
+ /**
252
+ * `'weapon_ak47'` → `7`, or `undefined`.
253
+ *
254
+ * The inverse, for building an inspect link back out of what the viewer is showing — see
255
+ * {@link toPlacement}, exported from the package root. Resolves HUD aliases, so a vanilla Bayonet round-trips to 500 rather than to 0.
256
+ */
257
+ export const defindexForWeaponId = (weapon) => DEFINDEX_BY_WEAPON_ID[normalizeWeaponId(weapon)];
258
+ /** Gloves take a different renderer and carry no stickers, charm, counter or name plate. */
259
+ export const isGloveId = (weapon) => weapon.endsWith('_gloves') || weapon === 'leather_handwraps';
260
+ //# sourceMappingURL=weapons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weapons.js","sourceRoot":"","sources":["../src/weapons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,UAAU;IACV,cAAc;IACd,eAAe;IACf,cAAc;IACd,kBAAkB;IAClB,cAAc;IACd,gBAAgB;IAChB,aAAa;IACb,iBAAiB;IACjB,aAAa;IACb,qBAAqB;IACrB,OAAO;IACP,cAAc;IACd,cAAc;IACd,cAAc;IACd,YAAY;IACZ,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,SAAS;IACT,aAAa;IACb,YAAY;IACZ,cAAc;IACd,gBAAgB;IAChB,aAAa;IACb,sBAAsB;IACtB,cAAc;IACd,UAAU;IACV,YAAY;IACZ,cAAc;IACd,eAAe;IACf,cAAc;IACd,QAAQ;IACR,aAAa;IACb,aAAa;IACb,cAAc;IACd,aAAa;IACb,iBAAiB;IACjB,eAAe;IACf,QAAQ;IACR,cAAc;IACd,SAAS;IACT,gBAAgB;IAChB,wBAAwB;IACxB,oBAAoB;IACpB,mBAAmB;IACnB,kBAAkB;IAClB,uBAAuB;IACvB,mBAAmB;IACnB,kBAAkB;IAClB,8BAA8B;IAC9B,uBAAuB;IACvB,oBAAoB;IACpB,yBAAyB;IACzB,sBAAsB;IACtB,mBAAmB;IACnB,uBAAuB;IACvB,uBAAuB;IACvB,6BAA6B;IAC7B,uBAAuB;IACvB,oBAAoB;IACpB,yBAAyB;IACzB,4FAA4F;IAC5F,4FAA4F;IAC5F,8BAA8B;IAC9B,mBAAmB;IACnB,mBAAmB;IACnB,cAAc;IACd,mBAAmB;IACnB,eAAe;IACf,2BAA2B;IAC3B,2BAA2B;IAC3B,sBAAsB;CACb,CAAA;AAcV;;;;;GAKG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAA4C;IAC7E,CAAC,EAAE,eAAe;IAClB,CAAC,EAAE,cAAc;IACjB,CAAC,EAAE,kBAAkB;IACrB,CAAC,EAAE,cAAc;IACjB,CAAC,EAAE,aAAa;IAChB,CAAC,EAAE,YAAY;IACf,CAAC,EAAE,YAAY;IACf,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,gBAAgB;IACpB,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,YAAY;IAChB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,eAAe;IACnB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,iBAAiB;IACrB,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,gBAAgB;IACpB,EAAE,EAAE,YAAY;IAChB,EAAE,EAAE,YAAY;IAChB,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,aAAa;IACjB,EAAE,EAAE,eAAe;IACnB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,sBAAsB;IAC1B,EAAE,EAAE,qBAAqB;IACzB,EAAE,EAAE,cAAc;IAClB,EAAE,EAAE,iBAAiB;IACrB,GAAG,EAAE,gBAAgB;IACrB,GAAG,EAAE,kBAAkB;IACvB,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,kBAAkB;IACvB,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,yBAAyB;IAC9B,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,6BAA6B;IAClC,GAAG,EAAE,wBAAwB;IAC7B,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,oBAAoB;IACzB,GAAG,EAAE,oBAAoB;IACzB,GAAG,EAAE,8BAA8B;IACnC,GAAG,EAAE,sBAAsB;IAC3B,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,yBAAyB;IAC9B,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,oBAAoB;IACzB,IAAI,EAAE,2BAA2B;IACjC,IAAI,EAAE,2BAA2B;IACjC,IAAI,EAAE,eAAe;IACrB,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE,mBAAmB;IACzB,IAAI,EAAE,mBAAmB;IACzB,IAAI,EAAE,mBAAmB;IACzB,IAAI,EAAE,sBAAsB;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAA4C;IACzE,wBAAwB,EAAE,gBAAgB;IAC1C,2BAA2B,EAAE,wBAAwB;IACrD,uBAAuB,EAAE,oBAAoB;IAC7C,sBAAsB,EAAE,mBAAmB;IAC3C,oBAAoB,EAAE,kBAAkB;IACxC,mCAAmC,EAAE,uBAAuB;IAC5D,qBAAqB,EAAE,mBAAmB;IAC1C,oBAAoB,EAAE,kBAAkB;IACxC,iCAAiC,EAAE,8BAA8B;IACjE,sBAAsB,EAAE,uBAAuB;IAC/C,uBAAuB,EAAE,oBAAoB;IAC7C,mBAAmB,EAAE,yBAAyB;IAC9C,yBAAyB,EAAE,sBAAsB;IACjD,sBAAsB,EAAE,mBAAmB;IAC3C,0BAA0B,EAAE,uBAAuB;IACnD,0BAA0B,EAAE,uBAAuB;IACnD,gCAAgC,EAAE,6BAA6B;IAC/D,yBAAyB,EAAE,uBAAuB;IAClD,uBAAuB,EAAE,oBAAoB;IAC7C,4BAA4B,EAAE,yBAAyB;CACvD,CAAA;AAED,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAS,UAAU,CAAC,CAAA;AAEzE;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAgB,EAAY,EAAE,CAAC,iBAAiB,CAAC,MAAM,CAAC,IAAI,MAAM,CAAA;AAEpG,wGAAwG;AACxG,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAgB,EAAW,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAA;AAE7G,MAAM,qBAAqB,GAAqC,MAAM,CAAC,WAAW,CACjF,MAAM,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CACrF,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,QAAgB,EAA6B,EAAE,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAA;AAEnH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAgB,EAAsB,EAAE,CAC3E,qBAAqB,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAA;AAEjD,4FAA4F;AAC5F,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAAgB,EAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,MAAM,KAAK,mBAAmB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@skinhub/viewer",
3
+ "version": "0.1.1",
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
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "bun": "./src/index.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./protocol": {
15
+ "types": "./dist/protocol.d.ts",
16
+ "bun": "./src/protocol.ts",
17
+ "import": "./dist/protocol.js"
18
+ },
19
+ "./weapons": {
20
+ "types": "./dist/weapons.d.ts",
21
+ "bun": "./src/weapons.ts",
22
+ "import": "./dist/weapons.js"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "files": ["dist", "src", "README.md", "EMBED.md"],
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/SkinHubgg/skinhub-viewer.git"
31
+ },
32
+ "homepage": "https://github.com/SkinHubgg/skinhub-viewer#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/SkinHubgg/skinhub-viewer/issues"
35
+ },
36
+ "keywords": ["cs2", "csgo", "counter-strike", "skin", "viewer", "inspect-link", "react", "iframe", "embed"],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "engines": {
41
+ "node": ">=18"
42
+ },
43
+ "scripts": {
44
+ "typecheck": "tsc --noEmit",
45
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
46
+ "test": "bun test",
47
+ "test:live": "SKINHUB_VIEWER_LIVE=1 bun test test/live.test.ts",
48
+ "test:full": "bun test && bun run test:live",
49
+ "release": "bash scripts/release.sh",
50
+ "prepare": "bun run build",
51
+ "prepublishOnly": "bun run typecheck && bun run build"
52
+ },
53
+ "peerDependencies": {
54
+ "react": ">=18"
55
+ },
56
+ "dependencies": {
57
+ "@skinhub/cdn": ">=0.1.3"
58
+ },
59
+ "devDependencies": {
60
+ "@types/bun": "latest",
61
+ "@types/react": "^19",
62
+ "react": "^19",
63
+ "typescript": "^5"
64
+ }
65
+ }