Add slow mode for model & texture animations

This commit is contained in:
Brian Beck 2024-08-26 12:48:36 -07:00
parent b41d6b1841
commit 9ee12bbfd2
25 changed files with 88 additions and 24 deletions

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/78e521c3-3739cc27b3254d35.js","static/chunks/95b64a6e-a0ff77d56afeed48.js","static/chunks/31664189-69d752d1129a4958.js","static/chunks/545f34e4-3e66c340444ca8b2.js","static/chunks/1bfc9850-b4ceccea4b74407c.js","static/chunks/d7eeaac4-d223ea230e13423c.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/50-a8a6240a880bd3e0.js","static/chunks/pages/index-357b9e2337384293.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/78e521c3-3739cc27b3254d35.js","static/chunks/95b64a6e-a0ff77d56afeed48.js","static/chunks/31664189-69d752d1129a4958.js","static/chunks/545f34e4-3e66c340444ca8b2.js","static/chunks/1bfc9850-b4ceccea4b74407c.js","static/chunks/d7eeaac4-d223ea230e13423c.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/50-a8a6240a880bd3e0.js","static/chunks/pages/index-24e06461362afe89.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

View file

@ -0,0 +1,2 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[250],{9250:function(e,t,a){a.r(t),a.d(t,{default:function(){return ModelViewer}});var l=a(5893),o=a(7294);a(5848);var d=a(485);function ModelViewerKeyedByModel(e){let{modelUrl:t,environmentImageUrl:a,showEnvironment:i=!1,exposure:r=1,animationName:n,animationPaused:u=!1,timeScale:s=1,cameraOrbit:f,cameraTarget:c,fieldOfView:m,children:v}=e,[h,w]=(0,o.useState)(null),[y,g]=(0,o.useState)(!1),x=(0,o.useMemo)(()=>h&&y&&h.model?{modelViewer:h,model:h.model,isLoaded:y}:null,[h,y]);return(0,o.useEffect)(()=>{h&&(h.timeScale=s)},[h,s]),(0,o.useEffect)(()=>{if(!h)return;let e=!1,handleLoad=()=>{e||g(!0)};return h.addEventListener("load",handleLoad),()=>{e=!0,h.removeEventListener("load",handleLoad)}},[h,t]),(0,o.useEffect)(()=>{h&&h.loaded&&g(!0)},[h,t]),(0,o.useEffect)(()=>{h&&y&&(u?h.pause():h.play())},[h,y,u]),(0,o.useEffect)(()=>{h&&y&&m&&h.setAttribute("field-of-view",m)},[h,y,m]),(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("model-viewer",{ref:w,alt:"Tribes 2 Model",src:t,"camera-controls":!0,"camera-orbit":f,"max-camera-orbit":a&&i?"auto 90deg auto":void 0,"camera-target":c,"min-field-of-view":"10deg","max-field-of-view":"45deg","animation-name":null!=n?n:void 0,autoplay:n?"true":"false","touch-action":"pan-y",exposure:r,"environment-image":null!=a?a:void 0,"skybox-image":a&&i?a:void 0,"skybox-height":"1.5m","shadow-intensity":a&&i?1:0,style:{width:"100%",height:"100%"}}),y?(0,l.jsx)(d.K.Provider,{value:x,children:v}):null]})}function ModelViewer(e){return(0,l.jsx)(ModelViewerKeyedByModel,{...e},e.modelUrl)}}}]);
//# sourceMappingURL=250.10c119307e239c98.js.map

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[250],{9250:function(e,t,a){a.r(t),a.d(t,{default:function(){return ModelViewer}});var l=a(5893),o=a(7294);a(5848);var d=a(485);function ModelViewerKeyedByModel(e){let{modelUrl:t,environmentImageUrl:a,showEnvironment:i=!1,exposure:r=1,animationName:n,animationPaused:u=!1,cameraOrbit:s,cameraTarget:f,fieldOfView:c,children:m}=e,[v,h]=(0,o.useState)(null),[w,y]=(0,o.useState)(!1),g=(0,o.useMemo)(()=>v&&w&&v.model?{modelViewer:v,model:v.model,isLoaded:w}:null,[v,w]);return(0,o.useEffect)(()=>{v&&(v.timeScale=.5)},[v]),(0,o.useEffect)(()=>{if(!v)return;let e=!1,handleLoad=()=>{e||y(!0)};return v.addEventListener("load",handleLoad),()=>{e=!0,v.removeEventListener("load",handleLoad)}},[v,t]),(0,o.useEffect)(()=>{v&&v.loaded&&y(!0)},[v,t]),(0,o.useEffect)(()=>{v&&w&&(u?v.pause():v.play())},[v,w,u]),(0,o.useEffect)(()=>{v&&w&&c&&v.setAttribute("field-of-view",c)},[v,w,c]),(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)("model-viewer",{ref:h,alt:"Tribes 2 Model",src:t,"camera-controls":!0,"camera-orbit":s,"max-camera-orbit":a&&i?"auto 90deg auto":void 0,"camera-target":f,"min-field-of-view":"10deg","max-field-of-view":"45deg","animation-name":null!=n?n:void 0,autoplay:n?"true":"false","touch-action":"pan-y",exposure:r,"environment-image":null!=a?a:void 0,"skybox-image":a&&i?a:void 0,"skybox-height":"1.5m","shadow-intensity":a&&i?1:0,style:{width:"100%",height:"100%"}}),w?(0,l.jsx)(d.K.Provider,{value:g,children:m}):null]})}function ModelViewer(e){return(0,l.jsx)(ModelViewerKeyedByModel,{...e},e.modelUrl)}}}]);
//# sourceMappingURL=250.a985869c089b2fd3.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +1,2 @@
!function(){"use strict";var e,r,_,t,n,i,u,c={},o={};function __webpack_require__(e){var r=o[e];if(void 0!==r)return r.exports;var _=o[e]={exports:{}},t=!0;try{c[e].call(_.exports,_,_.exports,__webpack_require__),t=!1}finally{t&&delete o[e]}return _.exports}__webpack_require__.m=c,e=[],__webpack_require__.O=function(r,_,t,n){if(_){n=n||0;for(var i=e.length;i>0&&e[i-1][2]>n;i--)e[i]=e[i-1];e[i]=[_,t,n];return}for(var u=1/0,i=0;i<e.length;i++){for(var _=e[i][0],t=e[i][1],n=e[i][2],c=!0,o=0;o<_.length;o++)u>=n&&Object.keys(__webpack_require__.O).every(function(e){return __webpack_require__.O[e](_[o])})?_.splice(o--,1):(c=!1,n<u&&(u=n));if(c){e.splice(i--,1);var a=t()}}return a},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,{a:r}),r},__webpack_require__.d=function(e,r){for(var _ in r)__webpack_require__.o(r,_)&&!__webpack_require__.o(e,_)&&Object.defineProperty(e,_,{enumerable:!0,get:r[_]})},__webpack_require__.f={},__webpack_require__.e=function(e){return Promise.all(Object.keys(__webpack_require__.f).reduce(function(r,_){return __webpack_require__.f[_](e,r),r},[]))},__webpack_require__.u=function(e){return"static/chunks/"+(737===e?"fb7d5399":e)+"."+({250:"a985869c089b2fd3",354:"c8f476539d33c65e",737:"bc4a70b34221e8c8",767:"5a1b83173dac696e",848:"fc0fe21cdc2e6431"})[e]+".js"},__webpack_require__.miniCssF=function(e){return"static/css/8f9c54e3d59c6be4.css"},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r={},_="_N_E:",__webpack_require__.l=function(e,t,n,i){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var u,c,o=document.getElementsByTagName("script"),a=0;a<o.length;a++){var p=o[a];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==_+n){u=p;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,__webpack_require__.nc&&u.setAttribute("nonce",__webpack_require__.nc),u.setAttribute("data-webpack",_+n),u.src=__webpack_require__.tu(e)),r[e]=[t];var onScriptComplete=function(_,t){u.onerror=u.onload=null,clearTimeout(b);var n=r[e];if(delete r[e],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(function(e){return e(t)}),_)return _(t)},b=setTimeout(onScriptComplete.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=onScriptComplete.bind(null,u.onerror),u.onload=onScriptComplete.bind(null,u.onload),c&&document.head.appendChild(u)},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.tt=function(){return void 0===t&&(t={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(t=trustedTypes.createPolicy("nextjs#bundler",t))),t},__webpack_require__.tu=function(e){return __webpack_require__.tt().createScriptURL(e)},__webpack_require__.p="/t2-model-skinner/_next/",n={272:0},__webpack_require__.f.j=function(e,r){var _=__webpack_require__.o(n,e)?n[e]:void 0;if(0!==_){if(_)r.push(_[2]);else if(272!=e){var t=new Promise(function(r,t){_=n[e]=[r,t]});r.push(_[2]=t);var i=__webpack_require__.p+__webpack_require__.u(e),u=Error();__webpack_require__.l(i,function(r){if(__webpack_require__.o(n,e)&&(0!==(_=n[e])&&(n[e]=void 0),_)){var t=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;u.message="Loading chunk "+e+" failed.\n("+t+": "+i+")",u.name="ChunkLoadError",u.type=t,u.request=i,_[1](u)}},"chunk-"+e,e)}else n[e]=0}},__webpack_require__.O.j=function(e){return 0===n[e]},i=function(e,r){var _,t,i=r[0],u=r[1],c=r[2],o=0;if(i.some(function(e){return 0!==n[e]})){for(_ in u)__webpack_require__.o(u,_)&&(__webpack_require__.m[_]=u[_]);if(c)var a=c(__webpack_require__)}for(e&&e(r);o<i.length;o++)t=i[o],__webpack_require__.o(n,t)&&n[t]&&n[t][0](),n[t]=0;return __webpack_require__.O(a)},(u=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(i.bind(null,0)),u.push=i.bind(null,u.push.bind(u))}();
//# sourceMappingURL=webpack-cbee82fe57b0dc2e.js.map
!function(){"use strict";var e,r,_,t,n,i,u,c={},o={};function __webpack_require__(e){var r=o[e];if(void 0!==r)return r.exports;var _=o[e]={exports:{}},t=!0;try{c[e].call(_.exports,_,_.exports,__webpack_require__),t=!1}finally{t&&delete o[e]}return _.exports}__webpack_require__.m=c,e=[],__webpack_require__.O=function(r,_,t,n){if(_){n=n||0;for(var i=e.length;i>0&&e[i-1][2]>n;i--)e[i]=e[i-1];e[i]=[_,t,n];return}for(var u=1/0,i=0;i<e.length;i++){for(var _=e[i][0],t=e[i][1],n=e[i][2],c=!0,o=0;o<_.length;o++)u>=n&&Object.keys(__webpack_require__.O).every(function(e){return __webpack_require__.O[e](_[o])})?_.splice(o--,1):(c=!1,n<u&&(u=n));if(c){e.splice(i--,1);var a=t()}}return a},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,{a:r}),r},__webpack_require__.d=function(e,r){for(var _ in r)__webpack_require__.o(r,_)&&!__webpack_require__.o(e,_)&&Object.defineProperty(e,_,{enumerable:!0,get:r[_]})},__webpack_require__.f={},__webpack_require__.e=function(e){return Promise.all(Object.keys(__webpack_require__.f).reduce(function(r,_){return __webpack_require__.f[_](e,r),r},[]))},__webpack_require__.u=function(e){return"static/chunks/"+(737===e?"fb7d5399":e)+"."+({250:"10c119307e239c98",354:"c8f476539d33c65e",737:"bc4a70b34221e8c8",767:"5a1b83173dac696e",848:"fc0fe21cdc2e6431"})[e]+".js"},__webpack_require__.miniCssF=function(e){return"static/css/964f7cf6b1575022.css"},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r={},_="_N_E:",__webpack_require__.l=function(e,t,n,i){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var u,c,o=document.getElementsByTagName("script"),a=0;a<o.length;a++){var p=o[a];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==_+n){u=p;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,__webpack_require__.nc&&u.setAttribute("nonce",__webpack_require__.nc),u.setAttribute("data-webpack",_+n),u.src=__webpack_require__.tu(e)),r[e]=[t];var onScriptComplete=function(_,t){u.onerror=u.onload=null,clearTimeout(b);var n=r[e];if(delete r[e],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(function(e){return e(t)}),_)return _(t)},b=setTimeout(onScriptComplete.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=onScriptComplete.bind(null,u.onerror),u.onload=onScriptComplete.bind(null,u.onload),c&&document.head.appendChild(u)},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.tt=function(){return void 0===t&&(t={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(t=trustedTypes.createPolicy("nextjs#bundler",t))),t},__webpack_require__.tu=function(e){return __webpack_require__.tt().createScriptURL(e)},__webpack_require__.p="/t2-model-skinner/_next/",n={272:0},__webpack_require__.f.j=function(e,r){var _=__webpack_require__.o(n,e)?n[e]:void 0;if(0!==_){if(_)r.push(_[2]);else if(272!=e){var t=new Promise(function(r,t){_=n[e]=[r,t]});r.push(_[2]=t);var i=__webpack_require__.p+__webpack_require__.u(e),u=Error();__webpack_require__.l(i,function(r){if(__webpack_require__.o(n,e)&&(0!==(_=n[e])&&(n[e]=void 0),_)){var t=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;u.message="Loading chunk "+e+" failed.\n("+t+": "+i+")",u.name="ChunkLoadError",u.type=t,u.request=i,_[1](u)}},"chunk-"+e,e)}else n[e]=0}},__webpack_require__.O.j=function(e){return 0===n[e]},i=function(e,r){var _,t,i=r[0],u=r[1],c=r[2],o=0;if(i.some(function(e){return 0!==n[e]})){for(_ in u)__webpack_require__.o(u,_)&&(__webpack_require__.m[_]=u[_]);if(c)var a=c(__webpack_require__)}for(e&&e(r);o<i.length;o++)t=i[o],__webpack_require__.o(n,t)&&n[t]&&n[t][0](),n[t]=0;return __webpack_require__.O(a)},(u=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(i.bind(null,0)),u.push=i.bind(null,u.push.bind(u))}();
//# sourceMappingURL=webpack-c24f5de5b7b1f01e.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -15,6 +15,8 @@ export default function AnimationSelector() {
setSelectedAnimation,
animationPaused,
setAnimationPaused,
slowModeEnabled,
setSlowModeEnabled,
} = useWarrior();
const animationList = useMemo(
@ -27,7 +29,20 @@ export default function AnimationSelector() {
return (
<>
<label>Animation</label>
<div className="LabelWithControls">
<label>Animation</label>
<div className="AnimationSpeed">
<input
type="checkbox"
id="SlowDownCheckbox"
checked={slowModeEnabled}
onChange={(event) => {
setSlowModeEnabled(event.target.checked);
}}
/>{" "}
<label htmlFor="SlowDownCheckbox">Slow?</label>
</div>
</div>
<div className="Buttons">
<select
value={selectedAnimation ?? ""}

View file

@ -3,6 +3,7 @@ import type { ModelViewerElement } from "@google/model-viewer";
import useSettings from "./useSettings";
import useSkin from "./useSkin";
import useModelViewer from "./useModelViewer";
import useWarrior from "./useWarrior";
// const secondaryMaterialTextures: Record<string, string[]> = {
// disc: ["textures/discshield2"],
@ -52,6 +53,7 @@ function useTexture({
}) {
const { modelViewer } = useModelViewer();
const { basePath } = useSettings();
const { slowModeEnabled } = useWarrior();
useEffect(() => {
let stale = false;
@ -81,6 +83,10 @@ function useTexture({
let textureUrls =
imageUrl ?? new Array(frameCount).fill(`${basePath}/white.png`);
if (textureUrls.some((url) => !url)) {
return;
}
switch (textureType) {
case "baseColorTexture":
if (baseColorFactor) {
@ -103,6 +109,7 @@ function useTexture({
textureUrls = new Array(frameCount).fill(`${basePath}/green.png`);
}
}
const textures = await Promise.all(
textureUrls.map((textureUrl) => modelViewer.createTexture(textureUrl))
);
@ -117,7 +124,7 @@ function useTexture({
material.emissiveTexture.setTexture(texture);
}
if (isMasterTexture) {
frameInfo.frameProgress += 1;
frameInfo.frameProgress += slowModeEnabled ? 0.05 : 1;
}
if (frameCount > 1) {
const frameTiming = frameTimings?.[frameInfo.frameIndex] ?? 1;
@ -150,6 +157,7 @@ function useTexture({
textureType,
imageUrl,
frameRef,
slowModeEnabled,
]);
}

View file

@ -20,12 +20,15 @@ declare global {
}
}
function useTimeScale(modelViewer: ModelViewerElement | null) {
function useTimeScale(
modelViewer: ModelViewerElement | null,
timeScale: number
) {
useEffect(() => {
if (modelViewer) {
modelViewer.timeScale = 0.5;
modelViewer.timeScale = timeScale;
}
}, [modelViewer]);
}, [modelViewer, timeScale]);
}
interface ModelViewerProps {
@ -37,6 +40,7 @@ interface ModelViewerProps {
metallicImageUrl?: string;
animationName: string | null;
animationPaused?: boolean;
timeScale?: number;
cameraOrbit?: string;
cameraTarget?: string;
fieldOfView?: string;
@ -50,6 +54,7 @@ function ModelViewerKeyedByModel({
exposure = 1,
animationName,
animationPaused = false,
timeScale = 1,
cameraOrbit,
cameraTarget,
fieldOfView,
@ -71,7 +76,7 @@ function ModelViewerKeyedByModel({
};
}, [modelViewer, isLoaded]);
useTimeScale(modelViewer);
useTimeScale(modelViewer, timeScale);
useEffect(() => {
if (!modelViewer) {

View file

@ -138,6 +138,7 @@ export default function WarriorProvider({ children }: { children: ReactNode }) {
null
);
const [animationPaused, setAnimationPaused] = useState(false);
const [slowModeEnabled, setSlowModeEnabled] = useState(false);
const { basePath } = useSettings();
const actualModel = selectedModel === "hfemale" ? "hmale" : selectedModel;
const selectedModelUrl = getModelUrl(
@ -188,6 +189,8 @@ export default function WarriorProvider({ children }: { children: ReactNode }) {
skinImageUrls,
setSkinImageUrls,
defaultSkinImageUrls,
slowModeEnabled,
setSlowModeEnabled,
};
}, [
selectedModel,
@ -207,6 +210,7 @@ export default function WarriorProvider({ children }: { children: ReactNode }) {
skinImageUrls,
setSkinImageUrls,
defaultSkinImageUrls,
slowModeEnabled,
]);
useEffect(() => {

View file

@ -17,6 +17,7 @@ export default function WarriorViewer() {
selectedModelType,
selectedAnimation,
animationPaused,
slowModeEnabled,
} = useWarrior();
const { environmentImageUrl, showEnvironment, exposure } = useEnvironment();
@ -27,6 +28,7 @@ export default function WarriorViewer() {
showEnvironment={showEnvironment}
animationName={selectedAnimation}
animationPaused={animationPaused}
timeScale={slowModeEnabled ? 0.05 : 0.5}
cameraOrbit={
selectedModelType === "weapon" ? "315deg 70deg 105%" : undefined
}

View file

@ -192,11 +192,17 @@ select {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
.Buttons select {
flex: 1 0 auto;
}
.Buttons button {
display: grid;
place-content: center;
flex: 0 0 auto;
border: 1px solid rgb(23, 159, 138);
border-radius: 2px;
width: 28px;
@ -278,10 +284,10 @@ select {
gap: 10px;
}
.ModelTools:has(input[type="checkbox"]:checked) {
/* .ModelTools:has(input[type="checkbox"]:checked) {
background-color: #01292e;
background-image: linear-gradient(to bottom, #124044 0%, #001720 100vh);
}
} */
.Field {
display: flex;
@ -290,13 +296,21 @@ select {
gap: 4px;
}
.Field > label {
.Field > label,
.Field > div > label {
text-transform: uppercase;
font-size: 11px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.8);
opacity: 0.8;
}
.Field .LabelWithControls {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.BrushToolsPopup {
background: rgba(5, 22, 21, 0.9);
border-radius: 4px;
@ -519,3 +533,17 @@ h6 {
font-size: 12px;
font-weight: 500;
}
.AnimationSpeed {
display: flex;
align-items: center;
font-size: 11px;
margin: 0 38px 0 16px;
gap: 3px;
color: rgb(122, 183, 165);
}
.AnimationSpeed input[type="checkbox"] {
width: 12px;
height: 12px;
}

View file

@ -26,6 +26,8 @@ type WarriorContextValue = {
selectedSkin: string | null;
setSelectedSkin: (selectedSkin: string | null) => void;
setSelectedModelType: (selectedModelType: string) => void;
slowModeEnabled: boolean;
setSlowModeEnabled: (slowModeEnabled: boolean) => void;
};
const WarriorContext = React.createContext<WarriorContextValue | null>(null);