zhejiushizhuce 2 жил өмнө
parent
commit
898c3bef8b
68 өөрчлөгдсөн 2672 нэмэгдсэн , 348 устгасан
  1. 3 1
      package.json
  2. BIN
      public/bigbgs/casino.png
  3. BIN
      public/bigbgs/racing.png
  4. BIN
      public/bigbgs/sports.png
  5. BIN
      public/bigbgs/trading.png
  6. 195 0
      public/test/aztec.atlas
  7. 0 0
      public/test/aztec.json
  8. BIN
      public/test/aztec.png
  9. 7 0
      public/test/skeleton.atlas
  10. 42 0
      public/test/skeleton.json
  11. BIN
      public/test/skeleton.png
  12. 23 113
      src/App.js
  13. 10 2
      src/api/config.js
  14. BIN
      src/assets/lvicon/USD.black.png
  15. BIN
      src/assets/lvicon/diamond_mini.png
  16. BIN
      src/assets/lvicon/gold_mini.png
  17. BIN
      src/assets/lvicon/platinum_mini.png
  18. 26 0
      src/components/AutoBanner/BannerControls.jsx
  19. 36 0
      src/components/AutoBanner/BannerPagination.jsx
  20. 82 23
      src/components/AutoBanner/index.jsx
  21. 10 2
      src/components/AutoBanner/style.js
  22. 73 0
      src/components/AutoBanner/styles.module.scss
  23. 348 0
      src/components/AutoWinList/index.jsx
  24. 192 0
      src/components/AutoWinList/styles.module.scss
  25. 58 0
      src/components/ColorCards/ColorCard.jsx
  26. 63 0
      src/components/ColorCards/ColorCardsGrid.jsx
  27. 114 0
      src/components/ColorCards/styles.module.scss
  28. 52 0
      src/components/GameItem/index.jsx
  29. 62 0
      src/components/GameItem/styles.module.scss
  30. 120 0
      src/components/GameList/index.jsx
  31. 52 0
      src/components/GameList/styles.module.scss
  32. 21 0
      src/components/GameSearch/index.jsx
  33. 36 0
      src/components/GameSearch/styles.module.scss
  34. 167 0
      src/components/GameSmallList/index.jsx
  35. 74 0
      src/components/GameSmallList/styles.module.scss
  36. 52 0
      src/components/GameTabs/index.jsx
  37. 45 0
      src/components/GameTabs/styles.module.scss
  38. 1 1
      src/components/LazyImage.jsx
  39. 16 1
      src/contexts/themeContext.js
  40. 153 153
      src/db/routes.js
  41. 31 0
      src/features/api.js
  42. 23 5
      src/features/mainSlice.js
  43. 43 32
      src/layout/AppGrid.jsx
  44. 114 0
      src/layout/GameSearchPanel/index.jsx
  45. 87 0
      src/layout/GameSearchPanel/styles.module.scss
  46. 3 1
      src/layout/Layout.jsx
  47. 2 2
      src/layout/Navbar/index.jsx
  48. 4 4
      src/layout/Sidebar/index.jsx
  49. 3 0
      src/layouts.js
  50. 18 0
      src/locales/languages/en_US.json
  51. 13 1
      src/pages/Home.jsx
  52. 12 5
      src/styles/_global.scss
  53. 1 1
      src/styles/_keyframes.scss
  54. 4 1
      src/styles/_utils.scss
  55. 25 0
      src/styles/_vars.scss
  56. 12 0
      src/styles/theme.js
  57. 14 0
      src/widgets/AutoBigWin/index.jsx
  58. 0 0
      src/widgets/AutoBigWin/styles.module.scss
  59. 56 0
      src/widgets/Banner/index.jsx
  60. 14 0
      src/widgets/BigColorCards/index.jsx
  61. 15 0
      src/widgets/ModuleGameList/index.jsx
  62. 0 0
      src/widgets/ModuleGameList/styles.module.scss
  63. 15 0
      src/widgets/ModuleGameSearch/index.jsx
  64. 0 0
      src/widgets/ModuleGameSearch/styles.module.scss
  65. 15 0
      src/widgets/ModuleGameSmallList/index.jsx
  66. 0 0
      src/widgets/ModuleGameSmallList/styles.module.scss
  67. 15 0
      src/widgets/ModuleGameTabs/index.jsx
  68. 0 0
      src/widgets/ModuleGameTabs/styles.module.scss

+ 3 - 1
package.json

@@ -20,18 +20,20 @@
     "@emotion/cache": "^11.10.5",
     "@emotion/react": "^11.10.6",
     "@emotion/styled": "^11.10.5",
+    "@esotericsoftware/spine-player": "^4.2.35",
     "@loadable/component": "^5.15.3",
     "@mui/base": "^5.0.0-beta.19",
     "@mui/material": "^5.11.4",
     "@mui/styled-engine-sc": "^5.11.0",
     "@react-spring/web": "^9.7.1",
-    "@reduxjs/toolkit": "^1.9.3",
+    "@reduxjs/toolkit": "^1.9.7",
     "@tanstack/react-table": "^8.7.9",
     "axios": "^1.6.7",
     "classnames": "^2.3.2",
     "country-state-city": "^3.1.2",
     "craco": "^0.0.3",
     "dayjs": "^1.11.7",
+    "gsap": "^3.12.5",
     "i18next": "^23.8.2",
     "i18next-browser-languagedetector": "^7.2.0",
     "lottie-react": "^2.4.0",

BIN
public/bigbgs/casino.png


BIN
public/bigbgs/racing.png


BIN
public/bigbgs/sports.png


BIN
public/bigbgs/trading.png


+ 195 - 0
public/test/aztec.atlas

@@ -0,0 +1,195 @@
+
+aztec.png
+size: 1624,783
+format: RGBA8888
+filter: Linear,Linear
+repeat: none
+cao01
+  rotate: false
+  xy: 1184, 347
+  size: 45, 54
+  orig: 311, 617
+  offset: 224, 234
+  index: -1
+cao02
+  rotate: true
+  xy: 268, 2
+  size: 101, 150
+  orig: 311, 617
+  offset: 0, 192
+  index: -1
+cao03
+  rotate: false
+  xy: 1543, 435
+  size: 76, 127
+  orig: 311, 617
+  offset: 71, 308
+  index: -1
+di
+  rotate: false
+  xy: 259, 333
+  size: 231, 448
+  orig: 231, 448
+  offset: 0, 0
+  index: -1
+di2
+  rotate: false
+  xy: 951, 192
+  size: 231, 209
+  orig: 231, 209
+  offset: 0, 0
+  index: -1
+gebo
+  rotate: true
+  xy: 1164, 56
+  size: 134, 129
+  orig: 311, 617
+  offset: 155, 102
+  index: -1
+gebo02
+  rotate: true
+  xy: 1425, 25
+  size: 96, 118
+  orig: 311, 617
+  offset: 33, 265
+  index: -1
+jian_g
+  rotate: true
+  xy: 1184, 207
+  size: 136, 251
+  orig: 138, 254
+  offset: 0, 0
+  index: -1
+jian_g01
+  rotate: true
+  xy: 975, 403
+  size: 163, 288
+  orig: 165, 295
+  offset: 2, 3
+  index: -1
+jian_g02
+  rotate: true
+  xy: 492, 180
+  size: 260, 262
+  orig: 260, 262
+  offset: 0, 0
+  index: -1
+mao
+  rotate: true
+  xy: 492, 442
+  size: 168, 481
+  orig: 311, 617
+  offset: 42, 5
+  index: -1
+mao_mh
+  rotate: true
+  xy: 492, 612
+  size: 169, 490
+  orig: 311, 617
+  offset: 41, 1
+  index: -1
+scepter_00003
+  rotate: false
+  xy: 683, 8
+  size: 144, 170
+  orig: 258, 355
+  offset: 94, 8
+  index: -1
+scepter_00004
+  rotate: false
+  xy: 268, 105
+  size: 180, 226
+  orig: 258, 355
+  offset: 61, 0
+  index: -1
+scepter_00005
+  rotate: true
+  xy: 2, 19
+  size: 195, 264
+  orig: 258, 355
+  offset: 54, 12
+  index: -1
+scepter_00006
+  rotate: true
+  xy: 1292, 564
+  size: 217, 286
+  orig: 258, 355
+  offset: 28, 21
+  index: -1
+scepter_00007
+  rotate: true
+  xy: 984, 568
+  size: 213, 306
+  orig: 258, 355
+  offset: 40, 11
+  index: -1
+scepter_00008
+  rotate: true
+  xy: 1265, 345
+  size: 217, 276
+  orig: 258, 355
+  offset: 21, 58
+  index: -1
+scepter_00009
+  rotate: false
+  xy: 756, 191
+  size: 193, 249
+  orig: 258, 355
+  offset: 28, 85
+  index: -1
+scepter_00010
+  rotate: false
+  xy: 1437, 123
+  size: 185, 220
+  orig: 258, 355
+  offset: 28, 124
+  index: -1
+scepter_00011
+  rotate: true
+  xy: 829, 2
+  size: 187, 180
+  orig: 258, 355
+  offset: 21, 154
+  index: -1
+scepter_00012
+  rotate: true
+  xy: 1011, 5
+  size: 185, 151
+  orig: 258, 355
+  offset: 6, 204
+  index: -1
+scepter_00013
+  rotate: false
+  xy: 1295, 90
+  size: 128, 115
+  orig: 258, 355
+  offset: 23, 229
+  index: -1
+shenti
+  rotate: false
+  xy: 2, 216
+  size: 255, 565
+  orig: 311, 617
+  offset: 36, 52
+  index: -1
+total
+  rotate: true
+  xy: 1580, 598
+  size: 183, 37
+  orig: 183, 37
+  offset: 0, 0
+  index: -1
+zhezhao
+  rotate: false
+  xy: 450, 7
+  size: 231, 171
+  orig: 231, 176
+  offset: 0, 0
+  index: -1
+zi
+  rotate: true
+  xy: 450, 222
+  size: 109, 31
+  orig: 109, 31
+  offset: 0, 0
+  index: -1

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
public/test/aztec.json


BIN
public/test/aztec.png


+ 7 - 0
public/test/skeleton.atlas

@@ -0,0 +1,7 @@
+skeleton.png
+size:190,151
+filter:Linear,Linear
+pma:true
+A
+bounds:2,2,186,147
+offsets:2,2,190,151

+ 42 - 0
public/test/skeleton.json

@@ -0,0 +1,42 @@
+{
+"skeleton": {
+	"hash": "q0mQ3NzwuIk",
+	"spine": "4.1.24",
+	"x": -95.55,
+	"y": -76.19,
+	"width": 191.09,
+	"height": 152.37,
+	"images": "",
+	"audio": ""
+},
+"bones": [
+	{ "name": "root" },
+	{ "name": "bone", "parent": "root", "length": 98.88, "rotation": 90.58 }
+],
+"slots": [
+	{ "name": "bone", "bone": "bone", "attachment": "A" }
+],
+"skins": [
+	{
+		"name": "default",
+		"attachments": {
+			"bone": {
+				"A": { "rotation": -91, "width": 190, "height": 151 }
+			}
+		}
+	}
+],
+"animations": {
+	"A": {
+		"bones": {
+			"bone": {
+				"scale": [
+					{},
+					{ "time": 0.5, "x": 1.25, "y": 1.25 },
+					{ "time": 1 }
+				]
+			}
+		}
+	}
+}
+}

BIN
public/test/skeleton.png


+ 23 - 113
src/App.js

@@ -40,45 +40,18 @@ import createCache from '@emotion/cache';
 // components
 import {BrowserRouter, HashRouter, Route, Routes, useLocation} from 'react-router-dom';
 import {ToastContainer} from 'react-toastify';
-import LoadingScreen from '@components/LoadingScreen';
-import Sidebar from '@layout/Sidebar';
-import BottomNav from '@layout/BottomNav';
-import Navbar from '@layout/Navbar';
-import ShoppingCart from '@widgets/ShoppingCart';
-import ScrollToTop from '@components/ScrollToTop';
-import AppContainer from "@layout/AppContainer";
-import {APP_ROUTES} from "@api/config";
-import Http from "@api/request";
 import Layout from "@layout/Layout";
+import {getFetchStatus} from '@features/mainSlice';
+import {useDispatch, useSelector} from "react-redux";
+import {REQ_STATUS} from "@api/config";
+import {reqGetRoutes} from "@features/api";
 
-// pages
-const ClubSummary = lazy(() => import('@pages/ClubSummary'));
-const GameSummary = lazy(() => import('@pages/GameSummary'));
-const Championships = lazy(() => import('@pages/Championships'));
-const LeagueOverview = lazy(() => import('@pages/LeagueOverview'));
-const FansCommunity = lazy(() => import('@pages/FansCommunity'));
-const Statistics = lazy(() => import('@pages/Statistics'));
-const PageNotFound = lazy(() => import('@pages/PageNotFound'));
-const MatchSummary = lazy(() => import('@pages/MatchSummary'));
-const MatchOverview = lazy(() => import('@pages/MatchOverview'));
-const PlayerProfile = lazy(() => import('@pages/PlayerProfile'));
-const Schedule = lazy(() => import('@pages/Schedule'));
-const Tickets = lazy(() => import('@pages/Tickets'));
-const FootballStore = lazy(() => import('@pages/FootballStore'));
-const BrandStore = lazy(() => import('@pages/BrandStore'));
-const Product = lazy(() => import('@pages/Product'));
-const Login = lazy(() => import('@pages/Login'));
-const SignUp = lazy(() => import('@pages/SignUp'));
-const Settings = lazy(() => import('@pages/Settings'));
 
 const App = () => {
+    const dispatch = useDispatch();
+    const fetchStatus=useSelector(getFetchStatus);
     const appRef = useRef(null);
     const {theme, direction} = useThemeProvider();
-    const {width} = useWindowSize();
-    // const isAuthRoute = useAuthRoute();
-    const isAuthRoute = false;
-    const {pathname, search, hash} = useLocation();
-    // Google Analytics init
     const gaKey = process.env.REACT_APP_PUBLIC_GA;
     gaKey && ReactGA.initialize(gaKey);
 
@@ -94,90 +67,25 @@ const App = () => {
         key: 'css-rtl',
         stylisPlugins: plugins,
     });
