shaogen1995 1 月之前
父節點
當前提交
76f1879b3a

+ 4 - 0
package.json

@@ -16,8 +16,11 @@
     "antd": "^5.8.3",
     "antd-mobile": "^5.30.0",
     "axios": "^1.7.9",
+    "i18next": "^25.6.3",
+    "i18next-browser-languagedetector": "^8.2.0",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
+    "react-i18next": "^16.3.5",
     "react-redux": "^8.0.4",
     "react-router-dom": "5.3",
     "react-scripts": "5.0.1",
@@ -26,6 +29,7 @@
     "redux-devtools-extension": "^2.13.9",
     "redux-thunk": "^2.4.1",
     "sass": "^1.55.0",
+    "svg-pan-zoom": "^3.6.2",
     "typescript": "^4.8.4",
     "vconsole": "^3.15.1",
     "web-vitals": "^2.1.4"

文件差異過大導致無法顯示
+ 499 - 0
src/assets/img/Graph.svg


+ 31 - 3
src/assets/styles/base.css

@@ -219,6 +219,34 @@ textarea {
   }
   #root .ant-tooltip {
     transform: rotate(0) !important;
+    inset: 24.331px auto auto 399.25px !important;
+    width: 250px !important;
+    height: 150px !important;
+    max-width: 250px !important;
+  }
+  #root .ant-tooltip .tooltip_MT .top {
+    width: 100%;
+    height: 20px !important;
+  }
+  #root .ant-tooltip .tooltip_MT .top .title {
+    width: 80px !important;
+    font-size: 13px !important;
+  }
+  #root .ant-tooltip .tooltip_MT .top .close {
+    width: 40px;
+    height: 100%;
+    cursor: pointer;
+    display: flex;
+    justify-content: end;
+    align-items: center;
+  }
+  #root .ant-tooltip .tooltip_MT .top .close > img {
+    height: 90%;
+    object-fit: contain;
+  }
+  #root .ant-tooltip .tooltip_MT .content {
+    font-size: 12px !important;
+    line-height: 13px !important;
   }
   body #A7Back {
     transform: rotate(90deg);
@@ -285,9 +313,9 @@ textarea {
 }
 /* 隐藏静音按钮 */
 #root .ant-tooltip {
-  width: 190px !important;
-  height: 92px !important;
-  max-width: 200px !important;
+  width: 190px;
+  height: 92px;
+  max-width: 200px;
 }
 #root .ant-tooltip .ant-tooltip-content {
   width: 100% !important;

+ 43 - 9
src/assets/styles/base.less

@@ -67,7 +67,7 @@ body #A7Back {
   left: 4%;
   cursor: pointer;
 
-  &>img {
+  & > img {
     height: 100%;
     object-fit: contain;
   }
@@ -78,7 +78,7 @@ body #A7Back {
   margin: auto;
   position: relative;
 
-  &>div {
+  & > div {
     width: 100%;
     height: 100%;
   }
@@ -207,7 +207,7 @@ textarea {
   bottom: 15px;
   right: 20px;
 
-  &>img {
+  & > img {
     position: absolute;
     top: 0;
     left: 0;
@@ -283,6 +283,40 @@ textarea {
 
   #root .ant-tooltip {
     transform: rotate(0) !important;
+    inset: 24.331px auto auto 399.25px !important;
+    width: 250px !important;
+    height: 150px !important;
+    max-width: 250px !important;
+  }
+
+  #root .ant-tooltip .tooltip_MT {
+    .top {
+      width: 100%;
+      height: 20px !important;
+
+      .title {
+        width: 80px !important;
+        font-size: 13px !important;
+      }
+
+      .close {
+        width: 40px;
+        height: 100%;
+        cursor: pointer;
+        display: flex;
+        justify-content: end;
+        align-items: center;
+
+        & > img {
+          height: 90%;
+          object-fit: contain;
+        }
+      }
+    }
+    .content {
+      font-size: 12px !important;
+      line-height: 13px !important;
+    }
   }
 
   body #A7Back {
@@ -296,7 +330,7 @@ textarea {
     z-index: 3;
     cursor: pointer;
 
-    &>img {
+    & > img {
       height: 100%;
       object-fit: contain;
     }
@@ -381,9 +415,9 @@ textarea {
 // }
 
 #root .ant-tooltip {
-  width: 190px !important;
-  height: 92px !important;
-  max-width: 200px !important;
+  width: 190px;
+  height: 92px;
+  max-width: 200px;
 
   .ant-tooltip-content {
     width: 100% !important;
@@ -432,7 +466,7 @@ textarea {
       justify-content: end;
       align-items: center;
 
-      &>img {
+      & > img {
         height: 90%;
         object-fit: contain;
       }
@@ -469,7 +503,7 @@ textarea {
 }
 
 .myFont {
-  font-family:'SimSun' !important;
+  font-family: 'SimSun' !important;
   font-weight: 700;
 }
 

+ 16 - 17
src/pages/A2yblm/components/ModalTxt/index.module.scss

@@ -40,7 +40,7 @@
           top: 35px;
           left: -109px;
 
-          &>img {
+          & > img {
             width: 100px;
             object-fit: contain;
           }
@@ -66,7 +66,7 @@
           cursor: pointer;
           position: relative;
 
-          &>img {
+          & > img {
             height: 45px;
             object-fit: contain;
           }
@@ -93,7 +93,6 @@
             color: rgba(255, 255, 255, 1);
             width: 200%;
             font-weight: 700;
-
           }
         }
 
@@ -165,11 +164,11 @@
               color: rgba(0, 0, 0, 0.25);
             }
 
-            &:has(> a:hover)>a:not(:hover) {
+            &:has(> a:hover) > a:not(:hover) {
               color: rgba(0, 0, 0, 0.25);
             }
 
-            &>a {
+            & > a {
               color: rgba(94, 52, 34, 1);
               position: relative;
 
@@ -221,8 +220,8 @@
           letter-spacing: 2px;
           color: #504e40;
 
-
-          &>p {}
+          & > p {
+          }
         }
       }
     }
@@ -238,7 +237,7 @@
           height: 60px;
           width: 120px;
 
-          &>img {
+          & > img {
             width: 100%;
           }
 
@@ -259,9 +258,6 @@
             font-size: 14px;
           }
         }
-
-
-
       }
 
       .content {
@@ -276,15 +272,11 @@
         }
       }
 
-
       .intro {
         font-size: 16px;
         line-height: 18px;
-
       }
 
-
-
       .detailTxt {
         .title {
           font-size: 18px !important;
@@ -299,9 +291,16 @@
             bottom: -2px !important;
             height: 2px !important;
           }
+
+          &:has(> a:hover) {
+            color: rgba(94, 52, 34, 1) !important;
+          }
+
+          &:has(> a:hover) > a:not(:hover) {
+            color: rgba(94, 52, 34, 1) !important;
+          }
         }
       }
     }
-
   }
-}
+}

+ 22 - 3
src/pages/A2yblm/components/ModalTxt/index.tsx

@@ -20,8 +20,10 @@ function ModalTxt({
 
   const ori_touchStartX = useRef(0)
   const trans_touchStartX = useRef(0)
+  const content_touchStartX = useRef(0)
   const originRef = useRef<any>(null)
   const translateRef = useRef<any>(null)
+  const contentRef = useRef<any>(null)
 
   useEffect(() => {
     if (selectedTab !== 0) {
@@ -53,6 +55,14 @@ function ModalTxt({
     }
     trans_touchStartX.current = e.touches[0].clientX
   }
+  const handleTooltipTouchMove = (e: any) => {
+    e.preventDefault()
+    const deltaX = e.touches[0].clientX - content_touchStartX.current
+    if (contentRef.current) {
+      contentRef.current.scrollTop += deltaX
+    }
+    content_touchStartX.current = e.touches[0].clientX
+  }
   // 处理触摸开始事件
   const handleTouchStart = (e: any, start: any) => {
     start.current = e.touches[0].clientX
@@ -84,13 +94,22 @@ function ModalTxt({
               <div className='title'>{word}</div>
               <div
                 className='close'
-                onClick={() => setShowTooltip(-1)}
-                onTouchEnd={() => setShowTooltip(-1)}
+                onClick={(e) => {
+                  e.preventDefault()
+                  e.stopPropagation()
+                  setShowTooltip(-1)
+                }}
+
+                onTouchEnd={(e) => {
+                  e.preventDefault()
+                  e.stopPropagation()
+                  setShowTooltip(-1)
+                }}
               >
                 <img src={require('@/assets/img/close.png')} alt='' draggable='false' />
               </div>
             </div>
-            <div className='content'>{define}</div>
+            <div className='content' ref={contentRef} onTouchStart={(e) => handleTouchStart(e, content_touchStartX)} onTouchMove={handleTooltipTouchMove}>{define}</div>
           </div>
         }
         getPopupContainer={() => document.body.querySelector('#root') as HTMLElement}

+ 29 - 56
src/pages/A6ybwx/A6_2_zxzgh/index.tsx

@@ -1,8 +1,6 @@
 import React, { useState, useRef } from 'react'
 import styles from './index.module.scss'
-import { isPc, myData } from '@/utils/http'
-import classNames from 'classnames'
-
+import { myData } from '@/utils/http'
 function Sinicize() {
   const [currentTab, setCurrentTab] = useState(0)
   const [selectedTime, setSelectedTime] = useState(0)
@@ -26,10 +24,7 @@ function Sinicize() {
     direct: string
   }) => {
     return (
-      <div
-        className={classNames(styles.label, isPc ? '' : styles.labelMo)}
-        style={{ inset: inset }}
-      >
+      <div className={styles.label} style={{ inset: inset }}>
         {direct === 'left' ? <div className='arrowL' style={{ width: width + 'px' }} /> : null}
         <div className='btn'>{name}</div>
         {direct === 'right' ? <div className='arrowR' style={{ width: width + 'px' }} /> : null}
@@ -64,7 +59,7 @@ function Sinicize() {
 
   // 处理触摸移动事件
   const handleOriTouchMove = (e: any) => {
-    // e.preventDefault()
+    e.preventDefault()
     const deltaX = e.touches[0].clientY - ori_touchStartX.current
     if (originRef.current) {
       originRef.current.scrollLeft -= deltaX
@@ -81,16 +76,13 @@ function Sinicize() {
     <>
       <div
         ref={originRef}
-        className={classNames(styles.Sinicize, isPc ? '' : styles.SinicizeMo)}
+        className={styles.Sinicize}
         onWheel={handleWheel}
         onTouchMove={handleOriTouchMove}
         onTouchStart={handleTouchStart}
       >
         <div className='SinicizeScroll'>
-          <div
-            ref={sinicize1Ref}
-            className={classNames(styles.Sinicize1, isPc ? '' : styles.Sinicize1Mo)}
-          >
+          <div ref={sinicize1Ref} className={styles.Sinicize1}>
             <div className='back' onClick={() => window.location.replace('#/ybwx?tab=1')}>
               <img src={require('@/assets/img/btn_back.png')} alt='' />
             </div>
@@ -101,7 +93,9 @@ function Sinicize() {
               <div className='line1'>
                 <img src={require('@/assets/img/A6_sinicize_line2.png')} draggable='false' alt='' />
               </div>
-              <div className='t2'>{myData.sinicizeDataStatic.desc}</div>
+              <div className='t2'>
+                {myData.sinicizeDataStatic.desc}
+              </div>
             </div>
             <div className={`zhufo ${currentTab === 0 ? 'active' : ''}`}>
               <img src={require('@/assets/img/A6_sinicize_zhufo.png')} draggable='false' alt='' />{' '}
@@ -144,21 +138,23 @@ function Sinicize() {
             </div>
           </div>
 
-          <div className={classNames(styles.Sinicize2, isPc ? '' : styles.Sinicize2Mo)}>
+          <div className={styles.Sinicize2}>
             <div className='scroll'>
-              {myData.sinicizeData.map((item, index) => (
+              {myData.sinicizeData.map((sItem, index) => (
                 <div className='yearItem' key={index}>
                   <div
                     className='year'
-                    style={{ backgroundImage: `url(${item.bgPath})` }}
+                    style={{ backgroundImage: `url(${sItem.bgPath})` }}
                     onClick={() => handleYearItemClick(index)}
                   >
-                    <div className='txt'>{item.time}</div>
+                    <div className='txt'>{sItem.time}</div>
                   </div>
                   <div className={`${selectedTime === index ? 'detailShow' : 'detailHide'}`}>
                     <div className='left'>
-                      <div className='desc'>{item.desc}</div>
-                      {item.items.map((item, index) => (
+                      <div className='desc'>
+                        {sItem.desc}
+                      </div>
+                      {sItem.items.map((item, index) => (
                         <div className='txtItem' key={index}>
                           <div className='title'>{item.title}</div>
                           <div className='txt'>{item.txt}</div>
@@ -166,39 +162,19 @@ function Sinicize() {
                       ))}
                     </div>
                     <div className='right'>
-                      <div className='title'>
-                        {myData.sinicizeData[index].imgItems[currentImgIndex].title}
-                      </div>
-                      <div className='pic'>
-                        <img
-                          src={myData.sinicizeData[index].imgItems[currentImgIndex].src}
-                          alt=''
-                        />
-                      </div>
-                      <div className='txt'>
-                        {myData.sinicizeData[index].imgItems[currentImgIndex].txt}
-                      </div>
-                      <div className='iconContainner'>
-                        {item.imgItems.map((i, index) => {
+                      <div className='title'>{sItem.imgItems[currentImgIndex]?.title || ''}</div>
+                      <div className='pic'><img src={sItem.imgItems[currentImgIndex]?.src || ''} alt="" /></div>
+                      <div className="txt">{sItem.imgItems[currentImgIndex]?.txt || ''}</div>
+                      <div className="iconContainner">
+                        {sItem.imgItems.map((i, index) => {
                           return (
-                            <div
-                              className='icon'
-                              key={index}
-                              onClick={() => {
-                                setCurrentImgIndex(index)
-                              }}
-                            >
-                              <img
-                                src={require(`@/assets/img/A6_sincize_detailIcon${
-                                  currentImgIndex === index ? '_ac' : ''
-                                }.png`)}
-                                draggable='false'
-                                alt=''
-                              />
+                            <div className='icon' key={index} onClick={() => { setCurrentImgIndex(index) }}>
+                              <img src={require(`@/assets/img/A6_sincize_detailIcon${currentImgIndex === index ? '_ac' : ''}.png`)} draggable='false' alt='' />
                             </div>
                           )
                         })}
                       </div>
+
                     </div>
                   </div>
                 </div>
@@ -208,14 +184,11 @@ function Sinicize() {
         </div>
       </div>
       {isShowTip && (
-        <div
-          className={classNames(styles.tip, isPc ? '' : styles.tipMo)}
-          onClick={() => {
-            setTimeout(() => {
-              setIsShowTip(false)
-            }, 1500)
-          }}
-        >
+        <div className={styles.tip} onClick={() => {
+          setTimeout(() => {
+            setIsShowTip(false)
+          }, 1500);
+        }}>
           <div className='gesture'>
             <img src={require('@/assets/img/A6_sinicize_gesture.png')} draggable='false' alt='' />{' '}
             <div className='txt'>向右滑动查看造像中国化的演变</div>{' '}

+ 50 - 0
src/pages/A6ybwx/Genealogy/components/GraphSVG/index.module.scss

@@ -0,0 +1,50 @@
+.SVGContainner {
+  width: 100%;
+  height: 100%;
+  position: relative;
+  :global {
+    .graphSvg {
+      width: 100%;
+      height: 100%;
+      position: absolute;
+      top: 0;
+      left: 0;
+      background: #ccc;
+    }
+  }
+}
+
+.miniMap {
+  position: fixed;
+  left: 20px;
+  bottom: 20px;
+  width: 300px;
+  height: 80px;
+  background: rgba(0, 0, 0, 0.5);
+  border: 1px solid rgba(255, 233, 182, 0.5);
+  overflow: hidden;
+
+  .viewport {
+    position: absolute;
+    border: 1px solid #ffe9b6;
+    cursor: move;
+    background: rgba(255, 233, 182, 0.1);
+  }
+
+  .miniContent {
+    transform-origin: 0 0;
+    pointer-events: none;
+    width: 3000px;
+    height: 800px;
+    position: relative;
+    touch-action: none;
+    :global {
+      .nodeActiveG {
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+      }
+    }
+  }
+}

+ 104 - 0
src/pages/A6ybwx/Genealogy/components/GraphSVG/index.tsx

@@ -0,0 +1,104 @@
+import React, { useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { ReactComponent as GraphSvg } from '@/assets/img/Graph.svg'
+import svgPanZoom from 'svg-pan-zoom'
+import { useDrag } from '@use-gesture/react'
+
+const MAIN_CONTENT_WIDTH = 1920
+const MAIN_CONTENT_HEIGHT = 945
+const MINIMAP_SCALE = 0.045
+function SvgGraph() {
+  const [startX, setStartX] = useState(0)
+  const [startY, setStartY] = useState(0)
+  const [offsetX, setOffsetX] = useState(0)
+  const [offsetY, setOffsetY] = useState(0)
+  const svgRef = useRef<any>(null)
+  const panZoomInstance = useRef<ReturnType<typeof svgPanZoom> | null>(null)
+
+  // 小地图相关逻辑
+
+  const miniMapScale = 0.1
+
+  // 小地图拖拽绑定
+  const bind = useDrag(({ offset: [x, y] }) => {
+    if (!panZoomInstance.current) return;
+    console.log(123123, offsetX, offsetY)
+    // 计算视口位置
+    const viewportX = x / miniMapScale;  // 减去初始 X 偏移
+    const viewportY = y / miniMapScale;
+
+
+    // 更新主视图位置
+    panZoomInstance.current.pan({  // 使用正确的实例方法
+      x: -viewportX,
+      y: -viewportY
+    });
+
+
+  });
+
+
+  // 设置拖拽
+  useEffect(() => {
+    if (svgRef.current) {
+      // 初始化 svg-pan-zoom
+      svgRef.current.setAttribute('viewBox', '0 0 3000 800')
+      panZoomInstance.current = svgPanZoom(svgRef.current, {
+        zoomEnabled: false,
+        dblClickZoomEnabled: false,
+        panEnabled: true,
+        controlIconsEnabled: false,
+        fit: false,
+        contain: false,
+        center: false,
+        beforePan: (newPos) => {
+          console.log(456, newPos.x, newPos.y)
+          setOffsetX(newPos.x);
+          setOffsetY(newPos.y);
+        },
+      })
+      panZoomInstance.current.pan({ x: 300, y: 100 })
+      panZoomInstance.current.zoom(2.85)
+      // setOffsetX(0)
+      // setOffsetY(-200)
+    }
+
+    return () => {
+      // 组件卸载时销毁实例
+      if (panZoomInstance.current) {
+        panZoomInstance.current.destroy()
+      }
+    }
+  }, [])
+
+  return (
+    <>
+      <div className={styles.SVGContainner}>
+        <GraphSvg ref={svgRef} className='graphSvg' />
+      </div>
+      <div className={styles.miniMap}>
+        <div
+          className={styles.viewport}
+          {...bind({ offsetX, offsetY })}
+          style={{
+            transform: `translate(${-offsetX * miniMapScale}px, ${-offsetY * miniMapScale}px)`,
+            width: `${MAIN_CONTENT_WIDTH * MINIMAP_SCALE}px`,
+            height: `${MAIN_CONTENT_HEIGHT * MINIMAP_SCALE}px`
+          }}
+        />
+        <div
+          className={styles.miniContent}
+          style={{
+            transform: `scale(${miniMapScale})`,
+          }}
+        >
+          <GraphSvg className='graphSvg' />
+        </div>
+      </div>
+    </>
+  )
+}
+
+const MemoSvgGraph = React.memo(SvgGraph)
+
+export default MemoSvgGraph

+ 3 - 2
src/pages/A6ybwx/Genealogy/index.tsx

@@ -2,7 +2,7 @@ import React, { useState } from 'react'
 import styles from './index.module.scss'
 import Graph from './components/Graph'
 import MemuSider from '@/components/MenuSider'
-
+import SvgGraph from './components/GraphSVG'
 function Genealogy({ setGotoTab }: { setGotoTab: (tab: number) => void }) {
   const [isShowIntro, setIsShowIntro] = useState(true)
   const [currentNodeIndex, setCurrentNodeIndex] = useState(-1)
@@ -17,7 +17,8 @@ function Genealogy({ setGotoTab }: { setGotoTab: (tab: number) => void }) {
       </div>
       <MemuSider activeTab={1} />
 
-      <Graph setCurrentNodeIndex={setCurrentNodeIndex} />
+      {/* <Graph setCurrentNodeIndex={setCurrentNodeIndex} /> */}
+      <SvgGraph />
 
       {isShowGesture && <div className={styles.gesture} onClick={() => setIsShowGesture(false)}>
         <img src={require('@/assets/img/A6_gen_gesture.png')} draggable='false' alt='' />

+ 46 - 1
yarn.lock

@@ -1093,7 +1093,7 @@
   dependencies:
     regenerator-runtime "^0.14.0"
 
-"@babel/runtime@^7.12.0":
+"@babel/runtime@^7.12.0", "@babel/runtime@^7.23.2", "@babel/runtime@^7.27.6", "@babel/runtime@^7.28.4":
   version "7.28.4"
   resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326"
   integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
@@ -5740,6 +5740,13 @@ html-minifier-terser@^6.0.2:
     relateurl "^0.2.7"
     terser "^5.10.0"
 
+html-parse-stringify@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.npmmirror.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
+  integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
+  dependencies:
+    void-elements "3.1.0"
+
 html-void-elements@^2.0.0:
   version "2.0.1"
   resolved "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f"
@@ -5839,6 +5846,13 @@ human-signals@^2.1.0:
   resolved "https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz"
   integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
 
+i18next-browser-languagedetector@^8.2.0:
+  version "8.2.0"
+  resolved "https://registry.npmmirror.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz#c3ca311e249d2f7d8bb9b3b13ac9af380a3b15b0"
+  integrity sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==
+  dependencies:
+    "@babel/runtime" "^7.23.2"
+
 i18next@^20.4.0:
   version "20.6.1"
   resolved "https://registry.npmmirror.com/i18next/-/i18next-20.6.1.tgz#535e5f6e5baeb685c7d25df70db63bf3cc0aa345"
@@ -5846,6 +5860,13 @@ i18next@^20.4.0:
   dependencies:
     "@babel/runtime" "^7.12.0"
 
+i18next@^25.6.3:
+  version "25.6.3"
+  resolved "https://registry.npmmirror.com/i18next/-/i18next-25.6.3.tgz#984dc1cf305fe10392e4db9ba5c56406eb4f27ad"
+  integrity sha512-AEQvoPDljhp67a1+NsnG/Wb1Nh6YoSvtrmeEd24sfGn3uujCtXCF3cXpr7ulhMywKNFF7p3TX1u2j7y+caLOJg==
+  dependencies:
+    "@babel/runtime" "^7.28.4"
+
 iconv-lite@0.4.24:
   version "0.4.24"
   resolved "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.4.24.tgz"
@@ -9010,6 +9031,15 @@ react-fast-compare@^3.2.2:
   resolved "https://registry.npmmirror.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49"
   integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==
 
+react-i18next@^16.3.5:
+  version "16.3.5"
+  resolved "https://registry.npmmirror.com/react-i18next/-/react-i18next-16.3.5.tgz#3a129dd05236cc979343b2870872d48cd48878f6"
+  integrity sha512-F7Kglc+T0aE6W2rO5eCAFBEuWRpNb5IFmXOYEgztjZEuiuSLTe/xBIEG6Q3S0fbl8GXMNo+Q7gF8bpokFNWJww==
+  dependencies:
+    "@babel/runtime" "^7.27.6"
+    html-parse-stringify "^3.0.1"
+    use-sync-external-store "^1.6.0"
+
 react-is@^16.12.0, react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0:
   version "16.13.1"
   resolved "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz"
@@ -10093,6 +10123,11 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
+svg-pan-zoom@^3.6.2:
+  version "3.6.2"
+  resolved "https://registry.npmmirror.com/svg-pan-zoom/-/svg-pan-zoom-3.6.2.tgz#be136506211b242627a234a9656f8128fcbbabed"
+  integrity sha512-JwnvRWfVKw/Xzfe6jriFyfey/lWJLq4bUh2jwoR5ChWQuQoOH8FEh1l/bEp46iHHKHEJWIyFJETbazraxNWECg==
+
 svg-parser@^2.0.2:
   version "2.0.4"
   resolved "https://registry.npmmirror.com/svg-parser/-/svg-parser-2.0.4.tgz"
@@ -10544,6 +10579,11 @@ use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0:
   resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
   integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
 
+use-sync-external-store@^1.6.0:
+  version "1.6.0"
+  resolved "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d"
+  integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==
+
 util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz"
@@ -10603,6 +10643,11 @@ vconsole@^3.15.1:
     core-js "^3.11.0"
     mutation-observer "^1.0.3"
 
+void-elements@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.npmmirror.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
+  integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
+
 w3c-hr-time@^1.0.2:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz"