@cntrl-site/sdk-nextjs 1.6.2 → 1.6.4

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.
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useItemFXData = void 0;
4
+ const sdk_1 = require("@cntrl-site/sdk");
5
+ const useKeyframeValue_1 = require("./useKeyframeValue");
6
+ const baseVariables = `precision mediump float;
7
+ uniform sampler2D u_image;
8
+ uniform sampler2D u_pattern;
9
+ uniform vec2 u_imgDimensions;
10
+ uniform vec2 u_patternDimensions;
11
+ uniform float u_time;
12
+ uniform vec2 u_cursor;
13
+ varying vec2 v_texCoord;`;
14
+ function useItemFXData(item, sectionId) {
15
+ const { fragmentShader: shaderBody, FXControls } = item.commonParams;
16
+ const controls = FXControls !== null && FXControls !== void 0 ? FXControls : [];
17
+ const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
18
+ .join('\n');
19
+ const fragmentShader = `${baseVariables}\n${controlsVariables}\n${shaderBody}`;
20
+ const controlsValues = (0, useKeyframeValue_1.useKeyframeValue)(item, sdk_1.KeyframeType.FXParams, () => {
21
+ const defaultValue = controls.reduce((acc, control) => {
22
+ if (Array.isArray(control.value)) {
23
+ acc[control.shaderParam] = control.value[0];
24
+ }
25
+ else {
26
+ acc[control.shaderParam] = control.value;
27
+ }
28
+ return acc;
29
+ }, {});
30
+ return defaultValue;
31
+ }, (animator, scroll, value) => animator.getFXParams(value, scroll), sectionId);
32
+ return {
33
+ fragmentShader,
34
+ controlsValues
35
+ };
36
+ }
37
+ exports.useItemFXData = useItemFXData;
@@ -11,7 +11,7 @@ const KeyframesContext_1 = require("../provider/KeyframesContext");
11
11
  const Animator_1 = require("../utils/Animator/Animator");
12
12
  const useLayoutContext_1 = require("../components/useLayoutContext");
13
13
  const emptyDeps = [];
14
- const useKeyframeValue = (item, type, itemParamsGetter, animatorGetter, sectionId, deps = emptyDeps) => {
14
+ function useKeyframeValue(item, type, itemParamsGetter, animatorGetter, sectionId, deps = emptyDeps) {
15
15
  const animatorGetterRef = (0, react_1.useRef)(animatorGetter);
16
16
  const itemParamsGetterRef = (0, react_1.useRef)(itemParamsGetter);
17
17
  animatorGetterRef.current = animatorGetter;
@@ -72,5 +72,5 @@ const useKeyframeValue = (item, type, itemParamsGetter, animatorGetter, sectionI
72
72
  });
73
73
  }, [handleKeyframeValue, articleRectObserver, animator]);
74
74
  return keyframes.length ? adjustedValue : paramValue;
75
- };
75
+ }
76
76
  exports.useKeyframeValue = useKeyframeValue;
@@ -69,11 +69,15 @@ const CompoundChild = ({ item, sectionId, isParentVisible = true }) => {
69
69
  ? { height: `${sizingAxis.y === 'manual'
70
70
  ? (0, getCompoundBondaryStyles_1.getCompoundHeight)(compoundSettings, height)
71
71
  : 'unset'}` }
72
- : {})), (scale !== undefined && compoundSettings ? { transform: `scale(${scale}) ${(0, getCompoundBondaryStyles_1.getCompoundTransform)(compoundSettings)}` } : {})), { transition: (_k = stateProps === null || stateProps === void 0 ? void 0 : stateProps.transition) !== null && _k !== void 0 ? _k : 'none', cursor: hasClickTriggers ? 'pointer' : 'unset', pointerEvents: allowPointerEvents ? 'auto' : 'none' }) }, triggers, { children: [(0, jsx_runtime_1.jsx)(RichTextWrapper_1.RichTextWrapper, { isRichText: isRichText, transformOrigin: transformOrigin, children: (0, jsx_runtime_1.jsx)(ItemComponent, { item: item, sectionId: sectionId, interactionCtrl: interactionCtrl, onVisibilityChange: handleVisibilityChange }) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
72
+ : {})), (compoundSettings ? { transform: `${(0, getCompoundBondaryStyles_1.getCompoundTransform)(compoundSettings)}` } : {})), { transition: (_k = stateProps === null || stateProps === void 0 ? void 0 : stateProps.transition) !== null && _k !== void 0 ? _k : 'none', cursor: hasClickTriggers ? 'pointer' : 'unset', pointerEvents: allowPointerEvents ? 'auto' : 'none' }) }, triggers, { children: [(0, jsx_runtime_1.jsx)("div", { className: `item-${item.id}-inner`, style: Object.assign({}, (scale !== undefined ? { transform: `scale(${scale})` } : {})), children: (0, jsx_runtime_1.jsx)(RichTextWrapper_1.RichTextWrapper, { isRichText: isRichText, transformOrigin: transformOrigin, children: (0, jsx_runtime_1.jsx)(ItemComponent, { item: item, sectionId: sectionId, interactionCtrl: interactionCtrl, onVisibilityChange: handleVisibilityChange }) }) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
73
73
  ${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, hidden, compoundSettings, layoutParams]) => {
74
74
  const sizingAxis = (0, useSizing_1.parseSizing)(layoutParams.sizing);
75
75
  const scaleAnchor = area.scaleAnchor;
76
76
  return (`
77
+ .item-${item.id}-inner {
78
+ transform-origin: ${ScaleAnchorMap_1.ScaleAnchorMap[scaleAnchor]};
79
+ transform: scale(${area.scale});
80
+ }
77
81
  .item-${item.id} {
78
82
  position: absolute;
79
83
  top: ${(0, getCompoundBondaryStyles_1.getCompoundTop)(compoundSettings, area.top)};
@@ -84,8 +88,7 @@ const CompoundChild = ({ item, sectionId, isParentVisible = true }) => {
84
88
  ? `${(0, getCompoundBondaryStyles_1.getCompoundWidth)(compoundSettings, area.width, isRichText)}`
85
89
  : 'max-content'};
86
90
  height: ${sizingAxis.y === 'manual' ? `${(0, getCompoundBondaryStyles_1.getCompoundHeight)(compoundSettings, area.height)}` : 'unset'};
87
- transform-origin: ${ScaleAnchorMap_1.ScaleAnchorMap[scaleAnchor]};
88
- transform: scale(${area.scale}) ${(0, getCompoundBondaryStyles_1.getCompoundTransform)(compoundSettings)};
91
+ transform: ${(0, getCompoundBondaryStyles_1.getCompoundTransform)(compoundSettings)};
89
92
  z-index: ${area.zIndex};
90
93
  }
91
94
  `);
@@ -18,7 +18,7 @@ const useImageFx_1 = require("../../../utils/effects/useImageFx");
18
18
  const useElementRect_1 = require("../../../utils/useElementRect");
19
19
  const useLayoutContext_1 = require("../../useLayoutContext");
20
20
  const getStyleFromItemStateAndParams_1 = require("../../../utils/getStyleFromItemStateAndParams");
21
- const shaderBaseVars_1 = require("./shaderBaseVars");
21
+ const useItemFXData_1 = require("../../../common/useItemFXData");
22
22
  const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
23
23
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
24
24
  const id = (0, react_1.useId)();
@@ -28,18 +28,11 @@ const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
28
28
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
29
29
  const [wrapperRef, setWrapperRef] = (0, react_1.useState)(null);
30
30
  (0, useRegisterResize_1.useRegisterResize)(wrapperRef, onResize);
31
- const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
31
+ const { url, hasGLEffect } = item.commonParams;
32
32
  const fxCanvas = (0, react_1.useRef)(null);
33
33
  const isInitialRef = (0, react_1.useRef)(true);
34
- const controls = FXControls !== null && FXControls !== void 0 ? FXControls : [];
35
- const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
36
- .join('\n');
37
- const controlValues = controls.reduce((acc, control) => {
38
- acc[control.shaderParam] = control.value;
39
- return acc;
40
- }, {});
41
34
  const layoutValues = [item.area, item.layoutParams];
42
- const fullShaderCode = `${shaderBaseVars_1.baseVariables}\n${controlsVariables}\n${fragmentShader}`;
35
+ const { controlsValues, fragmentShader } = (0, useItemFXData_1.useItemFXData)(item, sectionId);
43
36
  const area = layoutId ? item.area[layoutId] : null;
44
37
  const exemplary = (_a = layouts === null || layouts === void 0 ? void 0 : layouts.find(l => l.id === layoutId)) === null || _a === void 0 ? void 0 : _a.exemplary;
45
38
  const width = area && exemplary ? area.width * exemplary : 0;
@@ -51,9 +44,8 @@ const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
51
44
  }, []);
52
45
  const isFXAllowed = (0, useImageFx_1.useImageFx)(fxCanvas.current, !!(hasGLEffect && !isInitialRef.current), {
53
46
  imageUrl: url,
54
- fragmentShader: fullShaderCode,
55
- cursor: FXCursor,
56
- controls: controlValues
47
+ fragmentShader,
48
+ controlsValues
57
49
  }, width, height);
58
50
  const rect = (0, useElementRect_1.useElementRect)(wrapperRef);
59
51
  const rectWidth = Math.floor((_b = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _b !== void 0 ? _b : 0);
@@ -17,9 +17,9 @@ const useRegisterResize_1 = require("../../../common/useRegisterResize");
17
17
  const useLayoutContext_1 = require("../../useLayoutContext");
18
18
  const ScrollPlaybackVideo_1 = require("../../ScrollPlaybackVideo");
19
19
  const getStyleFromItemStateAndParams_1 = require("../../../utils/getStyleFromItemStateAndParams");
20
- const shaderBaseVars_1 = require("./shaderBaseVars");
21
20
  const useVideoFx_1 = require("../../../utils/effects/useVideoFx");
22
21
  const useElementRect_1 = require("../../../utils/useElementRect");
22
+ const useItemFXData_1 = require("../../../common/useItemFXData");
23
23
  const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
24
24
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
25
25
  const id = (0, react_1.useId)();
@@ -30,20 +30,13 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
30
30
  const [ref, setRef] = (0, react_1.useState)(null);
31
31
  const videoRef = (0, react_1.useRef)(null);
32
32
  const fxCanvas = (0, react_1.useRef)(null);
33
- const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
33
+ const { url, hasGLEffect } = item.commonParams;
34
34
  const isInitialRef = (0, react_1.useRef)(true);
35
35
  const area = layoutId ? item.area[layoutId] : null;
36
36
  const exemplary = (_a = layouts === null || layouts === void 0 ? void 0 : layouts.find(l => l.id === layoutId)) === null || _a === void 0 ? void 0 : _a.exemplary;
37
37
  const width = area && exemplary ? area.width * exemplary : 0;
38
38
  const height = area && exemplary ? area.height * exemplary : 0;
39
- const controls = FXControls !== null && FXControls !== void 0 ? FXControls : [];
40
- const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
41
- .join('\n');
42
- const controlValues = controls.reduce((acc, control) => {
43
- acc[control.shaderParam] = control.value;
44
- return acc;
45
- }, {});
46
- const fullShaderCode = `${shaderBaseVars_1.baseVariables}\n${controlsVariables}\n${fragmentShader}`;
39
+ const { controlsValues, fragmentShader } = (0, useItemFXData_1.useItemFXData)(item, sectionId);
47
40
  const rect = (0, useElementRect_1.useElementRect)(ref);
48
41
  const rectWidth = Math.floor((_b = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _b !== void 0 ? _b : 0);
49
42
  const rectHeight = Math.floor((_c = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _c !== void 0 ? _c : 0);
@@ -67,9 +60,8 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
67
60
  }, []);
68
61
  const isFXAllowed = (0, useVideoFx_1.useVideoFx)(fxCanvas.current, !!(hasGLEffect && !isInitialRef.current), {
69
62
  videoUrl: url,
70
- fragmentShader: fullShaderCode,
71
- cursor: FXCursor,
72
- controls: controlValues
63
+ fragmentShader,
64
+ controls: controlsValues
73
65
  }, width, height);
74
66
  (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
75
67
  const inlineStyles = Object.assign(Object.assign(Object.assign(Object.assign({}, (radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {})), (strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {})), (borderColor ? { borderColor: `${borderColor.toCss()}` } : {})), { transition: (_k = videoStateParams === null || videoStateParams === void 0 ? void 0 : videoStateParams.transition) !== null && _k !== void 0 ? _k : 'none' });
@@ -5,7 +5,7 @@ const sdk_1 = require("@cntrl-site/sdk");
5
5
  const useKeyframeValue_1 = require("../../../common/useKeyframeValue");
6
6
  const useLayoutContext_1 = require("../../useLayoutContext");
7
7
  const DEFAULT_COLOR = 'rgba(0, 0, 0, 1)';
8
- const useFileItem = (item, sectionId) => {
8
+ function useFileItem(item, sectionId) {
9
9
  const layoutId = (0, useLayoutContext_1.useLayoutContext)();
10
10
  const radius = (0, useKeyframeValue_1.useKeyframeValue)(item, sdk_1.KeyframeType.BorderRadius, (item, layoutId) => {
11
11
  if (!layoutId)
@@ -38,5 +38,5 @@ const useFileItem = (item, sectionId) => {
38
38
  return 'blur' in layoutParams ? layoutParams.blur : 0;
39
39
  }, (animator, scroll, value) => value !== undefined ? animator.getBlur({ blur: value }, scroll).blur : undefined, sectionId, [layoutId]);
40
40
  return { radius, strokeWidth, opacity, strokeColor, blur };
41
- };
41
+ }
42
42
  exports.useFileItem = useFileItem;
@@ -16,6 +16,25 @@ class Animator {
16
16
  this.keyframesMap = createKeyframesMap();
17
17
  this.fillKeyframesMap();
18
18
  }
19
+ getFXParams(valuesMap, pos) {
20
+ const keyframes = this.keyframesMap[sdk_1.KeyframeType.FXParams];
21
+ if (!keyframes.length)
22
+ return valuesMap;
23
+ if (keyframes.length === 1) {
24
+ const [keyframe] = keyframes;
25
+ return Object.entries(valuesMap).reduce((acc, [key, fallbackValue]) => {
26
+ var _a;
27
+ acc[key] = (_a = keyframe.value[key]) !== null && _a !== void 0 ? _a : fallbackValue;
28
+ return acc;
29
+ }, {});
30
+ }
31
+ const { start, end } = this.getStartEnd(pos, keyframes);
32
+ return Object.entries(valuesMap).reduce((acc, [key, fallbackValue]) => {
33
+ var _a, _b;
34
+ acc[key] = (0, rangeMap_1.rangeMap)(pos, start.position, end.position, (_a = start.value[key]) !== null && _a !== void 0 ? _a : fallbackValue, (_b = end.value[key]) !== null && _b !== void 0 ? _b : fallbackValue, true);
35
+ return acc;
36
+ }, {});
37
+ }
19
38
  getDimensions(values, pos) {
20
39
  const keyframes = this.keyframesMap[sdk_1.KeyframeType.Dimensions];
21
40
  if (!keyframes || !keyframes.length)
@@ -266,6 +285,7 @@ function createKeyframesMap() {
266
285
  [sdk_1.KeyframeType.BackdropBlur]: [],
267
286
  [sdk_1.KeyframeType.LetterSpacing]: [],
268
287
  [sdk_1.KeyframeType.WordSpacing]: [],
269
- [sdk_1.KeyframeType.TextColor]: []
288
+ [sdk_1.KeyframeType.TextColor]: [],
289
+ [sdk_1.KeyframeType.FXParams]: []
270
290
  };
271
291
  }
@@ -6,22 +6,27 @@ const effects_1 = require("@cntrl-site/effects");
6
6
  const rangeMap_1 = require("../rangeMap");
7
7
  const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
8
8
  const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
9
- function useImageFx(canvas, enabled, { imageUrl, fragmentShader, cursor, controls }, width, height) {
9
+ function useImageFx(canvas, enabled, { imageUrl, fragmentShader, controlsValues }, width, height) {
10
10
  const [isFXAllowed, setIsFXAllowed] = (0, react_1.useState)(true);
11
- const mousePos = (0, react_1.useRef)([0.0, 0.0]);
12
11
  const imageTextureManager = (0, react_1.useMemo)(() => {
13
12
  if (!imageUrl || !enabled)
14
13
  return;
15
14
  return new effects_1.ImageTextureManager(imageUrl);
16
15
  }, [imageUrl, enabled]);
17
16
  const imageFx = (0, react_1.useMemo)(() => {
18
- if (!cursor || !imageTextureManager)
17
+ if (!imageTextureManager)
19
18
  return;
20
- const { type, x, y } = cursor;
21
- return new effects_1.MediaEffect(imageTextureManager, PATTERN_URL, PATTERN_2_URL, fragmentShader, Object.assign({ time: 0, cursor: type === 'mouse' ? mousePos.current : [x, y] }, controls), width, height);
19
+ return new effects_1.MediaEffect(imageTextureManager, PATTERN_URL, PATTERN_2_URL, fragmentShader, Object.assign({ time: 0, cursor: [0, 0] }, controlsValues), width, height);
22
20
  }, [imageTextureManager, fragmentShader, width, height]);
23
21
  (0, react_1.useEffect)(() => {
24
- if (!cursor || cursor.type !== 'mouse' || !canvas || !imageFx)
22
+ if (!imageFx || !controlsValues)
23
+ return;
24
+ for (const [key, value] of Object.entries(controlsValues)) {
25
+ imageFx.setParam(key, value);
26
+ }
27
+ }, [imageFx, controlsValues]);
28
+ (0, react_1.useEffect)(() => {
29
+ if (!canvas || !imageFx)
25
30
  return;
26
31
  const handleMouseMove = (evt) => {
27
32
  const rect = canvas.getBoundingClientRect();
@@ -33,7 +38,7 @@ function useImageFx(canvas, enabled, { imageUrl, fragmentShader, cursor, control
33
38
  return () => {
34
39
  window.removeEventListener('mousemove', handleMouseMove);
35
40
  };
36
- }, [canvas, imageFx, cursor === null || cursor === void 0 ? void 0 : cursor.type]);
41
+ }, [canvas, imageFx]);
37
42
  (0, react_1.useEffect)(() => {
38
43
  const gl = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('webgl2');
39
44
  if (!enabled || !canvas || !gl || !imageFx)
@@ -6,11 +6,10 @@ const effects_1 = require("@cntrl-site/effects");
6
6
  const rangeMap_1 = require("../rangeMap");
7
7
  const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
8
8
  const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
9
- function useVideoFx(canvas, enabled, { videoUrl, fragmentShader, cursor, controls }, width, height) {
9
+ function useVideoFx(canvas, enabled, { videoUrl, fragmentShader, controls }, width, height) {
10
10
  const [isFXAllowed, setIsFXAllowed] = (0, react_1.useState)(true);
11
11
  const [isReady, setIsReady] = (0, react_1.useState)(false);
12
12
  const active = enabled && isReady;
13
- const mousePos = (0, react_1.useRef)([0.0, 0.0]);
14
13
  const videoTextureManager = (0, react_1.useMemo)(() => {
15
14
  if (!videoUrl || !enabled)
16
15
  return;
@@ -20,10 +19,9 @@ function useVideoFx(canvas, enabled, { videoUrl, fragmentShader, cursor, control
20
19
  return new effects_1.VideoTextureManager(videoUrl, handlePlayError);
21
20
  }, [videoUrl, enabled]);
22
21
  const videoFx = (0, react_1.useMemo)(() => {
23
- if (!cursor || !videoTextureManager)
22
+ if (!videoTextureManager)
24
23
  return;
25
- const { type, x, y } = cursor;
26
- return new effects_1.MediaEffect(videoTextureManager, PATTERN_URL, PATTERN_2_URL, fragmentShader, Object.assign({ time: 0, cursor: type === 'mouse' ? mousePos.current : [x, y] }, controls), width, height);
24
+ return new effects_1.MediaEffect(videoTextureManager, PATTERN_URL, PATTERN_2_URL, fragmentShader, Object.assign({ time: 0, cursor: [0, 0] }, controls), width, height);
27
25
  }, [videoTextureManager, fragmentShader, width, height]);
28
26
  (0, react_1.useEffect)(() => {
29
27
  if (!videoTextureManager)
@@ -31,7 +29,7 @@ function useVideoFx(canvas, enabled, { videoUrl, fragmentShader, cursor, control
31
29
  videoTextureManager.onReadyStatusChange(setIsReady);
32
30
  }, [videoTextureManager]);
33
31
  (0, react_1.useEffect)(() => {
34
- if (!cursor || cursor.type !== 'mouse' || !canvas || !videoFx)
32
+ if (!canvas || !videoFx)
35
33
  return;
36
34
  const handleMouseMove = (evt) => {
37
35
  const rect = canvas.getBoundingClientRect();
@@ -43,7 +41,7 @@ function useVideoFx(canvas, enabled, { videoUrl, fragmentShader, cursor, control
43
41
  return () => {
44
42
  window.removeEventListener('mousemove', handleMouseMove);
45
43
  };
46
- }, [canvas, videoFx, cursor === null || cursor === void 0 ? void 0 : cursor.type]);
44
+ }, [canvas, videoFx]);
47
45
  (0, react_1.useEffect)(() => {
48
46
  const gl = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('webgl2');
49
47
  if (!active || !canvas || !gl || !videoFx)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "1.6.2",
3
+ "version": "1.6.4",
4
4
  "description": "SDK for Next.js",
5
5
  "author": "arsen@momdesign.nyc",
6
6
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  "@antfu/eslint-config": "^3.8.0",
32
32
  "@cntrl-site/color": "^1.0.0",
33
33
  "@cntrl-site/effects": "^1.3.2",
34
- "@cntrl-site/sdk": "^1.19.3",
34
+ "@cntrl-site/sdk": "^1.19.4",
35
35
  "@types/vimeo__player": "^2.18.0",
36
36
  "@vimeo/player": "^2.25.0",
37
37
  "html-react-parser": "^3.0.1",
@@ -0,0 +1,47 @@
1
+ import { ImageItem, KeyframeType, VideoItem } from '@cntrl-site/sdk';
2
+ import { useKeyframeValue } from './useKeyframeValue';
3
+
4
+ const baseVariables = `precision mediump float;
5
+ uniform sampler2D u_image;
6
+ uniform sampler2D u_pattern;
7
+ uniform vec2 u_imgDimensions;
8
+ uniform vec2 u_patternDimensions;
9
+ uniform float u_time;
10
+ uniform vec2 u_cursor;
11
+ varying vec2 v_texCoord;`;
12
+
13
+ export function useItemFXData(item: ImageItem | VideoItem, sectionId: string): FXData {
14
+ const { fragmentShader: shaderBody, FXControls } = item.commonParams;
15
+ const controls = FXControls ?? [];
16
+ const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
17
+ .join('\n');
18
+ const fragmentShader = `${baseVariables}\n${controlsVariables}\n${shaderBody}`;
19
+ const controlsValues = useKeyframeValue(
20
+ item,
21
+ KeyframeType.FXParams,
22
+ () => {
23
+ const defaultValue = controls.reduce<Record<string, ControlValue>>((acc, control) => {
24
+ if (Array.isArray(control.value)) {
25
+ acc[control.shaderParam] = control.value[0];
26
+ } else {
27
+ acc[control.shaderParam] = control.value;
28
+ }
29
+ return acc;
30
+ }, {});
31
+ return defaultValue;
32
+ },
33
+ (animator, scroll, value) => animator.getFXParams(value, scroll),
34
+ sectionId,
35
+ );
36
+ return {
37
+ fragmentShader,
38
+ controlsValues
39
+ };
40
+ }
41
+
42
+ type FXData = {
43
+ fragmentShader: string;
44
+ controlsValues: Record<string, ControlValue>;
45
+ }
46
+
47
+ type ControlValue = number;
@@ -10,14 +10,14 @@ export type AnimatorGetter<T> = (animator: Animator, scroll: number, value: T) =
10
10
  type ItemParamGetter<T> = (item: ItemAny, layoutId: string | undefined) => T;
11
11
  const emptyDeps: DependencyList = [];
12
12
 
13
- export const useKeyframeValue = <T>(
13
+ export function useKeyframeValue<T>(
14
14
  item: ItemAny,
15
15
  type: KeyframeType,
16
16
  itemParamsGetter: ItemParamGetter<T>,
17
17
  animatorGetter: AnimatorGetter<T>,
18
18
  sectionId: string,
19
19
  deps: DependencyList = emptyDeps
20
- ): T => {
20
+ ): T {
21
21
  const animatorGetterRef = useRef(animatorGetter);
22
22
  const itemParamsGetterRef = useRef(itemParamsGetter);
23
23
 
@@ -64,7 +64,7 @@ export const useKeyframeValue = <T>(
64
64
  if (!articleRectObserver || !animator) return;
65
65
  const scroll = articleRectObserver.getSectionScroll(sectionId);
66
66
  handleKeyframeValue(scroll);
67
- }, [articleRectObserver, handleKeyframeValue, animator])
67
+ }, [articleRectObserver, handleKeyframeValue, animator]);
68
68
 
69
69
  useEffect(() => {
70
70
  if (!articleRectObserver || !animator) return;
@@ -82,5 +82,4 @@ export const useKeyframeValue = <T>(
82
82
  });
83
83
  }, [handleKeyframeValue, articleRectObserver, animator]);
84
84
  return keyframes.length ? adjustedValue : paramValue;
85
- };
86
-
85
+ }
@@ -92,13 +92,18 @@ export const CompoundChild: FC<ChildItemProps> = ({ item, sectionId, isParentVis
92
92
  ? getCompoundHeight(compoundSettings, height)
93
93
  : 'unset'}` }
94
94
  : {}),
95
- ...(scale !== undefined && compoundSettings ? { transform: `scale(${scale}) ${getCompoundTransform(compoundSettings)}` } : {}),
95
+ ...(compoundSettings ? { transform: `${getCompoundTransform(compoundSettings)}` } : {}),
96
96
  transition: stateProps?.transition ?? 'none',
97
97
  cursor: hasClickTriggers ? 'pointer' : 'unset',
98
98
  pointerEvents: allowPointerEvents ? 'auto' : 'none'
99
99
  }}
100
100
  {...triggers}
101
101
  >
102
+ <div className={`item-${item.id}-inner`}
103
+ style={{
104
+ ...(scale !== undefined ? { transform: `scale(${scale})` } : {}),
105
+ }}
106
+ >
102
107
  <RichTextWrapper isRichText={isRichText} transformOrigin={transformOrigin}>
103
108
  <ItemComponent
104
109
  item={item}
@@ -107,11 +112,16 @@ export const CompoundChild: FC<ChildItemProps> = ({ item, sectionId, isParentVis
107
112
  onVisibilityChange={handleVisibilityChange}
108
113
  />
109
114
  </RichTextWrapper>
115
+ </div>
110
116
  <JSXStyle id={id}>{`
111
117
  ${getLayoutStyles(layouts, layoutValues, ([area, hidden, compoundSettings, layoutParams]) => {
112
118
  const sizingAxis = parseSizing(layoutParams.sizing);
113
119
  const scaleAnchor = area.scaleAnchor as AreaAnchor;
114
120
  return (`
121
+ .item-${item.id}-inner {
122
+ transform-origin: ${ScaleAnchorMap[scaleAnchor]};
123
+ transform: scale(${area.scale});
124
+ }
115
125
  .item-${item.id} {
116
126
  position: absolute;
117
127
  top: ${getCompoundTop(compoundSettings, area.top)};
@@ -122,8 +132,7 @@ export const CompoundChild: FC<ChildItemProps> = ({ item, sectionId, isParentVis
122
132
  ? `${getCompoundWidth(compoundSettings, area.width, isRichText)}`
123
133
  : 'max-content'};
124
134
  height: ${sizingAxis.y === 'manual' ? `${getCompoundHeight(compoundSettings, area.height)}` : 'unset'};
125
- transform-origin: ${ScaleAnchorMap[scaleAnchor]};
126
- transform: scale(${area.scale}) ${getCompoundTransform(compoundSettings)};
135
+ transform: ${getCompoundTransform(compoundSettings)};
127
136
  z-index: ${area.zIndex};
128
137
  }
129
138
  `);
@@ -12,7 +12,7 @@ import { useImageFx } from '../../../utils/effects/useImageFx';
12
12
  import { useElementRect } from '../../../utils/useElementRect';
13
13
  import { useLayoutContext } from '../../useLayoutContext';
14
14
  import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemStateAndParams';
15
- import { baseVariables } from './shaderBaseVars';
15
+ import { useItemFXData } from '../../../common/useItemFXData';
16
16
 
17
17
  export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
18
18
  const id = useId();
@@ -28,18 +28,12 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
28
28
  const itemAngle = useItemAngle(item, sectionId);
29
29
  const [wrapperRef, setWrapperRef] = useState<HTMLDivElement | null>(null);
30
30
  useRegisterResize(wrapperRef, onResize);
31
- const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
31
+ const { url, hasGLEffect } = item.commonParams;
32
32
  const fxCanvas = useRef<HTMLCanvasElement | null>(null);
33
33
  const isInitialRef = useRef(true);
34
- const controls = FXControls ?? [];
35
- const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
36
- .join('\n');
37
- const controlValues = controls.reduce<Record<string, ControlValue>>((acc, control) => {
38
- acc[control.shaderParam] = control.value;
39
- return acc;
40
- }, {});
34
+
41
35
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
42
- const fullShaderCode = `${baseVariables}\n${controlsVariables}\n${fragmentShader}`;
36
+ const { controlsValues, fragmentShader } = useItemFXData(item, sectionId);
43
37
  const area = layoutId ? item.area[layoutId] : null;
44
38
  const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
45
39
  const width = area && exemplary ? area.width * exemplary : 0;
@@ -55,9 +49,8 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
55
49
  !!(hasGLEffect && !isInitialRef.current),
56
50
  {
57
51
  imageUrl: url,
58
- fragmentShader: fullShaderCode,
59
- cursor: FXCursor,
60
- controls: controlValues
52
+ fragmentShader,
53
+ controlsValues
61
54
  },
62
55
  width,
63
56
  height
@@ -143,7 +136,7 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
143
136
  box-sizing: border-box;
144
137
  }
145
138
  ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
146
- return (`
139
+ return (`
147
140
  .image-wrapper-${item.id} {
148
141
  opacity: ${layoutParams.opacity};
149
142
  transform: rotate(${area.angle}deg);
@@ -155,11 +148,9 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
155
148
  border-width: ${layoutParams.strokeWidth * 100}vw;
156
149
  }
157
150
  `);
158
- })}
151
+ })}
159
152
  `}</JSXStyle>
160
153
  </>
161
154
  </LinkWrapper>
162
155
  );
163
156
  };
164
-
165
- type ControlValue = number | [number, number];
@@ -11,9 +11,9 @@ import { useRegisterResize } from '../../../common/useRegisterResize';
11
11
  import { useLayoutContext } from '../../useLayoutContext';
12
12
  import { ScrollPlaybackVideo } from '../../ScrollPlaybackVideo';
13
13
  import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemStateAndParams';
14
- import { baseVariables } from './shaderBaseVars';
15
14
  import { useVideoFx } from '../../../utils/effects/useVideoFx';
16
15
  import { useElementRect } from '../../../utils/useElementRect';
16
+ import { useItemFXData } from '../../../common/useItemFXData';
17
17
 
18
18
  export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
19
19
  const id = useId();
@@ -30,20 +30,13 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
30
30
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
31
31
  const videoRef = useRef<HTMLVideoElement | null>(null);
32
32
  const fxCanvas = useRef<HTMLCanvasElement | null>(null);
33
- const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
33
+ const { url, hasGLEffect } = item.commonParams;
34
34
  const isInitialRef = useRef(true);
35
35
  const area = layoutId ? item.area[layoutId] : null;
36
36
  const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
37
37
  const width = area && exemplary ? area.width * exemplary : 0;
38
38
  const height = area && exemplary ? area.height * exemplary : 0;
39
- const controls = FXControls ?? [];
40
- const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
41
- .join('\n');
42
- const controlValues = controls.reduce<Record<string, ControlValue>>((acc, control) => {
43
- acc[control.shaderParam] = control.value;
44
- return acc;
45
- }, {});
46
- const fullShaderCode = `${baseVariables}\n${controlsVariables}\n${fragmentShader}`;
39
+ const { controlsValues, fragmentShader } = useItemFXData(item, sectionId);
47
40
  const rect = useElementRect(ref);
48
41
  const rectWidth = Math.floor(rect?.width ?? 0);
49
42
  const rectHeight = Math.floor(rect?.height ?? 0);
@@ -69,9 +62,8 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
69
62
  !!(hasGLEffect && !isInitialRef.current),
70
63
  {
71
64
  videoUrl: url,
72
- fragmentShader: fullShaderCode,
73
- cursor: FXCursor,
74
- controls: controlValues
65
+ fragmentShader,
66
+ controls: controlsValues
75
67
  },
76
68
  width,
77
69
  height
@@ -191,5 +183,3 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
191
183
  </LinkWrapper>
192
184
  );
193
185
  };
194
-
195
- type ControlValue = number | [number, number];
@@ -3,7 +3,7 @@ import { useKeyframeValue } from '../../../common/useKeyframeValue';
3
3
  import { useLayoutContext } from '../../useLayoutContext';
4
4
 
5
5
  const DEFAULT_COLOR = 'rgba(0, 0, 0, 1)';
6
- export const useFileItem = (item: ImageItem | VideoItem, sectionId: string) => {
6
+ export function useFileItem(item: ImageItem | VideoItem, sectionId: string) {
7
7
  const layoutId = useLayoutContext();
8
8
  const radius = useKeyframeValue(
9
9
  item,
@@ -11,7 +11,7 @@ export const useFileItem = (item: ImageItem | VideoItem, sectionId: string) => {
11
11
  (item, layoutId) => {
12
12
  if (!layoutId) return;
13
13
  const layoutParams = item.layoutParams[layoutId];
14
- return 'radius' in layoutParams ? layoutParams.radius : 0;
14
+ return 'radius' in layoutParams ? layoutParams.radius : 0;
15
15
  },
16
16
  (animator, scroll, value) => value !== undefined ? animator.getRadius({ radius: value }, scroll).radius : undefined,
17
17
  sectionId,
@@ -70,4 +70,4 @@ export const useFileItem = (item: ImageItem | VideoItem, sectionId: string) => {
70
70
  );
71
71
 
72
72
  return { radius, strokeWidth, opacity, strokeColor, blur };
73
- };
73
+ }
@@ -25,6 +25,7 @@ export class Animator {
25
25
  private static pushKeyframeToMap<T extends KeyframeType>(keyframe: AnimationData<T>, map: KeyframesMap): void {
26
26
  insertBin(map[keyframe.type], keyframe);
27
27
  }
28
+
28
29
  private keyframesMap: KeyframesMap = createKeyframesMap();
29
30
  constructor(
30
31
  private keyframes: AnimationData<KeyframeType>[]
@@ -32,6 +33,26 @@ export class Animator {
32
33
  this.fillKeyframesMap();
33
34
  }
34
35
 
36
+ getFXParams(
37
+ valuesMap: Record<string, number>,
38
+ pos: number
39
+ ): KeyframeValueMap[KeyframeType.FXParams] {
40
+ const keyframes = this.keyframesMap[KeyframeType.FXParams];
41
+ if (!keyframes.length) return valuesMap;
42
+ if (keyframes.length === 1) {
43
+ const [keyframe] = keyframes;
44
+ return Object.entries(valuesMap).reduce<Record<string, number>>((acc, [key, fallbackValue]) => {
45
+ acc[key] = keyframe.value[key] ?? fallbackValue;
46
+ return acc;
47
+ }, {});
48
+ }
49
+ const { start, end } = this.getStartEnd<KeyframeType.FXParams>(pos, keyframes);
50
+ return Object.entries(valuesMap).reduce<Record<string, number>>((acc, [key, fallbackValue]) => {
51
+ acc[key] = rangeMap(pos, start.position, end.position, start.value[key] ?? fallbackValue, end.value[key] ?? fallbackValue, true);
52
+ return acc;
53
+ }, {});
54
+ }
55
+
35
56
  getDimensions(
36
57
  values: KeyframeValueMap[KeyframeType.Dimensions],
37
58
  pos: number
@@ -302,7 +323,6 @@ export class Animator {
302
323
  };
303
324
  }
304
325
 
305
-
306
326
  getStartEnd<T extends KeyframeType>(position: number, keyframes: AnimationData<T>[]): PositionKeyframes<T> {
307
327
  const index = binSearchInsertAt(keyframes, { position }, compare);
308
328
  const end = index === keyframes.length ? index - 1 : index;
@@ -330,7 +350,6 @@ export class Animator {
330
350
  }
331
351
  }
332
352
 
333
-
334
353
  function createKeyframesMap(): KeyframesMap {
335
354
  return {
336
355
  [KeyframeType.Dimensions]: [],
@@ -346,6 +365,7 @@ function createKeyframesMap(): KeyframesMap {
346
365
  [KeyframeType.BackdropBlur]: [],
347
366
  [KeyframeType.LetterSpacing]: [],
348
367
  [KeyframeType.WordSpacing]: [],
349
- [KeyframeType.TextColor]: []
368
+ [KeyframeType.TextColor]: [],
369
+ [KeyframeType.FXParams]: []
350
370
  };
351
371
  }
@@ -1,18 +1,11 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
1
+ import { useEffect, useMemo, useState } from 'react';
2
2
  import { MediaEffect, ImageTextureManager } from '@cntrl-site/effects';
3
3
  import { rangeMap } from '../rangeMap';
4
4
 
5
- export interface FXCursor {
6
- type: 'mouse' | 'manual';
7
- x: number;
8
- y: number;
9
- }
10
-
11
5
  interface FxParams {
12
6
  imageUrl?: string;
13
7
  fragmentShader: string | null;
14
- cursor: FXCursor | null;
15
- controls?: Record<string, number | [number, number]>;
8
+ controlsValues?: Record<string, number | [number, number]>;
16
9
  }
17
10
 
18
11
  const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
@@ -24,21 +17,18 @@ export function useImageFx(
24
17
  {
25
18
  imageUrl,
26
19
  fragmentShader,
27
- cursor,
28
- controls
20
+ controlsValues
29
21
  }: FxParams,
30
22
  width: number,
31
23
  height: number
32
24
  ): boolean {
33
25
  const [isFXAllowed, setIsFXAllowed] = useState(true);
34
- const mousePos = useRef<[number, number]>([0.0, 0.0]);
35
26
  const imageTextureManager = useMemo(() => {
36
27
  if (!imageUrl || !enabled) return;
37
28
  return new ImageTextureManager(imageUrl);
38
29
  }, [imageUrl, enabled]);
39
30
  const imageFx = useMemo<MediaEffect | undefined>(() => {
40
- if (!cursor || !imageTextureManager) return;
41
- const { type, x, y } = cursor;
31
+ if (!imageTextureManager) return;
42
32
  return new MediaEffect(
43
33
  imageTextureManager,
44
34
  PATTERN_URL,
@@ -46,8 +36,8 @@ export function useImageFx(
46
36
  fragmentShader!,
47
37
  {
48
38
  time: 0,
49
- cursor: type === 'mouse' ? mousePos.current : [x, y],
50
- ...controls
39
+ cursor: [0, 0],
40
+ ...controlsValues
51
41
  },
52
42
  width,
53
43
  height
@@ -55,7 +45,14 @@ export function useImageFx(
55
45
  }, [imageTextureManager, fragmentShader, width, height]);
56
46
 
57
47
  useEffect(() => {
58
- if (!cursor || cursor.type !== 'mouse' || !canvas || !imageFx) return;
48
+ if (!imageFx || !controlsValues) return;
49
+ for (const [key, value] of Object.entries(controlsValues)) {
50
+ imageFx.setParam(key, value);
51
+ }
52
+ }, [imageFx, controlsValues]);
53
+
54
+ useEffect(() => {
55
+ if (!canvas || !imageFx) return;
59
56
  const handleMouseMove = (evt: MouseEvent) => {
60
57
  const rect = canvas.getBoundingClientRect();
61
58
  const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
@@ -66,7 +63,7 @@ export function useImageFx(
66
63
  return () => {
67
64
  window.removeEventListener('mousemove', handleMouseMove);
68
65
  };
69
- }, [canvas, imageFx, cursor?.type]);
66
+ }, [canvas, imageFx]);
70
67
 
71
68
  useEffect(() => {
72
69
  const gl = canvas?.getContext('webgl2');
@@ -1,17 +1,10 @@
1
- import { useEffect, useMemo, useRef, useState } from 'react';
1
+ import { useEffect, useMemo, useState } from 'react';
2
2
  import { MediaEffect, VideoTextureManager } from '@cntrl-site/effects';
3
3
  import { rangeMap } from '../rangeMap';
4
4
 
5
- export interface FXCursor {
6
- type: 'mouse' | 'manual';
7
- x: number;
8
- y: number;
9
- }
10
-
11
5
  interface FxParams {
12
6
  videoUrl?: string;
13
7
  fragmentShader: string | null;
14
- cursor: FXCursor | null;
15
8
  controls?: Record<string, number | [number, number]>;
16
9
  }
17
10
 
@@ -24,7 +17,6 @@ export function useVideoFx(
24
17
  {
25
18
  videoUrl,
26
19
  fragmentShader,
27
- cursor,
28
20
  controls
29
21
  }: FxParams,
30
22
  width: number,
@@ -33,7 +25,6 @@ export function useVideoFx(
33
25
  const [isFXAllowed, setIsFXAllowed] = useState(true);
34
26
  const [isReady, setIsReady] = useState(false);
35
27
  const active = enabled && isReady;
36
- const mousePos = useRef<[number, number]>([0.0, 0.0]);
37
28
  const videoTextureManager = useMemo(() => {
38
29
  if (!videoUrl || !enabled) return;
39
30
  const handlePlayError = () => {
@@ -42,8 +33,7 @@ export function useVideoFx(
42
33
  return new VideoTextureManager(videoUrl, handlePlayError);
43
34
  }, [videoUrl, enabled]);
44
35
  const videoFx = useMemo<MediaEffect | undefined>(() => {
45
- if (!cursor || !videoTextureManager) return;
46
- const { type, x, y } = cursor;
36
+ if (!videoTextureManager) return;
47
37
  return new MediaEffect(
48
38
  videoTextureManager,
49
39
  PATTERN_URL,
@@ -51,7 +41,7 @@ export function useVideoFx(
51
41
  fragmentShader!,
52
42
  {
53
43
  time: 0,
54
- cursor: type === 'mouse' ? mousePos.current : [x, y],
44
+ cursor: [0, 0],
55
45
  ...controls
56
46
  },
57
47
  width,
@@ -65,7 +55,7 @@ export function useVideoFx(
65
55
  }, [videoTextureManager]);
66
56
 
67
57
  useEffect(() => {
68
- if (!cursor || cursor.type !== 'mouse' || !canvas || !videoFx) return;
58
+ if (!canvas || !videoFx) return;
69
59
  const handleMouseMove = (evt: MouseEvent) => {
70
60
  const rect = canvas.getBoundingClientRect();
71
61
  const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
@@ -76,7 +66,7 @@ export function useVideoFx(
76
66
  return () => {
77
67
  window.removeEventListener('mousemove', handleMouseMove);
78
68
  };
79
- }, [canvas, videoFx, cursor?.type]);
69
+ }, [canvas, videoFx]);
80
70
 
81
71
  useEffect(() => {
82
72
  const gl = canvas?.getContext('webgl2');
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.baseVariables = void 0;
4
- exports.baseVariables = `precision mediump float;
5
- uniform sampler2D u_image;
6
- uniform sampler2D u_pattern;
7
- uniform vec2 u_imgDimensions;
8
- uniform vec2 u_patternDimensions;
9
- uniform float u_time;
10
- uniform vec2 u_cursor;
11
- varying vec2 v_texCoord;`;
@@ -1,8 +0,0 @@
1
- export const baseVariables = `precision mediump float;
2
- uniform sampler2D u_image;
3
- uniform sampler2D u_pattern;
4
- uniform vec2 u_imgDimensions;
5
- uniform vec2 u_patternDimensions;
6
- uniform float u_time;
7
- uniform vec2 u_cursor;
8
- varying vec2 v_texCoord;`;