-
+    const startReqRoutes=()=>{
+        dispatch(reqGetRoutes());
+    }
     useEffect(() => {
         // scroll to top on route change
+        //请求routes
+        startReqRoutes();
         appRef.current && appRef.current.scrollTo(0, 0);
         preventDefault();
     }, []);
-    // return (
-    //     <CacheProvider value={cacheRtl}>
-    //         <MuiThemeProvider theme={muiTheme}>
-    //             <SidebarProvider>
-    //                 <ThemeProvider theme={{theme: theme}}>
-    //                     <ThemeStyles/>
-    //                     <ToastContainer theme={theme} autoClose={2500}
-    //                                     position={direction === 'ltr' ? 'top-right' : 'top-left'}/>
-    //                     <StyleSheetManager stylisPlugins={plugins}>
-    //                         <div className={`app ${isAuthRoute ? 'fluid' : ''}`} ref={appRef}>
-    //                             <ScrollToTop/>
-    //                             {
-    //                                 !isAuthRoute && (
-    //                                     <>
-    //                                         <Sidebar/>
-    //                                         <Navbar/>
-    //                                         {
-    //                                             // width < 768 && <Navbar/>
-    //                                         }
-    //                                         {
-    //                                             width < 768 && <BottomNav/>
-    //                                         }
-    //                                     </>
-    //                                 )
-    //                             }
-    //                             {/*<div className={`app_container ${true?'sidebar-open':'sidebar-close'}`}>*/}
-    //                             <AppContainer>
-    //                                 <div className="app_container-content d-flex flex-column flex-1">
-    //                                     <Suspense fallback={<LoadingScreen/>}>
-    //                                         <Routes>
-    //                                             <Route path="*" element={<PageNotFound/>}/>
-    //                                             <Route path="/" element={<ClubSummary/>}/>
-    //                                             <Route path="/game-summary" element={<GameSummary/>}/>
-    //                                             <Route path="/championships" element={<Championships/>}/>
-    //                                             <Route path="/league-overview" element={<LeagueOverview/>}/>
-    //                                             <Route path="/fans-community" element={<FansCommunity/>}/>
-    //                                             <Route path="/statistics" element={<Statistics/>}/>
-    //                                             <Route path="/match-summary" element={<MatchSummary/>}/>
-    //                                             <Route path="/match-overview" element={<MatchOverview/>}/>
-    //                                             <Route path="/player-profile" element={<PlayerProfile/>}/>
-    //                                             <Route path="/schedule" element={<Schedule/>}/>
-    //                                             <Route path="/tickets" element={<Tickets/>}/>
-    //                                             <Route path="/football-store" element={<FootballStore/>}/>
-    //                                             <Route path="/brand-store" element={<BrandStore/>}/>
-    //                                             <Route path="/product" element={<Product/>}/>
-    //                                         </Routes>
-    //                                         {
-    //                                             hash != '' ? (
-    //                                                 <Routes location={{
-    //                                                     pathname: (pathname + hash),
-    //                                                     search: search,
-    //                                                     hash: hash
-    //                                                 }}>
-    //                                                     <Route path={`${pathname}#/settings`} element={<div style={{
-    //                                                         backgroundColor: '#fff',
-    //                                                         zIndex: 2000,
-    //                                                         position: "fixed",
-    //                                                         width: '100px',
-    //                                                         height: '100px'
-    //                                                     }}/>}/>
-    //                                                     <Route path={`${pathname}#/sign-up`} element={<MatchSummary/>}/>
-    //                                                 </Routes>
-    //                                             ) : (<></>)
-    //                                         }
-    //
-    //                                     </Suspense>
-    //
-    //                                 </div>
-    //                                 {/*</div>*/}
-    //                             </AppContainer>
-    //                             <ShoppingCart isPopup/>
-    //                         </div>
-    //                     </StyleSheetManager>
-    //                 </ThemeProvider>
-    //             </SidebarProvider>
-    //         </MuiThemeProvider>
-    //     </CacheProvider>
-    // );
+    const renderLayout=()=>{
+        return (
+            <>
+                {
+                    fetchStatus==REQ_STATUS.IDLE?<Layout />:(<></>)
+                }
+            </>
+        )
+    }
     return (
         <CacheProvider value={cacheRtl}>
             <MuiThemeProvider theme={muiTheme}>
@@ -187,7 +95,9 @@ const App = () => {
                         <ToastContainer theme={theme} autoClose={2500}
                                         position={direction === 'ltr' ? 'top-right' : 'top-left'}/>
                         <StyleSheetManager stylisPlugins={plugins}>
-                            <Layout />
+                            {
+                                renderLayout()
+                            }
                         </StyleSheetManager>
                     </ThemeProvider>
                 </SidebarProvider>

+ 10 - 2
src/api/config.js

@@ -1,5 +1,13 @@
+
+export const REQ_STATUS={
+    NONE:"NONE",
+    LOADING:"LOADING",
+    IDLE:"IDLE",
+    FAILED:"FAILED"
+}
+
 // export const BASE_API="https://api.24680.com/";
-export const BASE_API="http://localhost:8899/";
+export const BASE_API="https://api.ouro777.com/";
 // export const APP_ROUTES="routes";
-export const APP_ROUTES="mobile/testHttp";
+export const APP_ROUTES="game/routes";
 

BIN
src/assets/lvicon/USD.black.png


BIN
src/assets/lvicon/diamond_mini.png


BIN
src/assets/lvicon/gold_mini.png


BIN
src/assets/lvicon/platinum_mini.png


+ 26 - 0
src/components/AutoBanner/BannerControls.jsx

@@ -0,0 +1,26 @@
+// styling
+import styles from './styles.module.scss';
+import symbol from "@assets/symbol-defs.svg";
+
+const BannerControls = ({swiper}) => {
+
+    const handlePrev = () => swiper.slidePrev();
+    const handleNext = () => swiper.slideNext();
+
+    return (
+        <>
+            <button onClick={handlePrev} className={`${styles.banner_swiper_btn} ${styles.swiper_prev}`}>
+                <svg className={`${styles.swiper_icon}`}>
+                    <use xlinkHref={`${symbol}#icon_Arrow`} />
+                </svg>
+            </button>
+            <button onClick={handleNext} className={`${styles.banner_swiper_btn} ${styles.swiper_next}`}>
+                <svg className={`${styles.swiper_icon}`}>
+                    <use xlinkHref={`${symbol}#icon_Arrow`} />
+                </svg>
+            </button>
+        </>
+    );
+}
+
+export default BannerControls

+ 36 - 0
src/components/AutoBanner/BannerPagination.jsx

@@ -0,0 +1,36 @@
+// styling
+import styles from './styles.module.scss';
+import {useState} from "react";
+
+const BannerPagination = ({pageCount,swiper}) => {
+    const pages = Array.from({ length: pageCount }, (_, index) => index);
+    const checkActive=(index)=>{
+        if (swiper!=null){
+            let activeIndex=swiper.activeIndex;
+            if (activeIndex>pageCount)activeIndex=activeIndex%pageCount;
+            return activeIndex==index+1;
+        }
+        return false;
+    }
+    const changeIndex =(index)=>{
+        // slideTo
+        if (swiper!=null){
+            if (swiper.activeIndex!=index){
+                swiper.slideTo(index);
+            }
+        }
+    }
+    return (
+        <div className={`${styles.banner_pagination}`}>
+            {
+                pages.map((page,index)=>(
+                    <span onClick={()=>{
+                        changeIndex(index+1);
+                    }} key={index} className={`${checkActive(index)?styles.banner_pagination_dot_active:''}`}></span>
+                ))
+            }
+        </div>
+    );
+}
+
+export default BannerPagination

+ 82 - 23
src/components/AutoBanner/index.jsx

@@ -7,7 +7,7 @@ import LazyImage from '@components/LazyImage';
 import PropTypes from 'prop-types';
 import classNames from 'classnames';
 import {Swiper, SwiperSlide} from "swiper/react";
-import {useState,useEffect} from "react";
+import {useState, useEffect, useRef, createRef} from "react";
 import {
     A11y,
     Autoplay,
@@ -21,42 +21,101 @@ import {
 import {useThemeProvider} from "@contexts/themeContext";
 import Spring from "@components/Spring";
 import {StyledSwiper} from "@components/AutoBanner/style";
-import SwiperControls from "@ui/SwiperControls";
+import BannerControls from "@components/AutoBanner/BannerControls";
+import BannerPagination from "@components/AutoBanner/BannerPagination";
+import {NavLink} from "react-router-dom";
+import styles from './styles.module.scss';
+
 
 const AutoBanner = ({data,perview=3,spaceBetween=15,delay=3000,speed=1000}) => {
     const [swiper, setSwiper] = useState(null);
     const {isMobile} = useThemeProvider();
     if (isMobile())perview=1;
+    const [activeIndex,setActiveIndex]=useState(0);
     useEffect(() => {
         if (swiper) {
             swiper.update();
         }
     }, [swiper]);
-    return (
-        <Spring className="w-100">
-            <StyledSwiper onSwiper={setSwiper}
-                    slidesPerView={perview}
-                    spaceBetween={spaceBetween}
-                    loop
-                    autoplay={{delay: delay, disableOnInteraction: false}}
-                    effect="fade"
-                    fadeEffect={{crossFade: true}}
-                    modules={[Autoplay]}
-                    speed={speed}
-            >
+    const renderControls=()=>{
+        let controls=(<></>);
+        if (!isMobile()){
+            controls=(
+                <BannerControls swiper={swiper}/>
+            );
+        }
+        return controls;
+    }
+    const renderPagination=()=>{
+        let pageCount=Math.ceil(data.length/perview);
+        return (
+            <BannerPagination pageCount={pageCount} swiper={swiper}></BannerPagination>
+        )
+    }
+    const renderItems=()=>{
+        let itemsData=[];
+        for (let i = 0; i <data.length ; i+=perview) {
+            let s=i;
+            let e=i+perview;
+            itemsData.push(data.slice(s,e));
+        }
+        let lastData=itemsData[itemsData.length-1];
+        //补齐最后一组
+        if (lastData!=null){
+            let len=lastData.length;
+            if (len<perview){
+                let temp=data.slice(0);
+                let addSize=perview-len;
+                for (let i = 0; i < addSize; i++) {
+                    if (temp.length==0)temp=data.slice(0);
+                    lastData.push(temp.shift());
+                }
+            }
+        }
+        return (
+            <>
                 {
-                    data.map((item, index) => (
-                        <SwiperSlide key={index}>
-                            <a href={item.link}>
-                                <LazyImage className="banner-img" src={item.img} alt={item.alt}>
-
-                                </LazyImage>
-                            </a>
+                    itemsData.map((items, index) => (
+                        <SwiperSlide className="d-flex flex-row w-100" style={{gap:`${spaceBetween}px`}} key={index}>
+                            {
+                                items.map((item,iIndex)=>(
+                                    <NavLink key={iIndex} className="img-box w-100" to={item.link}>
+                                        <LazyImage style={{borderRadius:"var(--border-radius)"}} className="img-box-content" src={item.img} alt={item.alt}>
+                                        </LazyImage>
+                                    </NavLink>
+                                ))
+                            }
                         </SwiperSlide>
                     ))
                 }
-            </StyledSwiper>
-            <SwiperControls swiper={swiper}/>
+            </>
+        )
+    }
+    return (
+        <Spring className="w-100">
+            <div className={`w-100 p-relative ${styles.banner_container}`}>
+                <StyledSwiper onSwiper={setSwiper}
+                              slidesPerView={1}
+                              spaceBetween={spaceBetween}
+                              loop
+                              autoplay={{delay: delay, disableOnInteraction: false}}
+                              effect="fade"
+                              fadeEffect={{crossFade: true}}
+                              modules={[Autoplay]}
+                              speed={speed}
+                              onActiveIndexChange={()=>{
+                                  if (swiper){
+                                      setActiveIndex(swiper.activeIndex);
+                                  }
+                              }}
+                >
+                    {
+                        renderItems()
+                    }
+                </StyledSwiper>
+                {renderControls()}
+            </div>
+            {renderPagination()}
         </Spring>
     )
 }

+ 10 - 2
src/components/AutoBanner/style.js

@@ -2,11 +2,19 @@ import styled from 'styled-components/macro';
 import theme from "styled-theming";
 import {Swiper} from "swiper/react";
 export const StyledSwiper = styled(Swiper)`
- padding: 20px 0px;
+ padding: 0px 0px;
  .swiper-wrapper{
     transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
  }
  .swiper-slide{
-    
+    & .img-box{
+        transition: filter 0.3s;
+        &:hover{
+           filter: brightness(80%);
+        }
+        & .img-box-content{
+           
+        }
+    }
  }
 `

+ 73 - 0
src/components/AutoBanner/styles.module.scss

@@ -0,0 +1,73 @@
+.banner_container{
+  margin-top: 1rem;
+  @media screen and (max-width: 621px) {
+    margin-top: 0rem;
+  }
+}
+.banner_swiper_btn{
+  width: 2.25rem;
+  height: 2.25rem;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: absolute;
+  z-index: 9;
+  margin-top: -1.125rem;
+  top: 50%;
+  cursor: pointer;
+  border-radius: var(--border-radius);
+  background-color: var(--swiper-btn-bg);
+  //opacity:0.6;
+  &:hover{
+    background-color: var(--nav-item-hover);
+  }
+  & .swiper_icon{
+    width: 12px;
+    fill: var(--text);
+  }
+  &.swiper_prev{
+    left: -2.25rem;
+    & .swiper_icon{
+      transform: rotate(180deg);
+    }
+  }
+  &.swiper_next{
+    right: -2.25rem;
+  }
+}
+.banner_pagination{
+  height: 1.75rem;
+  width: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  z-index: 9;
+  @media screen and (max-width: 621px) {
+    margin-top: -1.75rem;
+  }
+  & .banner_pagination_dot_active{
+    background: #fff;
+    width: 1.75rem;
+    @media screen and (max-width: 621px) {
+      width: .625rem;
+    }
+  }
+  span{
+    cursor: pointer;
+    width: .625rem;
+    height: .625rem;
+    border-radius: .3125rem;
+    transition: width .3s linear;
+    margin: 0 .3125rem;
+    overflow: hidden;
+    background: rgba(151, 151, 151, .3);
+    @media screen and (max-width: 621px) {
+      width: .375rem;
+      height: .375rem;
+      border-radius: .1875rem;
+      margin: 0 .1875rem;
+      background: rgba(151, 151, 151, .4);
+    }
+  }
+}

+ 348 - 0
src/components/AutoWinList/index.jsx

@@ -0,0 +1,348 @@
+import styles from './styles.module.scss';
+import {useThemeProvider} from "@contexts/themeContext";
+import React, {useRef, useState,useEffect} from 'react';
+import {Swiper, SwiperSlide} from "swiper/react";
+import {Autoplay, EffectCube, EffectFade} from "swiper";
+import {StyledSwiper} from "@components/AutoBanner/style";
+import {NavLink} from "react-router-dom";
+import LazyImage from "@components/LazyImage";
+import {LazyLoadImage} from "react-lazy-load-image-component";
+import { gsap } from 'gsap';
+import lv_1 from "@assets/lvicon/gold_mini.png";
+import lv_2 from "@assets/lvicon/platinum_mini.png";
+import lv_3 from "@assets/lvicon/diamond_mini.png";
+import usdimg from "@assets/lvicon/USD.black.png";
+import symbol from '@assets/symbol-defs.svg';
+import {useNavigate} from "react-router";
+
+
+const i18n=global.i18n;
+const AutoWinList=({data})=>{
+    const {isMobile} = useThemeProvider();
+    const [currIndex,setCurrIndex]=useState(0);
+    const [swiper, setSwiper] = useState(null);
+    const navigate=useNavigate();
+    const lvicon=[lv_1,lv_2,lv_3];
+    if (data==null)data={};
+    data={
+        title:"win_list.big_wins",
+        tabs:[
+            {type:0,title:'win_list.tab_all'},
+            {type:1,title:'win_list.tab_slots'},
+        ],
+        list:[
+            {
+                img:'https://bc.imgix.net/game/image/e64b58eb7f.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'xllsdfag',
+                lv:1,
+                win:79893
+            },
+            {
+                img:'https://bc.imgix.net/game/image/eef9a463-c2e9-416a-9922-4ccd65e4898f.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'XDFLASKDJGLA',
+                lv:1,
+                win:79893
+            },
+            {
+                img:'https://bc.imgix.net/game/image/8eb116c0-8c55-4016-9d50-683c444666bf.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'XDFLASKDJGLA',
+                lv:1,
+                win:79893
+            },
+            {
+                img:'https://bc.imgix.net/game/image/898a5bb528.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'XDFLASKDJGLA',
+                lv:1,
+                win:79893
+            },
+            {
+                img:'https://bc.imgix.net/game/image/f7274b14-e905-4daf-a0c3-4be958022190.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'XDFLASKDJGLA',
+                lv:1,
+                win:79893
+            },
+            {
+                img:'https://bc.imgix.net/game/image/f7274b14-e905-4daf-a0c3-4be958022190.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'XDFLASKDJGLA',
+                lv:1,
+                win:79893
+            },
+            {
+                img:'https://bc.imgix.net/game/image/f7274b14-e905-4daf-a0c3-4be958022190.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'XDFLASKDJGLA',
+                lv:1,
+                win:79893
+            },
+            {
+                img:'https://bc.imgix.net/game/image/f7274b14-e905-4daf-a0c3-4be958022190.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'XDFLASKDJGLA',
+                lv:1,
+                win:79893
+            },{
+                img:'https://bc.imgix.net/game/image/f7274b14-e905-4daf-a0c3-4be958022190.png?auto=format&dpr=2&w=200',
+                gtype:'1',
+                uid:'1234567',
+                nick:'XDFLASKDJGLA',
+                lv:1,
+                win:79893
+            }
+        ]
+    }
+    const renderTitle=()=>{
+        return (
+            <p className={styles.list_title}>
+                <b></b>
+                {i18n.t(data.title)}
+            </p>
+        )
+    }
+    const changeTab=(index)=>{
+        setCurrIndex(index);
+    }
+    const renderTabs=()=>{
+        let tabs=data.tabs;
+        if (tabs==null)tabs=[];
+        return (
+            <div className={styles.tabs}>
+                {
+                    tabs.map((tab,index)=>{
+                        return(
+                            <button onClick={()=>{
+                                changeTab(tab.type);
+                            }} className={`${styles.tab_btn} ${tab.type==currIndex?styles.active:''}`} key={index}>
+                                <div>{i18n.t(tab.title)}</div>
+                            </button>
+                        )
+                    })
+                }
+            </div>
+        )
+    }
+    const getAutoList=()=>{
+        let list=[];
+        let all=data.list;
+        if (all!=null){
+            if (currIndex==0)list=all.slice(0);
+            else
+            {
+                for (let i = 0; i < all.length; i++) {
+                    let item=all[i];
+                    if (item){
+                        if (item.gtype==currIndex){
+                            list.push(item);
+                        }
+                    }
+                }
+            }
+        }
+        return list;
+    }
+    const containerRef=useRef(null);
+    const [rollState,setRollState]=useState(0);
+    const startRoll=()=>{
+        if (containerRef==null)return;
+        const container = containerRef.current;
+        if (container==null)return;
+        if (container.rollState==1)return;
+        if (container.firstChild==null)return;
+        const offsetX=container.firstChild.clientWidth;
+        const endIndex=container.endIndex;
+        let currIndex=container.currIndex;
+        currIndex++;
+        container.scrollLeft=currIndex*offsetX;
+        container.currIndex=currIndex;
+        if (currIndex>endIndex){
+            setRollState(2);
+        }
+        if (container.rollTimer!=null)clearTimeout(container.rollTimer)
+        container.rollTimer=setTimeout(function () {
+            startRoll();
+        },1000);
+    }
+    const initRollList=()=>{
+        if (containerRef==null)return;
+        const container = containerRef.current;
+        if (container==null)return;
+        const list=getAutoList();
+        container.endIndex=list.length;
+        container.currIndex=0;
+        startRoll();
+    }
+    const checkReset=()=>{
+        const container = containerRef.current;
+        if (container!=null){
+            const dis=container.scrollLeft;
+            const width=container.firstChild.clientWidth;
+            const index=Math.floor(dis/width);
+            const len=container.endIndex;
+            let currIndex=index<len?index:(index-len);
+            container.currIndex=currIndex;
+            container.scrollLeft=currIndex*width;
+        }
+    }
+    useEffect(()=>{
+        const container = containerRef.current;
+        if (container==null)return;
+        if (rollState==0){
+            startRoll();
+        }
+        else if (rollState==2){
+            checkReset();
+            setRollState(0);
+        }
+        container.rollState=rollState;
+    },[rollState])
+    useEffect(()=>{
+        let  container=null;
+        if (containerRef.current!=null){
+            container=containerRef.current;
+            initRollList();
+        }
+        return ()=>{
+            if (container){
+                if (container.rollTimer!=null)clearInterval(container.rollTimer);
+            }
+        };
+    },[currIndex]);
+    const formatGolds=(golds)=>{
+        golds=Number(golds);
+        let str=(golds/1000).toFixed(2);
+        return "$"+str+"K";
+    }
+    const renderHAuto=()=>{
+        let hlist=getAutoList();
+        let list=hlist.concat(hlist);
+        return (
+            <div onMouseEnter={()=>{setRollState(1)}} onMouseLeave={()=>setRollState(2)} className={`${styles.swiper_container} ${rollState==2?styles.swiper_stop:''}`} ref={containerRef}>
+                {
+                     list.map((item, index) => (
+                         <div key={index} className={styles.swiper_item} onClick={()=>{
+                             onClickLink(item.link);
+                         }}>
+                             <LazyLoadImage
+                                 src={item.img}
+                                 alt={"game-icon"}
+                                 effect={"blur"}
+                                 wrapperClassName={`lazy-image ${styles.game_icon}`}
+                             />
+                             <div className={styles.nick_name}>
+                                <img src={lvicon[item.lv-1]}/>
+                                <div className={styles.name_txt}>
+                                    {item.nick}
+                                </div>
+                             </div>
+                             <div className={styles.amount_wrap}>
+                                 {formatGolds(item.win)}
+                             </div>
+                         </div>
+                     ))
+                }
+            </div>
+        )
+    }
+    useEffect(() => {
+        if (swiper) {
+            swiper.update();
+        }
+    }, [swiper]);
+    const renderVAuto=()=>{
+        let all=data.list;
+        let list=all==null?[]:all.slice(0);
+        return(
+            <>
+                <Swiper
+                    className={styles.swiper_container_m}
+                    onSwiper={setSwiper}
+                    direction={"vertical"}
+                    slidesPerView={3}
+                    loop
+                    autoplay={{delay: 1000, disableOnInteraction: false}}
+                    effect="fade"
+                    fadeEffect={{crossFade: true}}
+                    modules={[Autoplay, EffectCube]}
+                    speed={1000}
+                >
+                    {
+                        list.map((item,index)=>{
+                            return (
+                                <SwiperSlide key={index}>
+                                    <div className={styles.swiper_item_m} onClick={()=>{
+                                        onClickLink(item.link);
+                                    }}>
+                                        <LazyLoadImage
+                                            src={item.img}
+                                            alt={"game-icon"}
+                                            effect={"blur"}
+                                            wrapperClassName={`lazy-image ${styles.game_icon_m}`}
+                                        />
+                                        <div className={styles.user_name_m}>
+                                            {item.nick}
+                                        </div>
+                                        <div className={styles.play_btn}>
+                                            <div className={styles.user_money}>
+                                                <div className={styles.amount_m}>
+                                                    {formatGolds(item.win)}
+                                                </div>
+                                                <img className={styles.coin_m} src={usdimg}/>
+                                            </div>
+                                            <svg className={styles.icon_arrow}>
+                                                <use xlinkHref={`${symbol}#icon_Arrow`} />
+                                            </svg>
+                                        </div>
+                                    </div>
+                                </SwiperSlide>
+                            )
+                        })
+                    }
+                </Swiper>
+            </>
+        )
+    }
+    const onClickLink=(link)=>{
+        if (navigate!=null){
+            navigate(link);
+        }
+    }
+    const renderDesktop=()=>{
+        return (
+            <div className={styles.list_context}>
+                {renderTabs()}
+                {renderHAuto()}
+            </div>
+        )
+    }
+    const renderMobile=()=>{
+        return (
+            <div className={styles.list_context_m}>
+                {renderVAuto()}
+            </div>
+        )
+    }
+    const renderContext=()=>{
+        let view=isMobile()?renderMobile():renderDesktop();
+        return view;
+    }
+    return (
+        <div className={`w-100 ${styles.win_list_container}`}>
+            {renderTitle()}
+            {renderContext()}
+        </div>
+    )
+}
+export default AutoWinList;

+ 192 - 0
src/components/AutoWinList/styles.module.scss

@@ -0,0 +1,192 @@
+@keyframes online-ani {
+  0% {
+    transform: scale(1);
+  }
+  100% {
+    transform: scale(2.5);
+  }
+}
+.win_list_container{
+  & .list_title{
+    font-size: .875rem;
+    line-height: 22px;
+    color: var(--header);
+    position: relative;
+    display: flex;
+    -webkit-box-align: center;
+    align-items: center;
+    margin: 0 0 8px 8px;
+    b{
+      background-color: #45d91e;
+      width: .5rem;
+      height: .5rem;
+      border-radius: .25rem;
+      position: relative;
+      margin-right: .625rem;
+      &:after{
+        content: "";
+        position: absolute;
+        display: block;
+        width: 100%;
+        height: 100%;
+        border-radius: 50%;
+        left: 50%;
+        top: 50%;
+        margin-top: -.25rem;
+        margin-left: -.25rem;
+        background-color: #45d91e4d;
+        animation: online-ani infinite 1.5s linear;
+      }
+    }
+  }
+  & .list_context{
+    background: var(--widget-grid-bg);
+    padding: 8px 6px;
+    border-radius: var(--border-radius);
+    & .tabs{
+      display: flex;
+      padding: 0 6px;
+      margin-bottom: 8px;
+      & .tab_btn{
+        padding: .3125rem .375rem;
+        line-height: 14px;
+        border-radius: var(--border-radius);
+        cursor: pointer;
+        width: auto;
+        height: auto;
+        margin-right: 2px;
+        color: var(--text);
+        &.active{
+          background-color:var(--nav-item-hover);
+          font-weight: 600;
+          color: var(--header);
+        }
+      }
+    }
+    & .swiper_stop{
+      scroll-snap-type:none !important;
+      scroll-behavior:auto !important;
+    }
+    & .swiper_container{
+      width: 100%;
+      display: flex;
+      overflow-x: auto;
+      scroll-snap-type: x mandatory;
+      scroll-behavior: smooth;
+      & .swiper_item{
+        padding: 0 6px;
+        flex: none;
+        width: 96px;
+        scroll-snap-align: start;
+        & .game_icon{
+          height: auto;
+          img {
+            border-radius: var(--border-radius);
+          }
+        }
+        & .amount_wrap{
+          color: var(--primary-color);
+          font-weight: 600;
+          font-size: 13px;
+          text-align: center;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+        }
+        & .nick_name{
+          font-size: 12px;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          margin-top: 6px;
+          color: var(--header);
+          & .name_txt{
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+          }
+          img {
+            width: 14px;
+            height: 14px;
+            display: inline-block;
+            vertical-align: top;
+            margin-right: 4px;
+          }
+        }
+      }
+    }
+  }
+  & .list_context_m{
+    background-color: var(--widget-grid-bg);
+    margin-top: .5rem;
+    border-radius: .25rem;
+    position: relative;
+    overflow: hidden;
+    height: 9.5rem;
+    padding:.5rem;
+    &::after{
+      content: '';
+      height: 3.125rem;
+      position: absolute;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background-image: var(--mask-blur);
+      z-index: 2;
+    }
+    & .swiper_container_m{
+      height: 100%;
+      & .swiper_item_m{
+        display: flex;
+        align-items: center;
+        height: 2.5rem;
+        & .game_icon_m{
+          width: 2.5rem;
+          height: 2.5rem;
+          img {
+            border-radius: var(--border-radius);
+          }
+        }
+        & .user_name_m{
+          display: flex;
+          align-items: center;
+          white-space: nowrap;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          margin: 0 .5rem;
+          width: 22%;
+          color: var(--text);
+        }
+        & .play_btn{
+          margin-left: auto;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          & .user_money{
+            display: inline-flex;
+            align-items: center;
+            white-space: nowrap;
+            justify-content: flex-end;
+            margin-right: 0.875rem;
+            & .amount_m{
+              color: var(--primary-color);
+              font-weight: 700;
+              max-width: 10em;
+              text-align: right;
+            }
+            & .coin_m{
+              width: 0.875rem;
+              height: 0.875rem;
+              margin-left: 0.2em;
+            }
+          }
+          & .icon_arrow{
+            width: .875rem;
+            height: 1.4em;
+            fill: var(--text);
+          }
+        }
+      }
+    }
+  }
+}

+ 58 - 0
src/components/ColorCards/ColorCard.jsx

@@ -0,0 +1,58 @@
+// styling
+import styles from './styles.module.scss';
+import symbol from "@assets/symbol-defs.svg";
+import React from "react";
+import {NavLink} from "react-router-dom";
+import styled from 'styled-components/macro';
+import LazyImage from "@components/LazyImage";
+import {LazyLoadImage} from "react-lazy-load-image-component";
+import symbol_m from "@assets/symbol-mobile.svg";
+const i18n=global.i18n;
+const ColorCard = ({data}) => {
+
+    // data={
+    //     link:'/casino',
+    //     img:'/bigbgs/casino.png',
+    //     icon:'icon_Casino',
+    //     title:'links.casino',
+    //     desc:'Dive into our in-house games, live casino and slots',
+    //     background:"linear-gradient(180deg, rgba(98, 22, 233, .2) 0%, rgba(98, 22, 233, 0) 100%), #1C1E22;",
+    //     background_blur:"#6216e9;",
+    //     background_btn:"linear-gradient(91deg, #5617cb 0%, #8447f6 96.49%)",
+    // }
+
+    const StyledNavLink=styled(NavLink)`
+        background:${data.background};
+    `;
+    const StyledBlur=styled.div`
+        background:${data.background_blur};
+    `;
+    const StyledGoBtn=styled.button`
+        background-image:${data.background_btn};
+    `
+    return(
+        <StyledNavLink className={styles.color_card_container} to={data.link}>
+            <StyledBlur className={styles.card_blur}></StyledBlur>
+            <div className={styles.card_mask}></div>
+            <img src={data.img} className={styles.card_img}/>
+            <div className={styles.card_content}>
+                <div className={styles.card_content_header}>
+                    <svg className={styles.icon}>
+                        <use xlinkHref={`${symbol_m}#${data.icon}`} />
+                    </svg>
+                    <div className={styles.title}>
+                        {i18n.t(data.title)}
+                    </div>
+                </div>
+                <div className={styles.desc}>
+                    {data.desc}
+                </div>
+                <StyledGoBtn className={styles.go_btn}>
+                    <div className={styles.btn_inner}>{`${i18n.t('color_card.go_to')}${i18n.t(data.title)}`}</div>
+                </StyledGoBtn>
+            </div>
+        </StyledNavLink>
+    )
+}
+
+export default ColorCard

+ 63 - 0
src/components/ColorCards/ColorCardsGrid.jsx

@@ -0,0 +1,63 @@
+// styling
+import styles from './styles.module.scss';
+import ColorCard from "@components/ColorCards/ColorCard";
+import React from "react";
+import {SwiperSlide} from "swiper/react";
+import {NavLink} from "react-router-dom";
+import LazyImage from "@components/LazyImage";
+const ColorCardsGrid = ({data}) => {
+    data=[
+        {
+            link:'/casino',
+            img:'/bigbgs/casino.png',
+            icon:'icon_Casino',
+            title:'links.casino',
+            desc:'Dive into our in-house games, live casino and slots',
+            background:"linear-gradient(180deg, rgba(98, 22, 233, .2) 0%, rgba(98, 22, 233, 0) 100%), #1C1E22;",
+            background_blur:"#6216e9;",
+            background_btn:"linear-gradient(91deg, #5617cb 0%, #8447f6 96.49%)",
+        },
+        {
+            link:'/sports',
+            img:'/bigbgs/sports.png',
+            icon:'icon_Sports',
+            title:'links.sports',
+            desc:'Experience the thrill of horse racing and enjoy the winnings',
+            background:"linear-gradient(180deg, rgba(23, 183, 69, .2) 0%, rgba(30, 130, 59, 0) 100%), #1C1E22;",
+            background_blur:"#079234;",
+            background_btn:"linear-gradient(91deg, #1e7d3a 0%, #2cae53 100%);",
+        },
+        {
+            link:'/slots',
+            img:'/bigbgs/racing.png',
+            icon:'icon_Casino',
+            title:'links.slots',
+            desc:'Slots Games',
+            background:"linear-gradient(180deg, rgba(196, 107, 2, .2) 0%, rgba(196, 107, 2, 0) 100%), #1C1E22;",
+            background_blur:"#c46b02;",
+            background_btn:"linear-gradient(91deg, #cc6504 0%, #eb9c53 100%);",
+        },
+        {
+            link:'/table_game',
+            img:'/bigbgs/trading.png',
+            icon:'icon_Casino',
+            title:'links.table_games',
+            desc:'Table Games',
+            background:"linear-gradient(180deg, rgba(217, 33, 33, .2) 0%, rgba(188, 34, 34, 0) 100%), #1C1E22;",
+            background_blur:"#f42525;",
+            background_btn:"linear-gradient(91deg, #438e15 0%, #51e61d 100%);",
+        }
+    ];
+    if (data==null)data=[];
+    return(
+        <div className={styles.color_cards_grid}>
+            {
+                data.map((item, index) => (
+                    <ColorCard data={item} key={index}></ColorCard>
+                ))
+            }
+        </div>
+    )
+}
+
+export default ColorCardsGrid

+ 114 - 0
src/components/ColorCards/styles.module.scss

@@ -0,0 +1,114 @@
+.color_card_container{
+  position: relative;
+  aspect-ratio: 326 / 330;
+  overflow: hidden;
+  border-radius: var(--border-radius);
+  & .card_blur{
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate3d(-50%, -50%, 0);
+    z-index: 1;
+    width: 109.54%;
+    aspect-ratio: 356 / 196;
+    border-radius: 22.25rem;
+    filter: blur(2.625rem);
+  }
+  & .card_mask{
+    position: absolute;
+    inset: 0;
+    z-index: 3;
+    background-image: linear-gradient(180deg, rgba(0, 0, 0, .2) 0%, rgba(0, 0, 0, 0) 100%);
+    opacity: 0;
+    transition: opacity cubic-bezier(.42,0,.03,1) .7s;
+  }
+  & .card_img{
+    position: relative;
+    z-index: 2;
+    display: block !important;
+    width: 100%;
+    object-fit: cover;
+    transition: transform cubic-bezier(.42,0,.03,1) .7s !important;
+  }
+  & .card_content{
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: -60px;
+    z-index: 10;
+    padding: 1.25rem .75rem;
+    backdrop-filter: blur(1.5625rem);
+    transition: all cubic-bezier(.42,0,.03,1) .7s;
+    & .card_content_header{
+      display: flex;
+      align-items: center;
+      margin-bottom: .75rem;
+      & .icon{
+        width: 1.375rem;
+        height: 1.375rem;
+        margin-right: .3125rem;
+      }
+      & .title{
+        line-height: 1;
+        color: #fff;
+        font-size: 1.5rem;
+        font-weight: 900;
+        text-transform: uppercase;
+        transition: color cubic-bezier(.42,0,.03,1) .7s;
+        white-space: nowrap;
+      }
+    }
+    & .desc{
+      height: 2.25rem;
+      color: #fff;
+      font-size: .875rem;
+      line-height: 1.125rem;
+      font-weight: 400;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      display: -webkit-box;
+      -webkit-line-clamp: 2;
+      -webkit-box-orient: vertical;
+      white-space: normal;
+      transition: color cubic-bezier(.42,0,.03,1) .7s;
+    }
+    & .go_btn{
+      display: block;
+      width: 100%;
+      user-select: none;
+      border-radius: var(--border-radius);
+      font-weight: 700;
+      cursor: pointer;
+      transition: transform .2s cubic-bezier(.36,.66,.04,1);
+      height: 2.5rem;
+      margin-top: .75rem;
+      color: #fff;
+      & .btn_inner{
+        display: flex;
+        align-items: center;
+        -webkit-box-pack: center;
+        justify-content: center;
+        width: 100%;
+        height: 100%;
+      }
+    }
+  }
+  &:hover{
+    & .card_content{
+      bottom: -5px;
+    }
+    & .card_img{
+      transform: scale(1.1)
+    }
+    & .card_mask{
+      opacity: 1;
+    }
+  }
+}
+.color_cards_grid{
+  display: grid;
+  grid-template-columns: repeat(4,1fr);
+  gap: .9375rem;
+  //margin-top: 40px;
+  //margin-bottom: 40px;
+}

+ 52 - 0
src/components/GameItem/index.jsx

@@ -0,0 +1,52 @@
+import styles from "@components/GameItem/styles.module.scss";
+import {NavLink} from "react-router-dom";
+import {LazyLoadImage} from "react-lazy-load-image-component";
+import symbol from "@assets/symbol-defs.svg";
+import React from "react";
+
+const GameItem=({data})=>{
+    const renderOlicon=()=>{
+        return (
+            <svg xmlns="http://www.w3.org/2000/svg" width="9" height="10" viewBox="0 0 9 10" fill="none"><path d="M7.78243 7.00126C7.98931 8.18376 7.16525 9.0422 6.01681 9.23532C4.90775 9.42188 3.09993 9.42188 1.99087 9.23532C0.842435 9.04251 0.0183723 8.18438 0.225247 7.00157C0.400872 5.99813 1.25118 5.37282 2.28993 5.42657C3.04868 5.46594 3.51493 5.56469 4.05462 5.56469C4.60056 5.56469 4.96431 5.46594 5.71743 5.42657C6.75587 5.37219 7.60712 5.99782 7.78243 7.00126ZM3.86275 0.907195C4.95368 0.907195 5.83837 1.79157 5.83837 2.88313C5.83837 3.9747 4.95368 4.85907 3.86275 4.85907C2.7715 4.85907 1.88681 3.97438 1.88681 2.88313C1.88681 1.79188 2.7715 0.907195 3.86275 0.907195Z" fill="url(#paint0_linear_451_3515)"></path><path d="M7.42806 5.14407C7.95931 5.2425 8.59868 5.525 8.85493 6.28813C9.09181 6.99375 8.81275 7.54688 8.49493 7.91282C8.39368 8.02938 8.19087 7.92813 8.24118 7.78532C8.37181 7.41594 8.42431 6.94407 8.21993 6.42157C8.02118 5.91313 7.65337 5.59282 7.31962 5.39563C7.18025 5.31344 7.26681 5.11407 7.42806 5.14407ZM5.63118 0.938442C5.47243 0.825317 5.58681 0.582817 5.77868 0.631255C5.98087 0.682192 6.189 0.761567 6.38306 0.883442C7.02337 1.28657 7.33993 1.99969 7.22712 2.79188C7.17462 3.16157 7.01056 3.44907 6.82587 3.66625C6.70431 3.80969 6.46431 3.69032 6.51431 3.51157C6.63181 3.09219 6.6665 2.55657 6.44056 1.96813C6.25431 1.48344 5.93587 1.15625 5.63118 0.938442Z" fill="#26AB4B"></path><defs><linearGradient id="paint0_linear_451_3515" x1="5.21662" y1="0.333274" x2="5.21662" y2="9.37524" gradientUnits="userSpaceOnUse"><stop stop-color="#85F25E"></stop><stop offset="1" stop-color="#33EF00"></stop></linearGradient></defs></svg>
+        );
+    }
+    const formatNum=(num)=>{
+        num=Number(num);
+        let snum=num+"";
+        if (num>=1000){
+            snum=(num/1000).toFixed(2)+"K";
+        }
+        return snum;
+    }
+    return (
+        <div className={styles.grid_item}>
+            <NavLink to={data.link} className={styles.game_img_wrap}>
+                <LazyLoadImage
+                    src={data.img}
+                    alt={"game-icon"}
+                    effect={"blur"}
+                    wrapperClassName={`lazy-image ${styles.game_img}`}
+                />
+                <div className={styles.online}>
+                    {
+                        renderOlicon()
+                    }
+                    <span>{formatNum(data.online)}</span>
+                </div>
+            </NavLink>
+            <div className={styles.bot_info}>
+                <div className={styles.game_tit}>
+                    <NavLink className={styles.game_desc}>
+                        {data.desc}
+                    </NavLink>
+                    <button>
+                        <svg className={`${styles.game_desc_icon}`}>
+                            <use xlinkHref={`${symbol}#icon_Inform`} />
+                        </svg>
+                    </button>
+                </div>
+            </div>
+        </div>
+    )
+}
+export default GameItem;

+ 62 - 0
src/components/GameItem/styles.module.scss

@@ -0,0 +1,62 @@
+.grid_item{
+  scroll-snap-align: start;
+  width: 100%;
+  background-color: var(--widget-grid-bg);
+  border-radius: var(--border-radius);
+  & .game_img_wrap{
+    width: 100%;
+    display: block;
+    position: relative;
+    & .game_img{
+      position: relative;
+      img{
+        width: 100%;
+        height: 100%;
+        border-top-left-radius: var(--border-radius);
+        border-top-right-radius: var(--border-radius);
+      }
+    }
+    & .online{
+      position: absolute;
+      display: flex;
+      align-items: center;
+      top: .375rem;
+      right: .375rem;
+      height: 1rem;
+      font-size: .75rem;
+      background-color: rgba(0, 0, 0, .4);
+      border-radius: .5rem;
+      padding: 0 .375rem;
+      line-height: .75rem;
+      backdrop-filter: blur(3px);
+      span{
+        color: #fff;
+        font-weight: 600;
+        margin-left: .25rem;
+      }
+    }
+  }
+  & .bot_info{
+    padding: .625rem;
+    & .game_tit{
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      font-weight: 600;
+      height: 1.25rem;
+      & .game_desc{
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        width: 1px;
+        flex: auto;
+        color: var(--text);
+      }
+      & .game_desc_icon{
+        width: 1.4em;
+        height: 1.4em;
+        fill: var(--text);
+      }
+    }
+  }
+}

+ 120 - 0
src/components/GameList/index.jsx

@@ -0,0 +1,120 @@
+import styles from "@components/GameList/styles.module.scss";
+import React from "react";
+import GameItem from "@components/GameItem";
+const i18n =global.i18n;
+const GameList=({data})=>{
+    data={
+        type:0,//游戏分类
+        page:1,
+        pageSize:40,
+        total:42,
+        totalPage:1,
+        data:[
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/f76d3f4a-9712-4bd1-9426-f3ab7069b6a2.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:1006,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/ff0b0511-1f48-43d1-8dd3-294bb6437b4b.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/d33b3fad-b6df-450b-bd85-528a17383043.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/5b3a4099-9462-4d3c-a854-ade78aad0d60.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/f76d3f4a-9712-4bd1-9426-f3ab7069b6a2.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/ff0b0511-1f48-43d1-8dd3-294bb6437b4b.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/d33b3fad-b6df-450b-bd85-528a17383043.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/5b3a4099-9462-4d3c-a854-ade78aad0d60.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+        ]
+    }
+    const renderGameList=()=>{
+        let list=data.data;
+        if (list==null)list=[];
+        return (
+            <div className={styles.game_list}>
+                {
+                    list.map((item,index)=>{
+                        return (
+                            <GameItem key={index} data={item}></GameItem>
+                        )
+                    })
+                }
+            </div>
+        )
+    }
+    const renderProgress=()=>{
+        let list=data.data;
+        if (list==null)list=[];
+        let current=list.length;
+        let total=data.total;
+        let progress=Math.ceil((current/total)*100);
+        let spro=progress+"%";
+        return (
+            <div className={styles.page_progress}>
+                <span>{current} / {total}</span>
+                <div className={styles.progress}>
+                    <div className={styles.green} style={{width:spro}}></div>
+                </div>
+                <span>{spro}</span>
+            </div>
+        )
+    }
+    const renderShowMoreBtn=()=>{
+        return (
+            <div className={styles.show_more}>
+                <button className={styles.more_btn}>
+                    <div>{i18n.t("game_list.show_more")}</div>
+                </button>
+            </div>
+        )
+    }
+    return (
+        <div className={`w-100 ${styles.list_container}`}>
+            {renderGameList()}
+            {renderProgress()}
+            {renderShowMoreBtn()}
+        </div>
+    )
+}
+export default GameList;

+ 52 - 0
src/components/GameList/styles.module.scss

@@ -0,0 +1,52 @@
+.list_container {
+  //margin-top: 2.75rem;
+  & .game_list {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+    gap: 1.25rem .75rem;
+    @media screen and (max-width: 621px){
+      grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
+    }
+  }
+  & .page_progress {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-bottom: 1.0625rem;
+    margin-top: 3.75rem;
+    & .progress {
+      width: 5rem;
+      height: .375rem;
+      border-radius: var(--border-radius);
+      background-color: #31343c;
+      position: relative;
+      margin: 0 .3125rem;
+      & .green{
+        position: absolute;
+        left: 0;
+        height: 100%;
+        border-radius: var(--border-radius);
+        top: 0;
+        max-width: 100%;
+        background-color: var(--primary-color);
+      }
+    }
+  }
+  & .show_more{
+    display: flex;
+    width: 100%;
+    align-items: center;
+    justify-content: center;
+    & .more_btn{
+      background: var(--nav-item-hover);
+      border-radius: var(--border-radius);
+      height: 44px;
+      line-height: 44px;
+      min-width: 148px;
+      width: auto;
+      margin: 0 auto;
+      font-weight: 600;
+      color: var(--header);
+    }
+  }
+}

+ 21 - 0
src/components/GameSearch/index.jsx

@@ -0,0 +1,21 @@
+import styles from "@components/GameSearch/styles.module.scss";
+import symbol from '@assets/symbol-defs.svg';
+import React from "react";
+import {useThemeProvider} from "@contexts/themeContext";
+const i18n=global.i18n;
+const GameSearch=({data})=>{
+    const {isShowSearch,searchData,showGameSearchPanel,isMobile} = useThemeProvider();
+    return (
+        <div className={styles.game_search} onClick={()=>{
+            if (showGameSearchPanel)showGameSearchPanel(true,null);
+        }}>
+            <div className={styles.icon_wrap}>
+                <svg className={styles.icon}>
+                    <use xlinkHref={`${symbol}#icon_Search`} />
+                </svg>
+            </div>
+            <p>{i18n.t('game_search.input_ph')}</p>
+        </div>
+    )
+}
+export default GameSearch;

+ 36 - 0
src/components/GameSearch/styles.module.scss

@@ -0,0 +1,36 @@
+.game_search{
+  width: 100%;
+  height: 2.75rem;
+  background-color: var(--nav-item-bg);
+  border: 1px solid rgba(255, 255, 255, .1);
+  display: flex;
+  border-radius: var(--border-radius);
+  align-items: center;
+  color: var(--text);
+  opacity: 0.7;
+  & .icon_wrap {
+    width: 3rem;
+    height: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    & .icon{
+      width: 1.4em;
+      height: 1.4em;
+      margin-left: .25rem;
+      font-size: .75rem;
+      fill: var(--text);
+    }
+  }
+  p{
+    border: 1px solid transparent;
+    white-space: nowrap;
+    width: 100%;
+    margin: 0;
+    height: 100%;
+    line-height: 2.625rem;
+    cursor: pointer;
+    color: var(--text);
+  }
+}

+ 167 - 0
src/components/GameSmallList/index.jsx

@@ -0,0 +1,167 @@
+import styles from "@components/GameSmallList/styles.module.scss";
+import React, {useRef, useEffect, useState} from "react";
+import {NavLink} from "react-router-dom";
+import symbol from "@assets/symbol-defs.svg";
+import {LazyLoadImage} from "react-lazy-load-image-component";
+import {useThemeProvider} from "@contexts/themeContext";
+import GameItem from "@components/GameItem";
+
+const i18n=global.i18n;
+const GameSmallList=({data})=>{
+    const {isMobile} = useThemeProvider();
+    const gridListRef=useRef(null);
+    //0 滚动条顶部 1滚动条中间 2滚动条底部
+    const [pageState, setPageState] = useState(0);
+    data={
+        title:"BC Originals",
+        data:[
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/f76d3f4a-9712-4bd1-9426-f3ab7069b6a2.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:1006,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/ff0b0511-1f48-43d1-8dd3-294bb6437b4b.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/d33b3fad-b6df-450b-bd85-528a17383043.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/5b3a4099-9462-4d3c-a854-ade78aad0d60.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/f76d3f4a-9712-4bd1-9426-f3ab7069b6a2.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/ff0b0511-1f48-43d1-8dd3-294bb6437b4b.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/d33b3fad-b6df-450b-bd85-528a17383043.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+            {
+                gid:0,//gameid
+                img:"https://bc.imgix.net/game/image/5b3a4099-9462-4d3c-a854-ade78aad0d60.png?auto=format&dpr=3&w=200",//图片
+                link:"",//跳转的链接
+                online:100,//游戏人数
+                desc:"BC Originals",//描述
+            },
+        ],
+        link:""//view all
+    }
+    let list=data.data;
+    if (list==null)list=[];
+    const handleScroll=()=>{
+        const gridList=gridListRef.current;
+        if (gridList!=null){
+            const { scrollLeft, clientWidth, scrollWidth } = gridList;
+            if (scrollLeft==0)setPageState(0);
+            else
+            {
+                if (scrollWidth-scrollLeft==clientWidth)setPageState(2);
+                else setPageState(1);
+            }
+        }
+    }
+    useEffect(() => {
+        const gridList=gridListRef.current;
+        if (gridList) {
+            gridList.addEventListener('scroll', handleScroll);
+
+            return () => {
+                gridList.removeEventListener('scroll', handleScroll);
+            };
+        }
+    }, []); // 空数组表示只在组件挂载和卸载时执行
+    const onChagePage=(change=1)=>{
+        const gridList=gridListRef.current;
+        if (gridList){
+            const { scrollLeft, clientWidth } = gridList;
+            gridList.scrollLeft=scrollLeft+(change*clientWidth);
+        }
+    }
+    const renderBtns=()=>{
+        const preState=(pageState==0);
+        const nextState=(pageState==2);
+        return (
+            <>
+                <button className={styles.slide_btn} disabled={preState} onClick={()=>{
+                    onChagePage(-1);
+                }}>
+                    <svg className={`${styles.btn_arrow} ${styles.btn_left}`}>
+                        <use xlinkHref={`${symbol}#icon_Arrow`} />
+                    </svg>
+                </button>
+                <button className={styles.slide_btn} disabled={nextState} onClick={()=>{
+                    onChagePage(1);
+                }}>
+                    <svg className={`${styles.btn_arrow}`}>
+                        <use xlinkHref={`${symbol}#icon_Arrow`} />
+                    </svg>
+                </button>
+            </>
+        );
+    }
+    const renderTop=()=>{
+        return (
+            <div className={styles.grid_top}>
+                <div className={styles.grid_tit}>
+                    <span>{data.title}</span>
+                </div>
+                <div className={styles.grid_nav}>
+                    <NavLink to={data.link}>
+                        <span>{i18n.t('small_list.view_all')}</span>
+                    </NavLink>
+                    {
+                        isMobile()?(<></>):renderBtns()
+                    }
+                </div>
+            </div>
+        );
+    }
+    const renderList=()=>{
+        return (
+            <div className={styles.grid_list} ref={gridListRef}>
+                {
+                    list.map((item,index)=>{
+                        return (
+                            <GameItem key={index} data={item}></GameItem>
+                        )
+                    })
+                }
+            </div>
+        )
+    }
+    return (
+        <div className={`w-100 ${styles.list_container}`}>
+            {renderTop()}
+            {renderList()}
+        </div>
+    )
+}
+export default GameSmallList;

+ 74 - 0
src/components/GameSmallList/styles.module.scss

@@ -0,0 +1,74 @@
+.list_container{
+  //margin-top: 2.75rem;
+  & .grid_top{
+    display: flex;
+    flex-direction: row;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+    margin-bottom: 10px;
+    & .grid_tit{
+      font-size: 1.25rem;
+      align-items: center;
+      height: 32px;
+      line-height: 32px;
+      display: flex;
+      color: var(--header);
+    }
+    & .grid_nav{
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 1px;
+      a{
+        display: block;
+        color: var(--primary-color);
+        padding: 0 .625rem;
+        height: 2.25rem;
+        line-height: 2.25rem;
+        font-size: .875rem;
+        font-weight: 400;
+      }
+      & .slide_btn{
+        width: 2.25rem;
+        height: 2.25rem;
+        background-color: var(--standard);
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+        border-radius: var(--border-radius);
+        &:disabled{
+          opacity: .5;
+          cursor: default;
+        }
+        &:enabled{
+          &:hover{
+            background-color: var(--nav-item-hover) !important;
+          }
+        }
+        & .btn_arrow{
+          width: 12px;
+          fill: var(--text);
+        }
+        & .btn_left{
+          transform: rotate(180deg);
+        }
+      }
+    }
+  }
+  & .grid_list{
+    --grid-gap: .75rem;
+    row-gap: 1.25rem;
+    display: grid;
+    grid-template-rows: repeat(var(--grid-rows), 1fr);
+    grid-auto-flow: column;
+    grid-auto-columns: calc(100% / var(--grid-num) - (var(--grid-num) - 1)* var(--grid-gap) / var(--grid-num));
+    gap: var(--grid-gap);
+    position: relative;
+    scroll-snap-type: x mandatory;
+    scroll-behavior: smooth;
+    overflow-x: auto;
+    overflow-y: hidden;
+  }
+}

+ 52 - 0
src/components/GameTabs/index.jsx

@@ -0,0 +1,52 @@
+import styles from "@components/GameTabs/styles.module.scss";
+import React, {useState} from "react";
+import symbol from '@assets/symbol-defs.svg';
+
+const GameTabs=({data})=>{
+    const [tabIndex, setTabIndex] = useState(0);
+    data={
+        tabs:[
+            {type:0,title:'Lobby',icon:'icon_Lobby'},
+            {type:1,title:'Top Picks',icon: 'icon_ClassicDice'},
+            {type:2,title:'Slots',icon: 'icon_Slots'},
+            {type:3,title:'Hot Games',icon: 'icon_HotGame'},
+            {type:4,title:'Live Casino',icon: 'icon_LiveCasino'},
+            {type:5,title:'Table Games',icon: 'icon_TableGame'},
+            {type:6,title:'New Releases',icon: 'icon_NewReleases'},
+        ],
+        data:{
+
+        }
+    }
+    const changeTab=(tabIndex)=>{
+        setTabIndex(tabIndex);
+    }
+    const renderTabs=()=>{
+        let tabs=data.tabs;
+        if (tabs==null)tabs=[];
+        return (
+            <div className={styles.game_tabs}>
+                {
+                    tabs.map((item,index)=>{
+                        return (
+                            <button key={index} onClick={()=>{
+                                changeTab(item.type);
+                            }} className={`${styles.tab_item} ${item.type==tabIndex?styles.active:''}`}>
+                                <svg className={styles.icon}>
+                                    <use xlinkHref={`${symbol}#${item.icon}`} />
+                                </svg>
+                                {item.title}
+                            </button>
+                        )
+                    })
+                }
+            </div>
+        )
+    }
+    return (
+        <div className={`w-100 ${styles.container}`}>
+            {renderTabs()}
+        </div>
+    )
+}
+export default GameTabs;

+ 45 - 0
src/components/GameTabs/styles.module.scss

@@ -0,0 +1,45 @@
+.container{
+  //padding-top: 1.5rem;
+  position: relative;
+  & .game_tabs{
+    display: flex;
+    align-items: center;
+    flex-wrap: wrap;
+    gap: .75rem;
+    & .tab_item{
+      background: var(--nav-item-bg);
+      padding: .75rem 1.125rem;
+      border-radius: var(--border-radius);
+      //margin-right: .75rem;
+      //margin-top: .625rem;
+      width: auto;
+      font-weight: 400;
+      white-space: nowrap;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: var(--text);
+      &:hover,&.active{
+        background: var(--nav-item-hover);
+        color: var(--header);
+        font-weight: 600;
+        & .icon{
+          fill: var(--header);
+        }
+      }
+      & .icon{
+        margin-right: .375rem;
+        width: 1.4em;
+        height: 1.4em;
+        fill: var(--text);
+      }
+    }
+    @media screen and (max-width: 621px) {
+      flex-wrap: nowrap;
+      width: 100%;
+      overflow-x: auto;
+      scroll-snap-type: x mandatory;
+      scroll-behavior: smooth;
+    }
+  }
+}

+ 1 - 1
src/components/LazyImage.jsx

@@ -27,4 +27,4 @@ LazyImage.propTypes = {
     className: PropTypes.string
 }
 
-export default LazyImage
+export default LazyImage

+ 16 - 1
src/contexts/themeContext.js

@@ -26,6 +26,13 @@ export const ThemeProvider = ({children}) => {
 
     const [headerHeight,setHeaderHeight]=useState('64px');
 
+    //GameSearch
+    const [isShowSearch,setIsShowSearch]=useState(false);
+
+    const [searchData,setSearchData]=useState(null);
+
+    //GameSearch
+
     const stopTransition = () => {
         page.classList.add('no-transition');
         setTimeout(() => page.classList.remove('no-transition'), 100);
@@ -58,6 +65,11 @@ export const ThemeProvider = ({children}) => {
         setHeaderHeight(height);
     }
 
+    const showGameSearchPanel=(show,data)=>{
+        setIsShowSearch(show);
+        setSearchData(data);
+    }
+
     useEffect(() => {
 
         page.style.setProperty('--font-scale', fontScale);
@@ -88,7 +100,10 @@ export const ThemeProvider = ({children}) => {
                 isTablet,
                 isMobile,
                 headerHeight,
-                initHeaderHeight
+                initHeaderHeight,
+                isShowSearch,
+                searchData,
+                showGameSearchPanel
             }}>
             {children}
         </ThemeContext.Provider>

+ 153 - 153
src/db/routes.js

@@ -1,155 +1,155 @@
 export const routes=[
-    {
-        path:"/",//路由
-        type:0,//0嵌入,1弹窗,2全屏
-        side:0,//是否在侧边栏
-        block:0,//块排序
-        title:"",//标题 i118内link下的key
-        icon:"",//icon
-        fill:";;",//icon 颜色 默认 选中 本身颜色
-        component:"Home",//组件
-        query: "",//参数
-        login:0,//0,1 检不检查登录
-        lpath:"#/signin",//登录path
-        subs:[],//子路由
-    },
-    {
-        path: "/bonus",
-        type: 0,
-        side: 0,
-        block:0,//块排序
-        title: "",
-        icon:"",
-        fill:";;",//icon 颜色 默认 选中 本身颜色
-        component:"Bonus",
-        login:0,//0,1 检不检查登录
-        lpath:"#/signin",//登录path
-        subs:[]
-    },
-    {
-        path: "/casino",
-        type: 0,
-        side: 1,
-        block:0,//块排序
-        title: "casino",
-        icon:"icon_Casino",
-        fill:";;#3bc117",
-        component:"Casino",
-        login:0,
-        lpath:"#/signin",
-        subs:[
-            {
-                path: "/slots",
-                type: 0,
-                side: 1,
-                block:0,//块排序
-                title: "slots",
-                icon:"icon_Slots",
-                fill:";;",
-                component:"Game",
-                login:0,
-                lpath:"#/signin",
-                subs:[
-                    {
-                        path: "/hot_game",
-                        type: 0,
-                        side: 1,
-                        block:0,//块排序
-                        title: "hot_games",
-                        icon:"icon_HotGame",
-                        fill:";;",
-                        component:"Game",
-                        login:0,
-                        lpath:"#/signin",
-                        subs:[]
-                    }
-                ]
-            },
-            {
-                path: "/table_games",
-                type: 0,
-                side: 1,
-                block:0,//块排序
-                title: "table_games",
-                icon:"icon_TableGame",
-                fill:";;",
-                component:"Game",
-                login:0,
-                lpath:"#/signin",
-                subs:[
-
-                ]
-            }
-        ]
-    },
-    {
-        path: "/sports",
-        type: 0,
-        side: 1,
-        block:0,//块排序
-        title: "sports",
-        icon:"icon_Sports",
-        fill:";;#ea6518",//icon 颜色 默认 选中 本身颜色
-        component:"Sports",
-        login:0,//0,1 检不检查登录
-        lpath:"#/signin",//登录path
-        subs:[
-            {
-                path: "/live_events",
-                type: 0,
-                side: 1,
-                block:0,//块排序
-                title: "live_events",
-                icon:"icon_LiveEvents",
-                fill:";;",
-                component:"Game",
-                login:0,
-                lpath:"#/signin",
-                subs:[
-
-                ]
-            },
-            {
-                path: "/basketball",
-                type: 0,
-                side: 1,
-                block:0,//块排序
-                title: "basketball",
-                icon:"icon_Basketball",
-                fill:";;",
-                component:"Game",
-                login:0,
-                lpath:"#/signin",
-                subs:[
-
-                ]
-            }
-        ]
-    },
-    {
-        path: "#/signin",
-        type: 1,
-        side: 0,
-        block:1,//块排序
-        title: "signin",
-        icon:"",
-        fill:";;",//icon 颜色 默认 选中 本身颜色
-        component:"SignIn",
-        login:0,//0,1 检不检查登录
-        lpath:"#/signin",//登录path
-        subs:[]
-    },
-    {
-        path: "#/signup",
-        type: 1,
-        side: 0,
-        block:1,//块排序
-        title: "signup",
-        icon:"",
-        fill:";;",//icon 颜色 默认 选中 本身颜色
-        component:"SignUp",
-        login:0,//0,1 检不检查登录
-        lpath:"#/signin",//登录path
-        subs:[]
-    },
+    // {
+    //     path:"/",//路由
+    //     type:0,//0嵌入,1弹窗,2全屏
+    //     side:0,//是否在侧边栏
+    //     block:0,//块排序
+    //     title:"",//标题 i118内link下的key
+    //     icon:"",//icon
+    //     fill:";;",//icon 颜色 默认 选中 本身颜色
+    //     component:"Home",//组件
+    //     query: "",//参数
+    //     login:0,//0,1 检不检查登录
+    //     lpath:"#/signin",//登录path
+    //     subs:[],//子路由
+    // },
+    // {
+    //     path: "/bonus",
+    //     type: 0,
+    //     side: 0,
+    //     block:0,//块排序
+    //     title: "",
+    //     icon:"",
+    //     fill:";;",//icon 颜色 默认 选中 本身颜色
+    //     component:"Bonus",
+    //     login:0,//0,1 检不检查登录
+    //     lpath:"#/signin",//登录path
+    //     subs:[]
+    // },
+    // {
+    //     path: "/casino",
+    //     type: 0,
+    //     side: 1,
+    //     block:0,//块排序
+    //     title: "casino",
+    //     icon:"icon_Casino",
+    //     fill:";;#3bc117",
+    //     component:"Casino",
+    //     login:0,
+    //     lpath:"#/signin",
+    //     subs:[
+    //         {
+    //             path: "/slots",
+    //             type: 0,
+    //             side: 1,
+    //             block:0,//块排序
+    //             title: "slots",
+    //             icon:"icon_Slots",
+    //             fill:";;",
+    //             component:"Game",
+    //             login:0,
+    //             lpath:"#/signin",
+    //             subs:[
+    //                 {
+    //                     path: "/hot_game",
+    //                     type: 0,
+    //                     side: 1,
+    //                     block:0,//块排序
+    //                     title: "hot_games",
+    //                     icon:"icon_HotGame",
+    //                     fill:";;",
+    //                     component:"Game",
+    //                     login:0,
+    //                     lpath:"#/signin",
+    //                     subs:[]
+    //                 }
+    //             ]
+    //         },
+    //         {
+    //             path: "/table_games",
+    //             type: 0,
+    //             side: 1,
+    //             block:0,//块排序
+    //             title: "table_games",
+    //             icon:"icon_TableGame",
+    //             fill:";;",
+    //             component:"Game",
+    //             login:0,
+    //             lpath:"#/signin",
+    //             subs:[
+    //
+    //             ]
+    //         }
+    //     ]
+    // },
+    // {
+    //     path: "/sports",
+    //     type: 0,
+    //     side: 1,
+    //     block:0,//块排序
+    //     title: "sports",
+    //     icon:"icon_Sports",
+    //     fill:";;#ea6518",//icon 颜色 默认 选中 本身颜色
+    //     component:"Sports",
+    //     login:0,//0,1 检不检查登录
+    //     lpath:"#/signin",//登录path
+    //     subs:[
+    //         {
+    //             path: "/live_events",
+    //             type: 0,
+    //             side: 1,
+    //             block:0,//块排序
+    //             title: "live_events",
+    //             icon:"icon_LiveEvents",
+    //             fill:";;",
+    //             component:"Game",
+    //             login:0,
+    //             lpath:"#/signin",
+    //             subs:[
+    //
+    //             ]
+    //         },
+    //         {
+    //             path: "/basketball",
+    //             type: 0,
+    //             side: 1,
+    //             block:0,//块排序
+    //             title: "basketball",
+    //             icon:"icon_Basketball",
+    //             fill:";;",
+    //             component:"Game",
+    //             login:0,
+    //             lpath:"#/signin",
+    //             subs:[
+    //
+    //             ]
+    //         }
+    //     ]
+    // },
+    // {
+    //     path: "#/signin",
+    //     type: 1,
+    //     side: 0,
+    //     block:1,//块排序
+    //     title: "signin",
+    //     icon:"",
+    //     fill:";;",//icon 颜色 默认 选中 本身颜色
+    //     component:"SignIn",
+    //     login:0,//0,1 检不检查登录
+    //     lpath:"#/signin",//登录path
+    //     subs:[]
+    // },
+    // {
+    //     path: "#/signup",
+    //     type: 1,
+    //     side: 0,
+    //     block:1,//块排序
+    //     title: "signup",
+    //     icon:"",
+    //     fill:";;",//icon 颜色 默认 选中 本身颜色
+    //     component:"SignUp",
+    //     login:0,//0,1 检不检查登录
+    //     lpath:"#/signin",//登录path
+    //     subs:[]
+    // },
 ]

+ 31 - 0
src/features/api.js

@@ -0,0 +1,31 @@
+import {createAsyncThunk} from "@reduxjs/toolkit";
+import {APP_ROUTES, REQ_STATUS} from "@api/config";
+import Http from "@api/request";
+
+export const buildReqReducer=(builder,thunk,callback)=>{
+    if (builder==null||thunk==null)return;
+    builder
+        .addCase(thunk.pending, (state) => {
+            //正在请求
+            state.status=REQ_STATUS.LOADING;
+        })
+        .addCase(thunk.fulfilled, (state, action) => {
+            //请求成功
+            if (callback)callback(state,action);
+            state.status=REQ_STATUS.IDLE;
+        })
+        .addCase(thunk.rejected, (state) => {
+            //请求失败
+            state.status=REQ_STATUS.FAILED;
+        });
+}
+//api
+//路由
+export const reqGetRoutes = createAsyncThunk(
+    APP_ROUTES,
+    async (thunkAPI) => {
+        const response = await Http.get({url:APP_ROUTES})
+        return response;
+    },
+)
+//api

+ 23 - 5
src/features/mainSlice.js

@@ -1,6 +1,8 @@
-import {createSlice} from "@reduxjs/toolkit";
+import {createAsyncThunk, createSlice} from "@reduxjs/toolkit";
 import {routes} from "@db/routes";
 import {ROUTE_TYPE_FULL, ROUTE_TYPE_IN, ROUTE_TYPE_OUTLINK, ROUTE_TYPE_POP} from "@constants/routes";
+import {REQ_STATUS} from "@api/config";
+import {buildReqReducer, reqGetRoutes} from "@features/api";
 const generateNavs=(routes)=>{
     const navs=[];
     const allNavs=[];
@@ -42,22 +44,37 @@ const generateRoutes=(routes,asyncRoutes=[])=>{
     const allroutes=[...routes, ...asyncRoutes];
     _generate(allroutes,"");
     const gnavs=generateNavs(allroutes);
-    // console.log(allMap)
     return {allMap,inRoutes,popRoutes,blockNavs:gnavs.navs,allNavs:gnavs.allNavs};
 }
+const reducerRoutes=(state,action)=>{
+    let json=action.payload;
+    if (json&&json.code==0){
+        let data=json.data==null?[]:json.data;
+        let generData=generateRoutes(routes,data);
+        Object.assign(state, {routes:routes,asyncRoutes:data,...generData});
+    }
+}
 export const Main=createSlice({
     name: 'main',
     initialState: {
+        status:REQ_STATUS.NONE,
         routes:routes,
         asyncRoutes:[],
         ...generateRoutes(routes)
     },
     reducers: {
-
-    }
+    },
+    extraReducers: (builder) => {
+        // Add reducers for additional action types here, and handle loading state as needed
+        // reducerRoutes(builder);
+        buildReqReducer(builder,reqGetRoutes,reducerRoutes);
+    },
 })
 export const {} = Main.actions;
-
+//数据获取
+export const getFetchStatus=(state)=>{
+    return state.main.status;
+}
 export const routesMap=(state)=>{
     return state.main.allMap;
 }
@@ -98,5 +115,6 @@ export const isOutRoute=(path)=>(state)=>{
     if (route)return route.type===ROUTE_TYPE_OUTLINK;
     return false;
 }
+//数据获取
 export default Main.reducer;
 

+ 43 - 32
src/layout/AppGrid.jsx

@@ -27,41 +27,52 @@ const AppGrid = ({widgets, id}) => {
         lg: width >= 1280 && width < 1500,
         xl: width >= 1500
     }
-    const rowH=180;
+    const rowH=220;
+    // return (
+    //     <div className="layout">
+    //         {
+    //             width >= 768 ?
+    //                 <ResponsiveGridLayout
+    //                     className="w-100"
+    //                     layouts={layouts[id]}
+    //                     breakpoints={breakpoints}
+    //                     cols={{xl: 4, lg: 3, md: 2}}
+    //                     rowHeight={fontScale === 1 ? rowH : rowH + (fontScale * 3)}
+    //                     isDraggable={false}
+    //                     isResizable={false}
+    //                     margin={[25, 20]}
+    //                     autoSize={true}
+    //                     useCSSTransforms={false}
+    //                 >
+    //                     {
+    //                         Object.keys(widgets).map(widget => (
+    //                             <div key={widget}>
+    //                                 {widgets[widget]}
+    //                             </div>
+    //                         ))
+    //                     }
+    //                 </ResponsiveGridLayout>
+    //                 :
+    //                 <>
+    //                     {
+    //                         Object.keys(widgets).map(widget => (
+    //                             <Fragment key={widget}>
+    //                                 {widgets[widget]}
+    //                             </Fragment>
+    //                         ))
+    //                     }
+    //                 </>
+    //         }
+    //     </div>
+    // )
     return (
         <div className="layout">
             {
-                width >= 768 ?
-                    <ResponsiveGridLayout
-                        className="w-100"
-                        layouts={layouts[id]}
-                        breakpoints={breakpoints}
-                        cols={{xl: 4, lg: 3, md: 2}}
-                        rowHeight={fontScale === 1 ? rowH : rowH + (fontScale * 3)}
-                        isDraggable={false}
-                        isResizable={false}
-                        margin={[25, 20]}
-                        autoSize={true}
-                        useCSSTransforms={false}
-                    >
-                        {
-                            Object.keys(widgets).map(widget => (
-                                <div key={widget}>
-                                    {widgets[widget]}
-                                </div>
-                            ))
-                        }
-                    </ResponsiveGridLayout>
-                    :
-                    <>
-                        {
-                            Object.keys(widgets).map(widget => (
-                                <Fragment key={widget}>
-                                    {widgets[widget]}
-                                </Fragment>
-                            ))
-                        }
-                    </>
+                Object.keys(widgets).map(widget => (
+                    <Fragment key={widget}>
+                     {widgets[widget]}
+                    </Fragment>
+                ))
             }
         </div>
     )

+ 114 - 0
src/layout/GameSearchPanel/index.jsx

@@ -0,0 +1,114 @@
+import {useThemeProvider} from "@contexts/themeContext";
+import styles from "./styles.module.scss";
+import {useSidebar} from "@contexts/sidebarContext";
+import {animated,easings} from "@react-spring/web";
+import {useSpring} from "@react-spring/core";
+import React, {useState,useEffect} from "react";
+import symbol from '@assets/symbol-defs.svg';
+const i18n=global.i18n;
+
+const GameSearchPanel=()=>{
+    const {open} = useSidebar();
+    const {isShowSearch,searchData,showGameSearchPanel,isMobile} = useThemeProvider();
+    const sideleft=open?'var(--side-open)':'var(--side-close)';
+    const left=isMobile()?'0px':sideleft;
+    const [show,setShow]=useState(false);
+    const [inputFocus,setInputFocus]=useState(false);
+    const [inputValue, setInputValue] = useState('');
+    const duration=200;
+    var timer=null;
+    useEffect(() => {
+        if(setShow)setShow(isShowSearch);
+    }, [isShowSearch])
+    useEffect(()=>{
+        return () => {
+            if (timer!=null)clearTimeout(timer);
+        };
+    },[])
+    const deskAni=useSpring({
+        from: {transform: 'translateY(-100%) translateZ(0px)'},
+        transform:show?'translateY(0%) translateZ(0px)':'translateY(-100%) translateZ(0px)',
+        config: {
+            duration:duration,
+        }
+    });
+    const hidePanel=()=>{
+        setInputValue("");
+        setShow(false);
+        timer=setTimeout(()=>{
+            if (showGameSearchPanel)showGameSearchPanel(false,null);
+        },duration);
+    }
+    const renderInput=()=>{
+        return (
+            <div className={`${styles.search_input_wrap} ${inputFocus?styles.focus:''}`}>
+                <div className={styles.input_before}>
+                    <svg className={styles.icon}>
+                        <use xlinkHref={`${symbol}#icon_Search`} />
+                    </svg>
+                </div>
+                <input
+                    onFocus={(e)=>{
+                        setInputFocus(true);
+                    }}
+                    onBlur={(e)=>{
+                        setInputFocus(false);
+                    }}
+                    value={inputValue}
+                    onChange={(e) => setInputValue(e.target.value)}
+                    // autoFocus={true}
+                    className={styles.search_input} type="text" placeholder={i18n.t("game_search.input_ph")}/>
+                {
+                    inputValue==""?(<></>):(
+                        <button className={styles.clear_btn} onClick={()=>{
+                            setInputValue("");
+                        }}>
+                            <svg className={styles.icon}>
+                                <use xlinkHref={`${symbol}#icon_Close`} />
+                            </svg>
+                        </button>
+                    )
+                }
+                <div className={styles.input_after} onClick={()=>{
+                    hidePanel();
+                }}>
+                    <p>{i18n.t("game_search.cancel")}</p>
+                </div>
+            </div>
+        )
+    }
+    const renderDesktop=()=>{
+        return (
+            <animated.div className={styles.search_panel} style={deskAni} onClick={(event)=>{
+                event.stopPropagation();
+            }}>
+                <div className={`${styles.search_panel_container}`}>
+                    {renderInput()}
+                </div>
+            </animated.div>
+        )
+    }
+    const renderMobile=()=>{
+        return (<></>)
+    }
+    const renderPanel=()=>{
+        if (!isShowSearch)return (<></>);
+        return (
+            <div onClick={()=>{
+                hidePanel();
+            }} className={`${styles.search_container}`} style={{top:"var(--header-active)",left:left}}>
+                {
+                    isMobile()?renderMobile():renderDesktop()
+                }
+            </div>
+        )
+    }
+    return (
+        <>
+            {
+                renderPanel()
+            }
+        </>
+    )
+}
+export default GameSearchPanel;

+ 87 - 0
src/layout/GameSearchPanel/styles.module.scss

@@ -0,0 +1,87 @@
+.search_container{
+  position: fixed;
+  right: 0;
+  bottom: 0;
+  background: rgba(0, 0, 0, .5);
+  z-index: 110;
+}
+.search_panel{
+  min-height: 440px;
+  width: 100%;
+  background-color: var(--widget);
+  position: relative;
+}
+.search_panel_container{
+  padding-top: 2.25rem;
+  padding-bottom: 1.5rem;
+  width: 100%;
+  margin: 0 auto;
+  padding-left: 25px;
+  padding-right: 25px;
+  max-width: 1430px;
+}
+.search_input_wrap{
+  width: 100%;
+  height: 2.75rem;
+  margin: 0;
+  margin-bottom: 1rem;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  background-color: var(--standard);
+  border-radius: var(--border-radius);
+  border: 1px solid rgba(255, 255, 255, .1);
+  &.focus{
+    border: 1px solid var(--primary-color);
+  }
+}
+.input_before{
+  width: 2.5rem;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  & .icon{
+    width: 1.4em;
+    height: 1.4em;
+    margin-left: .25rem;
+    font-size: .75rem;
+    fill: var(--text);
+  }
+}
+.search_input{
+  flex: 1;
+  height: 100%;
+  line-height: 2.875rem;
+  background-color: transparent;
+  border: none;
+  color: #D4D9E6;
+  padding: 0;
+  padding-right: .75rem;
+  font-size: .875rem;
+  vertical-align: middle;
+}
+.input_after{
+  overflow: hidden;
+  width: 4.5rem;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  color: var(--text);
+}
+.clear_btn{
+  width: 1rem;
+  height: 1rem;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  & .icon{
+    width: 1.4em;
+    height: 1.4em;
+    margin-left: .25rem;
+    font-size: .75rem;
+    fill: var(--text);
+  }
+}

+ 3 - 1
src/layout/Layout.jsx

@@ -13,6 +13,7 @@ import {useThemeProvider} from "@contexts/themeContext";
 import CRoute from "@layout/Routes";
 import {CBrowserRouter,CHashRouter} from "@layout/Routes";
 import Footer from "@layout/Footer";
+import GameSearchPanel from "@layout/GameSearchPanel";
 const PageNotFound = lazy(() => import('@pages/PageNotFound'));
 
 const Layout=()=>{
@@ -47,9 +48,10 @@ const Layout=()=>{
                           <CBrowserRouter routes={inRoutes}></CBrowserRouter>
                       </Suspense>
                   </div>
-                  <Footer />
                   {/*</div>*/}
+                  <Footer />
               </AppContainer>
+              <GameSearchPanel />
           </div>
           {
               hash!=''?(

+ 2 - 2
src/layout/Navbar/index.jsx

@@ -108,7 +108,7 @@ const DesktopNavToggle=()=>{
                             <svg className={"icon"}>
                                 <use xlinkHref={`${symbol}#${link.icon}`} />
                             </svg>
-                            {i18n.t("links."+link.title)}
+                            {i18n.t(link.title)}
                         </NavLink>
                     </StyledToggle>
                 ))
@@ -280,7 +280,7 @@ const PageHeader = () => {
                                 <svg className={"icon"}>
                                     <use xlinkHref={`${symbol_m}#${link.icon}`} />
                                 </svg>
-                                <span>{i18n.t("links."+link.title)}</span>
+                                <span>{i18n.t(link.title)}</span>
                             </button>
                         ))
                     }

+ 4 - 4
src/layout/Sidebar/index.jsx

@@ -188,13 +188,13 @@ const Sidebar = () => {
         let pages=link.subs||link.pages;
         let linkItem=(
                 <StyledAccordion defaultExpanded={true} open={open} square={true} key={link.lv} expanded={checkCkey(link.lv)} onChange={handleChange(`${link.lv}`,link.path)}>
-                    <MTooltip  disableHoverListener={open} title={i18n.t("links."+link.title)} arrow placement="right">
+                    <MTooltip  disableHoverListener={open} title={i18n.t(link.title)} arrow placement="right">
                         <StyledAccordionSummary open={open}>
                                 <Link open={open} className={`${checkPath(link) ? 'active' : ''} h4`}>
                                     <svg className={"icon"}>
                                         <use xlinkHref={`${symbol}#${link.icon}`} />
                                     </svg>
-                                    {open?i18n.t("links."+link.title):''}
+                                    {open?i18n.t(link.title):''}
                                 </Link>
                             {
                                 open?(
@@ -246,14 +246,14 @@ const Sidebar = () => {
                 <svg className={"icon"}>
                     <use xlinkHref={`${symbol}#${link.icon}`} />
                 </svg>
-                {open?i18n.t(`links.${link.title}`):''}
+                {open?i18n.t(`${link.title}`):''}
             </>
         )
         return ispop?(<a className={clname} href={link.path}>{linktxt}</a>):(<NavLink className={clname} to={link.path}>{linktxt}</NavLink>)
     }
     const renderSingleLink=(link)=>{
         return (
-            <MTooltip key={link.path} disableHoverListener={open} title={i18n.t(`links.${link.title}`)} arrow placement="right">
+            <MTooltip key={link.path} disableHoverListener={open} title={i18n.t(`${link.title}`)} arrow placement="right">
                 <SingleLink open={open}>
                     {
                         renderRLink(link)

+ 3 - 0
src/layouts.js

@@ -433,12 +433,15 @@ const layouts = {
     home:{
         xl:[
             {i: 'banner', x: 0, y: 0, w: 4, h: 1},
+            {i: 'bigColorCards', x: 0, y: 1, w: 4, h: 2},
         ],
         lg:[
             {i: 'banner', x: 0, y: 0, w: 3, h: 1},
+            {i: 'bigColorCards', x: 0, y: 1, w: 3, h: 2},
         ],
         md:[
             {i: 'banner', x: 0, y: 0, w: 2, h: 1},
+            {i: 'bigColorCards', x: 0, y: 1, w: 2, h: 2},
         ]
     }
 }

+ 18 - 0
src/locales/languages/en_US.json

@@ -36,5 +36,23 @@
     "privacy_policy": "Privacy Policy",
     "terms_of_service": "Terms of Service",
     "kyc_policy": "AML & KYC Policy"
+  },
+  "color_card": {
+    "go_to": "Go to "
+  },
+  "win_list": {
+    "big_wins": "Recent Big Wins",
+    "tab_all": "All",
+    "tab_slots": "Slots"
+  },
+  "small_list": {
+    "view_all": "View all"
+  },
+  "game_list": {
+    "show_more": "Load More"
+  },
+  "game_search": {
+    "input_ph": "Game name",
+    "cancel": "Cancel"
   }
 }

+ 13 - 1
src/pages/Home.jsx

@@ -1,8 +1,20 @@
 import AppGrid from "@layout/AppGrid";
 import Banner from "@widgets/Banner";
+import BigColorCards from "@widgets/BigColorCards";
+import AutoBigWin from "@widgets/AutoBigWin";
+import ModuleGameSmallList from "@widgets/ModuleGameSmallList";
+import ModuleGameList from "@widgets/ModuleGameList";
+import ModuleGameTabs from "@widgets/ModuleGameTabs";
+import ModuleGameSearch from "@widgets/ModuleGameSearch";
 
 const widgets = {
-    banner:<Banner />
+    banner:<Banner />,
+    // bigColorCards:<BigColorCards />,
+    ModuleSearch:<ModuleGameSearch />,
+    ModuleGameTabs:<ModuleGameTabs />,
+    ModuleGameSmallList:<ModuleGameSmallList />,
+    autoBigWin:<AutoBigWin />,
+    ModuleGameList:<ModuleGameList />
 }
 
 const Home = () => {

+ 12 - 5
src/styles/_global.scss

@@ -25,15 +25,16 @@ body {
     min-height: 100vh;
     display: flex;
     flex-direction: column;
-
     &_container {
         display: flex;
         flex-direction: column;
         align-items: stretch;
         flex: 1;
-        margin: 15px 15px 70px;
+        margin: 0px 0px 70px;
         &-content{
+            padding: 0px 15px;
             min-height: 92vh;
+            background-color: var(--widget-bg);
         }
         &-footer{
             margin-top: 3rem;
@@ -61,7 +62,7 @@ body {
         }
 
         .layout {
-            margin: -20px -25px;
+            //margin: 0px 0px;
         }
     }
 
@@ -101,10 +102,12 @@ input, input::placeholder {
     color: var(--text);
 }
 .sidebar-open{
-    margin-left: 265px;
+    //margin-left: 250px;
+    margin-left: var(--side-open);
 }
 .sidebar-close{
-    margin-left: 80px;
+    //margin-left: 65px;
+    margin-left: var(--side-close);
 }
 .sidebar-nav{
     //padding: 20px 10px 80px;
@@ -182,3 +185,7 @@ input, input::placeholder {
     padding: 0 25px;
     max-width: 1430px;
 }
+.grid-list {
+    --grid-gap: .75rem;
+    row-gap: 1.25rem;
+}

+ 1 - 1
src/styles/_keyframes.scss

@@ -23,4 +23,4 @@
 
 .rotate-center {
     animation: rotate-center 3s ease-in-out infinite;
-}
+}

+ 4 - 1
src/styles/_utils.scss

@@ -40,6 +40,9 @@
 .flex-column {
   flex-direction: column;
 }
+.flex-row {
+    flex-direction: row;
+}
 
 .flex-row-reverse {
   flex-direction: row-reverse;
@@ -221,4 +224,4 @@
 
 .card-padded {
     padding: var(--card-padding);
-}
+}

+ 25 - 0
src/styles/_vars.scss

@@ -52,6 +52,10 @@
     --primary-color: #3BC117;
     --border-radius: .25rem;
     --nav-item-h:46px;
+    --mask-blur:linear-gradient(to bottom,transparent,#2E3035);
+    --grid-num:7;
+    --side-open:250px;
+    --side-close:65px;
 }
 
 @media screen and (min-width: 414px) {
@@ -59,3 +63,24 @@
         --card-padding: 30px;
     }
 }
+@media screen and (max-width: 621px) {
+    :root {
+        --grid-num:3.2;
+    }
+}
+@media screen and (min-width: 622px) and (max-width: 962px) {
+    :root {
+        --grid-num:4;
+    }
+}
+@media screen and (min-width: 963px) and (max-width: 1263px) {
+    :root {
+        --grid-num:5;
+    }
+}
+@media screen and (min-width: 1264px) and (max-width: 1564px) {
+    :root {
+        --grid-num:6;
+    }
+}
+

+ 12 - 0
src/styles/theme.js

@@ -11,6 +11,10 @@ export default createGlobalStyle`
       light: 'var(--widget-light)',
       dark: 'var(--widget-dark)',
     })};
+    --widget-grid-bg: ${theme('theme', {
+        light: 'var(--widget-light)',
+        dark: '#2f3035',
+    })};
     --widget-shadow: ${theme('theme', {
       light: '0 1px 8px rgba(110, 110, 110, .1)',
       dark: '0 1px 8px rgba(18, 20, 20, .5)',
@@ -91,5 +95,13 @@ export default createGlobalStyle`
         light:'rgba(236,236,245,0.5)',
         dark:'rgba(69,70,80,0.5)'
     })};
+    --widget-bg: ${theme('theme', {
+        light: '#f6f7fb',
+        dark: '#25262b',
+    })};
+    --swiper-btn-bg: ${theme('theme',{
+        light:'#FFFFFF',
+        dark:'#2a2b31',
+    })}
   }
 `;

+ 14 - 0
src/widgets/AutoBigWin/index.jsx

@@ -0,0 +1,14 @@
+import React from "react";
+import Spring from "@components/Spring";
+import AutoWinList from "@components/AutoWinList";
+import {useThemeProvider} from "@contexts/themeContext";
+
+const AutoBigWin=()=>{
+    const {isMobile} = useThemeProvider();
+    return (
+        <div className={`${isMobile()?'':'page-max-width-wrap'}`}>
+            <AutoWinList data={{}}></AutoWinList>
+        </div>
+    )
+}
+export default AutoBigWin;

+ 0 - 0
src/widgets/AutoBigWin/styles.module.scss


+ 56 - 0
src/widgets/Banner/index.jsx

@@ -1,9 +1,34 @@
 import AutoBanner from "@components/AutoBanner";
 import {useThemeProvider} from "@contexts/themeContext";
 
+import { SpinePlayer } from '@esotericsoftware/spine-player';
+import React from "react";
+import ColorCard from "@components/ColorCards/ColorCard";
+import ColorCardsGrid from "@components/ColorCards/ColorCardsGrid";
+
 const Banner=()=>{
     const {isMobile} = useThemeProvider();
     const testdata=[
+        {
+            img:'http://localhost:3000/banner/1.png',
+            alt:'1',
+            link:'http://localhost:3000/'
+        },
+        {
+            img:'http://localhost:3000/banner/2.png',
+            alt:'2',
+            link:'http://localhost:3000/'
+        },
+        {
+            img:'http://localhost:3000/banner/3.png',
+            alt:'3',
+            link:'http://localhost:3000/'
+        },
+        {
+            img:'http://localhost:3000/banner/4.png',
+            alt:'4',
+            link:'http://localhost:3000/'
+        },
         {
             img:'http://localhost:3000/banner/1.png',
             alt:'1',
@@ -25,11 +50,42 @@ const Banner=()=>{
             link:'http://localhost:3000/'
         }
     ];
+    const Test=()=>{
+        var jsonUrl = 'test/skeleton.json';
+        var atlasUrl = 'test/skeleton.atlas';
+
+
+        setTimeout(()=>{
+            var player=new SpinePlayer("aztec", {
+                jsonUrl: jsonUrl,
+                atlasUrl: atlasUrl,
+                animation: 'A',
+                premultipliedAlpha: false,
+                // backgroundColor: 'rgba(241,118,118,0.71)',
+                showControls: false,
+                viewport: {
+                    debugRender: false,
+                    width:10,
+                    height:10,
+                },
+                alpha:true,
+                // showControls: true,
+            });
+        },1000);
+        // return <div id="container-aztec"></div>;
+        // return (<div id="container-aztec"></div>);
+        return (
+            <div id={"aztec"} style={{width:'50px',height:'50px'}}>
+
+            </div>
+        )
+    }
     return (
         <div className={`${isMobile()?'':'page-max-width-wrap'}`}>
             <AutoBanner data={testdata}>
 
             </AutoBanner>
+            {/*<Test></Test>*/}
         </div>
     )
 }

+ 14 - 0
src/widgets/BigColorCards/index.jsx

@@ -0,0 +1,14 @@
+import React from "react";
+import ColorCardsGrid from "@components/ColorCards/ColorCardsGrid";
+import {useThemeProvider} from "@contexts/themeContext";
+
+
+const BigColorCards=()=>{
+    const {isMobile} = useThemeProvider();
+    return (
+        <div className={`${isMobile()?'':'page-max-width-wrap'}`}>
+            <ColorCardsGrid data={[]}></ColorCardsGrid>
+        </div>
+    )
+}
+export default BigColorCards;

+ 15 - 0
src/widgets/ModuleGameList/index.jsx

@@ -0,0 +1,15 @@
+import React from "react";
+import GameList from "@components/GameList";
+import {useThemeProvider} from "@contexts/themeContext";
+
+const ModuleGameList=()=>{
+    const {isMobile} = useThemeProvider();
+    return (
+        <div className={`${isMobile()?'':'page-max-width-wrap'}`}>
+            <GameList>
+
+            </GameList>
+        </div>
+    )
+}
+export default ModuleGameList;

+ 0 - 0
src/widgets/ModuleGameList/styles.module.scss


+ 15 - 0
src/widgets/ModuleGameSearch/index.jsx

@@ -0,0 +1,15 @@
+import {useThemeProvider} from "@contexts/themeContext";
+import React from "react";
+import GameSearch from "@components/GameSearch";
+
+const ModuleGameSearch=()=>{
+    const {isMobile} = useThemeProvider();
+    return (
+        <div className={`${isMobile()?'':'page-max-width-wrap'}`}>
+            <GameSearch>
+
+            </GameSearch>
+        </div>
+    )
+}
+export default ModuleGameSearch;

+ 0 - 0
src/widgets/ModuleGameSearch/styles.module.scss


+ 15 - 0
src/widgets/ModuleGameSmallList/index.jsx

@@ -0,0 +1,15 @@
+import React from "react";
+import GameSmallList from "@components/GameSmallList";
+import {useThemeProvider} from "@contexts/themeContext";
+
+const ModuleGameSmallList=()=>{
+    const {isMobile} = useThemeProvider();
+    return (
+        <div className={`${isMobile()?'':'page-max-width-wrap'}`}>
+            <GameSmallList>
+
+            </GameSmallList>
+        </div>
+    )
+}
+export default ModuleGameSmallList;

+ 0 - 0
src/widgets/ModuleGameSmallList/styles.module.scss


+ 15 - 0
src/widgets/ModuleGameTabs/index.jsx

@@ -0,0 +1,15 @@
+import {useThemeProvider} from "@contexts/themeContext";
+import React from "react";
+import GameTabs from "@components/GameTabs";
+
+const ModuleGameTabs=()=>{
+    const {isMobile} = useThemeProvider();
+    return (
+        <div className={`${isMobile()?'':'page-max-width-wrap'}`}>
+            <GameTabs>
+
+            </GameTabs>
+        </div>
+    )
+}
+export default ModuleGameTabs;

+ 0 - 0
src/widgets/ModuleGameTabs/styles.module.scss


Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно