diff --git a/.cursor/README.md b/.cursor/README.md new file mode 100644 index 0000000..54683de --- /dev/null +++ b/.cursor/README.md @@ -0,0 +1,15 @@ +# Agent Rules + +The files in this directory are shared project instructions, not Cursor-only notes. + +Codex and other coding agents must read and follow the matching rules through the repository-level `AGENTS.md` entry point. + +## Always Applied + +- `skills/react-best-practices/SKILL.md` +- `rules/changelog-writing.mdc` + +## Conditional Skills + +- `skills/frontend-design/SKILL.md`: frontend UI creation, styling, visual refinement, component/page/app design work. +- `skills/figma-ui-implementation/SKILL.md`: Figma-linked implementation or pixel-level design restoration. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0787610 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,26 @@ +# Codex Project Instructions + +This repository keeps project-specific agent rules in `.cursor/`. These rules are mandatory for Codex work in this repo. + +## Mandatory Rule Loading + +Before making code, UI, release-note, or documentation changes, inspect the relevant `.cursor` instructions and follow them as binding project policy. + +Always apply: +- `.cursor/skills/react-best-practices/SKILL.md` +- `.cursor/rules/changelog-writing.mdc` + +Apply for frontend UI work: +- `.cursor/skills/frontend-design/SKILL.md` + +Apply for Figma design implementation: +- `.cursor/skills/figma-ui-implementation/SKILL.md` + +## Enforcement + +- Treat the matching `.cursor` instructions as higher-priority project conventions. +- If a task matches multiple files, read all matching files before editing. +- Do not silently ignore a matching rule. If a rule conflicts with user instructions or existing code constraints, explain the conflict and choose the safest path. +- For Figma-linked UI implementation, read the Figma design context first, then implement with Flex/Grid where possible and verify visual fidelity before finishing. +- For React changes, follow the React, TypeScript, state, effect, styling, i18n, and maintainability rules in `react-best-practices`. +- For changelog or version-update requests, inspect real repository changes first and write only from observed changes. diff --git a/src/assets/images/layout/nav/characters.png b/src/assets/images/layout/nav/characters.png new file mode 100644 index 0000000..4d9f050 Binary files /dev/null and b/src/assets/images/layout/nav/characters.png differ diff --git a/src/assets/images/layout/nav/characters_active.png b/src/assets/images/layout/nav/characters_active.png new file mode 100644 index 0000000..3fb9c51 Binary files /dev/null and b/src/assets/images/layout/nav/characters_active.png differ diff --git a/src/assets/images/layout/nav/collapse.png b/src/assets/images/layout/nav/collapse.png new file mode 100644 index 0000000..9c62c7a Binary files /dev/null and b/src/assets/images/layout/nav/collapse.png differ diff --git a/src/assets/images/layout/nav/create.png b/src/assets/images/layout/nav/create.png new file mode 100644 index 0000000..95e91cf Binary files /dev/null and b/src/assets/images/layout/nav/create.png differ diff --git a/src/assets/images/layout/nav/create_active.png b/src/assets/images/layout/nav/create_active.png new file mode 100644 index 0000000..794c324 Binary files /dev/null and b/src/assets/images/layout/nav/create_active.png differ diff --git a/src/assets/images/layout/nav/explore.png b/src/assets/images/layout/nav/explore.png new file mode 100644 index 0000000..efc4364 Binary files /dev/null and b/src/assets/images/layout/nav/explore.png differ diff --git a/src/assets/images/layout/nav/explore_active.png b/src/assets/images/layout/nav/explore_active.png new file mode 100644 index 0000000..5a7a3a5 Binary files /dev/null and b/src/assets/images/layout/nav/explore_active.png differ diff --git a/src/assets/images/layout/nav/more.png b/src/assets/images/layout/nav/more.png new file mode 100644 index 0000000..9e0a90c Binary files /dev/null and b/src/assets/images/layout/nav/more.png differ diff --git a/src/assets/images/layout/nav/popi-logo.png b/src/assets/images/layout/nav/popi-logo.png new file mode 100644 index 0000000..4336b0e Binary files /dev/null and b/src/assets/images/layout/nav/popi-logo.png differ diff --git a/src/assets/images/layout/nav/popi-tv.png b/src/assets/images/layout/nav/popi-tv.png new file mode 100644 index 0000000..13548d2 Binary files /dev/null and b/src/assets/images/layout/nav/popi-tv.png differ diff --git a/src/assets/images/layout/nav/popi-tv_active.png b/src/assets/images/layout/nav/popi-tv_active.png new file mode 100644 index 0000000..cb8bda2 Binary files /dev/null and b/src/assets/images/layout/nav/popi-tv_active.png differ diff --git a/src/assets/images/layout/nav/profile.png b/src/assets/images/layout/nav/profile.png new file mode 100644 index 0000000..178e87e Binary files /dev/null and b/src/assets/images/layout/nav/profile.png differ diff --git a/src/assets/images/layout/nav/profile_active.png b/src/assets/images/layout/nav/profile_active.png new file mode 100644 index 0000000..c26cca8 Binary files /dev/null and b/src/assets/images/layout/nav/profile_active.png differ diff --git a/src/assets/images/layout/nav/subscribe.png b/src/assets/images/layout/nav/subscribe.png new file mode 100644 index 0000000..647c421 Binary files /dev/null and b/src/assets/images/layout/nav/subscribe.png differ diff --git a/src/assets/images/layout/nav/subscribe_active.png b/src/assets/images/layout/nav/subscribe_active.png new file mode 100644 index 0000000..41d7932 Binary files /dev/null and b/src/assets/images/layout/nav/subscribe_active.png differ diff --git a/src/assets/images/layout/nav/top-help.png b/src/assets/images/layout/nav/top-help.png new file mode 100644 index 0000000..d6ece4e Binary files /dev/null and b/src/assets/images/layout/nav/top-help.png differ diff --git a/src/assets/images/layout/nav/top-language.png b/src/assets/images/layout/nav/top-language.png new file mode 100644 index 0000000..2fd7f85 Binary files /dev/null and b/src/assets/images/layout/nav/top-language.png differ diff --git a/src/assets/images/layout/nav/top-skill.png b/src/assets/images/layout/nav/top-skill.png new file mode 100644 index 0000000..2e39a74 Binary files /dev/null and b/src/assets/images/layout/nav/top-skill.png differ diff --git a/src/assets/images/layout/nav/top-weixin.png b/src/assets/images/layout/nav/top-weixin.png new file mode 100644 index 0000000..e51c865 Binary files /dev/null and b/src/assets/images/layout/nav/top-weixin.png differ diff --git a/src/assets/images/layout/nav/voice.png b/src/assets/images/layout/nav/voice.png new file mode 100644 index 0000000..859242e Binary files /dev/null and b/src/assets/images/layout/nav/voice.png differ diff --git a/src/assets/images/layout/nav/voice_active.png b/src/assets/images/layout/nav/voice_active.png new file mode 100644 index 0000000..6e3b817 Binary files /dev/null and b/src/assets/images/layout/nav/voice_active.png differ diff --git a/src/assets/images/menu/canvas.svg b/src/assets/images/menu/canvas.svg new file mode 100644 index 0000000..3e73e65 --- /dev/null +++ b/src/assets/images/menu/canvas.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/menu/characters.svg b/src/assets/images/menu/characters.svg new file mode 100644 index 0000000..468f69c --- /dev/null +++ b/src/assets/images/menu/characters.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/images/menu/create.svg b/src/assets/images/menu/create.svg new file mode 100644 index 0000000..199c07a --- /dev/null +++ b/src/assets/images/menu/create.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/images/menu/explore-group.svg b/src/assets/images/menu/explore-group.svg new file mode 100644 index 0000000..fc17e66 --- /dev/null +++ b/src/assets/images/menu/explore-group.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/menu/explore-vector.svg b/src/assets/images/menu/explore-vector.svg new file mode 100644 index 0000000..3381de9 --- /dev/null +++ b/src/assets/images/menu/explore-vector.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/menu/more.svg b/src/assets/images/menu/more.svg new file mode 100644 index 0000000..34f3b35 --- /dev/null +++ b/src/assets/images/menu/more.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/layouts/Layout/Layout.scss b/src/layouts/Layout/Layout.scss index 74b2da7..c977b8b 100644 --- a/src/layouts/Layout/Layout.scss +++ b/src/layouts/Layout/Layout.scss @@ -1,18 +1,16 @@ // ============================================================================= // Layout 视口缩放(逻辑见 useViewportScale.ts) // DOM:layoutShellViewport → layoutShellScaleSpacer → layoutShellScaleInner → layoutShell -// 变量:--layout-scale(JS 写入) / --layout-chrome-top(顶栏 + promo 总高) +// 变量:--layout-scale(JS 写入) / --layout-chrome-top(供页面高度计算) // ============================================================================= -// 1. 视口容器 & 缩放包装层(未缩放时的基础样式) .layoutShellViewport { width: 100%; height: 100vh; - overflow-x: hidden; + overflow: hidden; box-sizing: border-box; scrollbar-width: none; -ms-overflow-style: none; - overflow-y: hidden; } .layoutShellScaleSpacer { @@ -24,18 +22,15 @@ box-sizing: border-box; } -// 3. 缩放激活态(host 挂 layoutShellViewport_scaled;--layout-chrome-top 由下方 .layoutShell 提供) .layoutShellViewport_scaled { .layoutShellScaleInner { width: 1920px; } - /* Chromium / Safari:zoom 重排文字,通常比 transform 栅格化更清晰 */ &.layoutShellViewport_scaled--zoom .layoutShellScaleInner { zoom: var(--layout-scale, 1); } - /* Firefox 等:回退 transform */ &.layoutShellViewport_scaled--transform .layoutShellScaleInner { transform-origin: top left; transform: scale(var(--layout-scale, 1)); @@ -43,77 +38,61 @@ -moz-osx-font-smoothing: grayscale; } - /* 缩放后 100vh 视觉变矮,用未缩放坐标系下的 vh / scale 补足 */ .layoutShell { min-height: calc(100vh / var(--layout-scale, 1)); - - .layoutShell__body, - .layoutShell__body .layoutShell__sidebar { - height: calc((100vh - var(--layout-chrome-top)) / var(--layout-scale, 1)); - } } } -// 4. 主壳层 & 顶栏高度 token .layoutShell { - --layout-chrome-top: 89px; /* 33px promo + 56px topbar */ + --layout-promo-height: 33px; + --layout-sidebar-width: 110px; + --layout-topbar-height: 87px; + --layout-chrome-top: calc(var(--layout-promo-height) + var(--layout-topbar-height)); + --layout-sidebar-transition: 0.26s cubic-bezier(0.4, 0, 0.2, 1); + + position: relative; + width: 100%; + min-height: 100vh; + overflow: hidden; + font-family: "Source Han Sans CN", "Noto Sans SC", system-ui, sans-serif; + background: #ffffff; &.layoutShell_promoDismissed { - --layout-chrome-top: 56px; + --layout-promo-height: 0px; } - min-height: 100vh; - overflow-x: hidden; - font-family: "Source Han Sans CN", "Noto Sans SC", system-ui, sans-serif; - background-color: #f9f9f9; + &.layoutShell_sidebarCollapsed { + --layout-sidebar-width: 0px; + } .layoutShell__promoBar { + position: absolute; + inset: 0 0 auto; + z-index: 40; height: 33px; - flex-shrink: 0; display: flex; align-items: center; justify-content: center; - background: linear-gradient(90deg, #8a7fae 0%, #5c3bc7 50%, #8a7fae 100%); - cursor: pointer; overflow: hidden; box-sizing: border-box; - position: relative; - - .layoutShell__promoClose { - position: absolute; - right: 16px; - top: 7px; - width: 16px; - height: 16px; - padding: 0; - border: 0; - background: transparent; - display: block; - cursor: pointer; - - img { - width: 16px; - height: 16px; - object-fit: cover; - display: block; - } - } + background: linear-gradient(90deg, #f2def9 0%, #7f61d5 50%, #f2def9 100%); + cursor: pointer; } .layoutShell__promoBarInner { display: flex; align-items: center; justify-content: center; - gap: 36px; + gap: 43px; max-width: 100%; - padding: 0 16px; + padding: 0 48px; box-sizing: border-box; } .layoutShell__promoSegment { display: flex; align-items: center; - gap: 6px; + gap: 5px; flex-shrink: 0; } @@ -123,1119 +102,1077 @@ justify-content: center; min-width: 39px; height: 16px; - padding: 0 4px; + padding: 0 5px; box-sizing: border-box; border-radius: 5px 0 5px 0; - background-color: rgba(255, 255, 255, 0.3); + background: rgba(255, 255, 255, 0.3); + color: #ffffff; font-size: 12px; font-weight: 500; - line-height: 1; - color: #ffffff; - flex-shrink: 0; + line-height: 16px; + white-space: nowrap; } .layoutShell__promoText { margin: 0; - font-size: 14px; - line-height: 21px; - font-weight: 500; color: #ffffff; + font-size: 14px; + font-weight: 400; + line-height: 20px; white-space: nowrap; - flex-shrink: 0; &_heavy { - font-weight: 800; + font-weight: 500; } &_seedance { display: flex; align-items: center; - gap: 0; } } - @media (max-width: 1200px) { - .layoutShell__promoBarInner { - gap: 20px; + .layoutShell__promoClose { + position: absolute; + top: 8px; + right: 16px; + width: 16px; + height: 16px; + padding: 0; + border: 0; + background: transparent; + cursor: pointer; + + img { + width: 16px; + height: 16px; + display: block; + object-fit: contain; } } - @media (max-width: 960px) { - .layoutShell__promoBar { - display: none; + .layoutShell__sidebarCollapse { + position: absolute; + top: calc(var(--layout-promo-height) + 27px); + left: max(16px, calc(var(--layout-sidebar-width) - 18px)); + z-index: 45; + width: 36px; + height: 36px; + padding: 10px; + border: 0; + border-radius: 24px; + background: #ffffff; + color: #333333; + cursor: pointer; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + transition: + top var(--layout-sidebar-transition), + left var(--layout-sidebar-transition), + width var(--layout-sidebar-transition), + height var(--layout-sidebar-transition), + padding var(--layout-sidebar-transition), + transform var(--layout-sidebar-transition), + box-shadow var(--layout-sidebar-transition); + + .layoutShell__sidebarCollapseText { + max-height: 0; + opacity: 0; + overflow: hidden; + color: #333333; + font-size: 14px; + font-weight: 400; + line-height: 20px; + text-align: center; + white-space: pre-line; + transition: + max-height var(--layout-sidebar-transition), + opacity 0.14s ease; + } + + img { + width: 16px; + height: 16px; + display: block; + object-fit: contain; + transition: transform var(--layout-sidebar-transition); } + } - --layout-chrome-top: 56px; + &.layoutShell_sidebarCollapsed .layoutShell__sidebarCollapse { + top: calc(var(--layout-promo-height) + ((100vh - var(--layout-promo-height)) / 2)); + left: 0; + width: 36px; + height: 91px; + padding: 16px 10px; + transform: translateY(-50%); + + .layoutShell__sidebarCollapseText { + max-height: 40px; + opacity: 1; + transition: + max-height var(--layout-sidebar-transition), + opacity 0.16s ease 0.1s; + } + } + + &.layoutShell_sidebarCollapsed .layoutShell__sidebarCollapse img { + transform: rotate(270deg); } .layoutShell__topbar { - height: 56px; + position: absolute; + top: var(--layout-promo-height); + left: var(--layout-sidebar-width); + right: 0; + z-index: 30; + height: var(--layout-topbar-height); + background: #f1f1f1; + box-sizing: border-box; + transition: left var(--layout-sidebar-transition); + } + + .layoutShell__topbarRight { + position: absolute; + top: 20px; + right: 20px; + height: 47px; display: flex; align-items: center; - justify-content: space-between; - padding: 0 16px 0 0; + gap: 10px; + } + + .layoutShell__topbarTools { + display: flex; + align-items: center; + gap: 10px; + } + + .goToSkillHub { + width: 88px; + height: 31px; + padding: 7px; + border: 1px solid #e7e7e7; + border-radius: 15.5px; + background: #ffffff; box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + gap: 12px; + color: #7f61d5; + font-family: inherit; + cursor: pointer; + } - .layoutShell__topbarLeft { - width: 160px; + .layoutShell__topbarSkillIcon { + width: 17px; + height: 17px; + flex-shrink: 0; + display: block; + object-fit: contain; + } - .layoutShell__logo { - width: 83px; - height: 21px; - display: block; - cursor: pointer; - margin-left: 18px; - } + .layoutShell__topbarSkillText { + font-size: 12px; + font-weight: 400; + line-height: 17px; + color: #7f61d5; + } - .layoutShell__collapse { - border: none; - background: transparent; - padding: 0; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - - img { - width: 18px; - height: 18px; - display: block; - transition: transform 0.2s ease; - } - } + .layoutShell__topbarTool { + position: relative; + width: 31px; + height: 31px; + flex-shrink: 0; + } + + .layoutShell__topbarIconBtn { + width: 31px; + height: 31px; + padding: 0; + border: 0; + border-radius: 15.5px; + background: transparent; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + img { + width: 31px; + height: 31px; + display: block; + object-fit: contain; } + } - .layoutShell__topbarRight { - display: flex; - align-items: center; - gap: 8px; + .layoutShell__topbarIconBtn_active { + background: #ffffff; + } - .layoutShell__aliceMeetupTool { - position: relative; + .layoutShell__topbarServiceQrFloat { + position: absolute; + top: calc(100% + 8px); + right: 0; + z-index: 1300; + width: 140px; + padding: 10px; + border: 1px solid #e8e8e8; + border-radius: 8px; + background: #ffffff; + box-shadow: 2px 4px 15px rgba(0, 0, 0, 0.12); + box-sizing: border-box; + } - &:hover, - &:focus-within { - .layoutShell__aliceMeetupFloat { - opacity: 1; - visibility: visible; - pointer-events: auto; - } - } + .layoutShell__topbarServiceQrImage { + width: 120px; + height: 120px; + display: block; + margin: 0 auto; + object-fit: contain; + } - .layoutShell__aliceMeetupFloat { - position: absolute; - top: calc(100% + 8px); - left: 50%; - transform: translateX(-50%); - width: 140px; - padding: 10px; - border-radius: 8px; - background: #fff; - border: 1px solid #e8e8e8; - box-shadow: 2px 4px 15px rgb(0 0 0 / 12%); - box-sizing: border-box; - z-index: 1300; - opacity: 0; - visibility: hidden; - pointer-events: none; - transition: - opacity 0.15s ease, - visibility 0.15s ease; - } + .layoutShell__topbarServiceQrTip { + margin: 8px 0 0; + color: #333333; + font-size: 12px; + line-height: 1.4; + text-align: center; + } - .layoutShell__aliceMeetupQr { - width: 120px; - height: 120px; - display: block; - margin: 0 auto; - object-fit: contain; - } + .layoutShell__languageDropdown { + min-width: 128px; + padding: 4px; + border: 1px solid #e5e6eb; + border-radius: 9px; + background: #ffffff; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); + box-sizing: border-box; + } - .layoutShell__aliceMeetupFloatText { - margin: 8px 0 0; - font-size: 12px; - line-height: 1.4; - color: #333; - text-align: center; - } - } + .layoutShell__languageDropdownItem { + width: 100%; + height: 32px; + padding: 0 8px; + border: 0; + border-radius: 6px; + background: transparent; + color: #333333; + font-size: 13px; + text-align: left; + cursor: pointer; + } - .layoutShell__aliceMeetup { - display: flex; - align-items: center; - justify-content: center; - gap: 4px; - padding: 6px; - border: none; - border-radius: 100px; - background: #f9f9f9; - cursor: pointer; - font-family: inherit; - - .layoutShell__aliceMeetupIcon { - width: 20px; - height: 20px; - flex-shrink: 0; - display: block; - border-radius: 50%; - object-fit: cover; - } + .layoutShell__languageDropdownItem:hover { + background: #f5f5f5; + } - .layoutShell__aliceMeetupText { - font-size: 14px; - font-weight: 500; - line-height: normal; - color: #5c3bc7; - white-space: nowrap; - } - } + .layoutShell__languageDropdownItem.layoutShell__languageDropdownItem_active { + color: #7f61d5; + font-weight: 500; + } - .layoutShell__topbarTools { - display: flex; - align-items: center; - gap: 8px; - } + .layoutShell__userPoints { + height: 31px; + min-width: 110px; + padding: 0 16px; + border: 1px solid #e7e7e7; + border-radius: 15.5px; + background: #ffffff; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + cursor: pointer; + } - .goToSkillHub { - width: 68px; - height: 33px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 9px; - background-color: #ffffff; - cursor: pointer; - - img { - width: 9px; - height: 9px; - display: block; - margin-right: 3px; - } + .layoutShell__userPointsValue, + .layoutShell__userPointsLabel { + color: #7b7b7b; + font-size: 12px; + font-weight: 400; + line-height: 17px; + } - font-size: 12px; - font-weight: 700; - color: #666666; - } + .layoutShell__avatarMenu { + position: relative; + width: 47px; + height: 47px; + flex-shrink: 0; + } - .layoutShell__topbarTool { - position: relative; - - .layoutShell__topbarIconBtn { - width: 33px; - height: 33px; - border: none; - background: transparent; - padding: 0; - cursor: pointer; - border-radius: 9px; - - img { - width: 20px; - height: 20px; - display: block; - } - } + .layoutShell__avatar, + .layoutShell__avatar_guest { + width: 47px; + height: 47px; + padding: 0; + border: 0; + border-radius: 50%; + background: transparent; + display: block; + cursor: pointer; + } - .layoutShell__topbarIconBtn_active { - background: #f0f0f0; - } + .layoutShell__avatarBg { + width: 47px; + height: 47px; + border-radius: 50%; + display: block; + object-fit: cover; + } - .layoutShell__topbarServiceQrFloat { - position: absolute; - top: calc(100% + 8px); - left: 0; - width: 140px; - padding: 10px; - border-radius: 8px; - background: #fff; - border: 1px solid #e8e8e8; - box-shadow: 2px 4px 15px rgb(0 0 0 / 12%); - box-sizing: border-box; - z-index: 1300; - } + .layoutShell__avatarDropdown { + position: absolute; + top: 55px; + right: 0; + z-index: 1300; + width: 317px; + padding: 10px; + border-radius: 9px; + background: #ffffff; + box-shadow: 0 2px 15px rgba(51, 51, 51, 0.2); + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 5px; + } - .layoutShell__topbarServiceQrImage { - width: 120px; - height: 120px; - display: block; - margin: 0 auto; - object-fit: contain; - } + .layoutShell__avatarUserCard { + width: 297px; + min-height: 67px; + padding: 13px 17px; + border-radius: 5px; + background: #ffffff; + display: flex; + align-items: flex-start; + gap: 10px; + box-sizing: border-box; + } - .layoutShell__topbarServiceQrTip { - margin: 8px 0 0; - font-size: 12px; - line-height: 1.4; - color: #333; - text-align: center; - } + .layoutShell__avatarUserAvatar { + width: 38px; + height: 38px; + border-radius: 19px; + flex-shrink: 0; + display: block; + object-fit: cover; + } - /* 语言菜单:由 Arco Dropdown 定位,此处只保留卡片样式 */ - .layoutShell__languageDropdown { - min-width: 128px; - background: #fff; - border: 1px solid #e5e6eb; - border-radius: 9px; - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); - padding: 4px; - box-sizing: border-box; - } + .layoutShell__avatarUserMain { + min-width: 0; + padding-top: 2px; + display: flex; + flex-direction: column; + } - .layoutShell__languageDropdownItem { - width: 100%; - height: 32px; - border: none; - background: transparent; - border-radius: 6px; - font-size: 13px; - color: #333; - cursor: pointer; - text-align: left; - padding: 0 8px; - } + .layoutShell__avatarUserName { + overflow: hidden; + color: #333333; + font-size: 16px; + font-weight: 500; + line-height: 16px; + text-overflow: ellipsis; + white-space: nowrap; + } - .layoutShell__languageDropdownItem:hover { - background: #f5f5f5; - } + .layoutShell__avatarUserMeta { + display: flex; + align-items: center; + } - .layoutShell__languageDropdownItem.layoutShell__languageDropdownItem_active { - color: #165dff; - font-weight: 500; - } - } + .layoutShell__avatarUserId { + color: #999999; + font-size: 12px; + line-height: 26px; + } - .layoutShell__userPoints { - border-radius: 9px; - padding: 0 10px; - height: 33px; - display: flex; - align-items: center; - gap: 4px; - background-color: #ffffff; - cursor: pointer; - - .layoutShell__userPointsIcon { - width: 18px; - height: 18px; - object-fit: cover; - display: block; - } + .layoutShell__avatarCopyId { + width: 15px; + height: 15px; + margin-left: 2px; + padding: 0; + border: 0; + background: transparent; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; - .layoutShell__userPointsValue { - font-size: 14px; - font-weight: 500; - line-height: 14px; - color: #333333; - } + img { + width: 15px; + height: 15px; + display: block; + } + } - .layoutShell__userPointsLabel { - font-size: 12px; - font-weight: 400; - line-height: 12px; - color: #666666; - } - } + .layoutShell__avatarMemberRow { + width: 297px; + height: 39px; + padding: 0 17px; + border: 0; + border-radius: 5px; + background: linear-gradient(90deg, #f0f0f0 0%, #d6ceef 100%); + display: flex; + align-items: center; + text-align: left; + cursor: pointer; + } - .layoutShell__avatarMenu { - position: relative; - - .layoutShell__avatar { - width: 38px; - height: 38px; - position: relative; - border: none; - background: transparent; - padding: 0; - cursor: pointer; - - .layoutShell__avatarBg { - width: 38px; - height: 38px; - display: block; - border-radius: 19px; - object-fit: cover; - } - } + .layoutShell__avatarMemberLabel { + margin-right: auto; + color: #666666; + font-size: 12px; + line-height: 12px; + } - .layoutShell__avatarDropdown { - position: absolute; - top: 44px; - right: 0; - width: 317px; - background: #fff; - border-radius: 9px; - box-shadow: 0 2px 15px rgba(51, 51, 51, 0.2); - padding: 10px; - box-sizing: border-box; - z-index: 20; - display: flex; - flex-direction: column; - gap: 5px; - - .layoutShell__avatarUserCard { - width: 297px; - height: 67px; - border-radius: 5px; - background: #fff; - padding: 13px 17px; - display: flex; - align-items: flex-start; - box-sizing: border-box; - gap: 10px; - } - - .layoutShell__avatarUserAvatar { - width: 38px; - height: 38px; - border-radius: 19px; - display: block; - object-fit: cover; - } - - .layoutShell__avatarUserMain { - display: flex; - flex-direction: column; - min-width: 0; - padding-top: 2px; - } - - .layoutShell__avatarUserName { - font-size: 16px; - line-height: 16px; - font-weight: 500; - color: #333; - } - - .layoutShell__avatarUserMeta { - display: flex; - align-items: center; - } - - .layoutShell__avatarUserId { - font-size: 12px; - line-height: 26px; - color: #999; - } - - .layoutShell__avatarCopyId { - width: 15px; - height: 15px; - margin-left: 2px; - border: none; - background: transparent; - padding: 0; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - - img { - width: 15px; - height: 15px; - display: block; - } - } - - .layoutShell__avatarMemberRow { - width: 297px; - height: 39px; - border: none; - border-radius: 5px; - background: linear-gradient(90deg, #f0f0f0 0%, #d6ceef 100%); - padding: 0 17px; - display: flex; - align-items: center; - text-align: left; - cursor: pointer; - } - - .layoutShell__avatarMemberLabel { - font-size: 12px; - line-height: 12px; - color: #666; - margin-right: auto; - } - - .layoutShell__avatarMemberAction { - font-size: 12px; - line-height: 12px; - font-weight: 500; - color: #333; - } - - .layoutShell__avatarChevron { - width: 15px; - height: 15px; - margin-left: 1px; - display: block; - } - - .layoutShell__avatarPointsCard { - width: 297px; - height: 63px; - border-radius: 5px; - border: 1px solid #f8f8f8; - background: #f0f0f0; - padding: 8px 17px; - display: flex; - align-items: flex-start; - justify-content: space-between; - box-sizing: border-box; - } - - .layoutShell__avatarPointsLeft { - display: flex; - flex-direction: column; - } - - .layoutShell__avatarPointsTop { - display: flex; - align-items: center; - } - - .layoutShell__avatarPointsValue { - font-size: 16px; - line-height: 16px; - font-weight: 500; - color: #333; - } - - .layoutShell__avatarPointsIcon { - width: 15px; - height: 15px; - margin-left: 0; - display: block; - } - - .layoutShell__avatarPointsDetail { - margin-top: 7px; - border: none; - background: transparent; - padding: 0; - display: flex; - align-items: center; - text-align: left; - font-size: 12px; - line-height: 12px; - color: #666; - cursor: pointer; - } - - .layoutShell__avatarArrow { - width: 15px; - height: 15px; - margin-left: 0; - display: block; - } - - .layoutShell__avatarRechargeBtn { - margin-top: 1px; - width: 61px; - height: 19px; - border: none; - border-radius: 5px; - background: linear-gradient(90deg, #8a7fae 0%, #5c3bc7 100%); - color: #fff; - font-size: 12px; - line-height: 12px; - font-weight: 500; - padding: 0; - cursor: pointer; - } - - .layoutShell__avatarDropdownItem { - width: 297px; - height: 37px; - border: none; - border-radius: 5px; - background: #fff; - padding: 0 12px; - display: flex; - align-items: center; - gap: 8px; - text-align: left; - cursor: pointer; - color: #666; - font-size: 14px; - line-height: 14px; - } - - .layoutShell__avatarDropdownIcon { - width: 18px; - height: 18px; - display: block; - } - - .layoutShell__avatarDropdownItem:hover { - background: #f7f7f7; - } - } - } + .layoutShell__avatarMemberAction { + color: #333333; + font-size: 12px; + font-weight: 500; + line-height: 12px; + } - .layoutShell__loginBtn { - height: 33px; - border-radius: 9px; - background-color: #ffffff; - display: flex; - align-items: center; - justify-content: center; - padding: 0 10px; - color: #333333; - cursor: pointer; - } - } + .layoutShell__avatarChevron { + width: 15px; + height: 15px; + margin-left: 1px; + display: block; } - .layoutShell__body { - position: relative; + .layoutShell__avatarPointsCard { + width: 297px; + height: 63px; + padding: 8px 17px; + border: 1px solid #f8f8f8; + border-radius: 5px; + background: #f0f0f0; display: flex; - height: calc(100vh - var(--layout-chrome-top)); + align-items: flex-start; + justify-content: space-between; box-sizing: border-box; + } - .layoutShell__sidebar { - position: relative; - width: 160px; - height: calc(100vh - var(--layout-chrome-top)); - padding: 0 8px 16px; - display: flex; - flex-direction: column; - box-sizing: border-box; - position: relative; - z-index: 21; // 高于 modalMount 的 20 + .layoutShell__avatarPointsLeft { + display: flex; + flex-direction: column; + } - &.layoutShell__sidebar_footerPopoverOpen { - z-index: 100; - overflow: visible; - } + .layoutShell__avatarPointsTop { + display: flex; + align-items: center; + } - .layoutShell__appDownload { - width: 100%; - padding: 8px 0 ; - box-sizing: border-box; - - .layoutShell__appDownloadTitle { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - height: 37px; - margin: 0; - padding: 0 10px; - box-sizing: border-box; - border: none; - border-radius: 9px; - background-color: #f9f9f9; - cursor: pointer; - font-family: inherit; - text-align: left; - - } + .layoutShell__avatarPointsValue { + color: #333333; + font-size: 16px; + font-weight: 500; + line-height: 16px; + } - .layoutShell__appDownloadTitleIcon { - width: 18px; - height: 18px; - flex-shrink: 0; - display: block; - object-fit: cover; - } + .layoutShell__avatarPointsIcon { + width: 15px; + height: 15px; + display: block; + } - .layoutShell__appDownloadTitleText { - font-size: 12px; - font-weight: 400; - line-height: normal; - color: #333333; - white-space: nowrap; - } + .layoutShell__avatarPointsDetail { + margin-top: 7px; + padding: 0; + border: 0; + background: transparent; + display: flex; + align-items: center; + color: #666666; + font-size: 12px; + line-height: 12px; + text-align: left; + cursor: pointer; + } - } + .layoutShell__avatarArrow { + width: 15px; + height: 15px; + display: block; + } - .layoutShell__sideNav { - flex: 1; - width: 100%; - padding-top: 7px; - display: flex; - flex-direction: column; - gap: 10px; - - .layoutShell__sideItem { - height: 37px; - width: 100%; - border-radius: 9px; - background-color: #f9f9f9; - display: flex; - align-items: center; - gap: 8px; - text-decoration: none; - box-sizing: border-box; - padding: 0 10px; - - .layoutShell__sideIcon { - width: 18px; - height: 18px; - object-fit: cover; - display: block; - } - - .layoutShell__sideText { - font-size: 14px; - font-weight: 500; - line-height: 14px; - color: #333333; - white-space: nowrap; - } - - .layoutShell__sideBadge { - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 34px; - height: 16px; - margin-left: auto; - padding: 2px 3px; - box-sizing: border-box; - border-radius: 24px; - background-color: #d6ceef; - font-size: 12px; - font-weight: 500; - line-height: 1; - color: #ffffff; - white-space: nowrap; - } - } + .layoutShell__avatarRechargeBtn { + width: 61px; + height: 19px; + margin-top: 1px; + padding: 0; + border: 0; + border-radius: 5px; + background: linear-gradient(90deg, #8a7fae 0%, #5c3bc7 100%); + color: #ffffff; + font-size: 12px; + font-weight: 500; + line-height: 12px; + cursor: pointer; + } - .layoutShell__sideItem.layoutShell__sideItem_active { - background-color: #f0f0f0; - } - } + .layoutShell__avatarDropdownItem { + width: 297px; + height: 37px; + padding: 0 12px; + border: 0; + border-radius: 5px; + background: #ffffff; + display: flex; + align-items: center; + gap: 8px; + color: #666666; + font-family: inherit; + font-size: 14px; + line-height: 14px; + text-align: left; + cursor: pointer; + } - .layoutShell__aliceLiveClassTool { - flex-shrink: 0; - align-self: flex-start; - margin-left: -8px; - margin-bottom: 10px; - z-index: 16; - - .layoutShell__aliceLiveClass { - width: 212px; - height: auto; - height: 47px; - padding: 5px; - box-sizing: border-box; - border: none; - border-radius: 0 24px 24px 0; - background: linear-gradient(270deg, #ebe5ff 0%, #f0f0f0 100%); - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - gap: 5px; - cursor: pointer; - font-family: inherit; - } + .layoutShell__avatarDropdownItem:hover { + background: #f7f7f7; + } - .layoutShell__aliceLiveClass:focus-visible { - outline: 2px solid #5c3bc7; - outline-offset: 2px; - } + .layoutShell__avatarDropdownIcon { + width: 18px; + height: 18px; + display: block; + } - .layoutShell__aliceLiveClassIcon { - width: 37px; - height: 37px; - flex-shrink: 0; - display: block; - object-fit: cover; - } + .layoutShell__body { + position: absolute; + top: var(--layout-promo-height); + right: 0; + bottom: 0; + left: 0; + z-index: 20; + background: #ffffff; + } - .layoutShell__aliceLiveClassText { - flex-shrink: 0; - font-size: 18px; - font-weight: 500; - line-height: normal; - color: #333333; - white-space: nowrap; - } + .layoutShell__sidebar { + position: absolute; + top: 0; + bottom: 0; + left: 0; + z-index: 35; + width: 110px; + padding: 27px 20px; + background: #ffffff; + box-sizing: border-box; + display: flex; + flex-direction: column; + transition: + opacity var(--layout-sidebar-transition), + transform var(--layout-sidebar-transition); + } - .layoutShell__aliceLiveClassArrow { - width: 25px; - height: 25px; - flex-shrink: 0; - display: block; - object-fit: contain; - transform: rotate(-90deg); - } - } + &.layoutShell_sidebarCollapsed .layoutShell__sidebar { + transform: translateX(-110px); + opacity: 0; + pointer-events: none; + } - .layoutShell__footer { - position: relative; - margin-top: auto; - padding-top: 8px; - padding-bottom: 44px; - - .layoutShell__footerNav { - display: flex; - flex-direction: column; - gap: 10px; - width: 100%; - - .layoutShell__sideItem { - height: 37px; - width: 100%; - border-radius: 9px; - background-color: #f9f9f9; - display: flex; - align-items: center; - gap: 8px; - text-decoration: none; - box-sizing: border-box; - padding: 0 10px; - border: none; - cursor: pointer; - font-family: inherit; - text-align: left; - - .layoutShell__sideIcon { - width: 18px; - height: 18px; - object-fit: cover; - display: block; - } - - .layoutShell__sideText { - font-size: 14px; - font-weight: 500; - line-height: 14px; - color: #333333; - white-space: nowrap; - } - } - - .layoutShell__sideItem.layoutShell__sideItem_active { - background-color: #f0f0f0; - } - } + .layoutShell__sidebar_footerPopoverOpen { + z-index: 100; + overflow: visible; + } - .layoutShell__footerPopoverTrigger { - position: absolute; - left: 5px; - bottom: 0; - } + .layoutShell__brand { + width: 70px; + height: 36px; + padding: 5px 15px 4px; + border: 0; + border-radius: 24px; + background: #ffffff; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + } - .layoutShell__footerMenuBtn { - display: block; - width: 32px; - min-width: auto; - height: 32px; - padding: 0; - line-height: 32px; - border: none; - outline: none; - background: transparent; - color: inherit; - cursor: pointer; - - img { - display: block; - width: 14px; - height: 10px; - margin: 0 auto; - } - } + .layoutShell__brandLogo { + width: 40px; + height: 27px; + display: block; + object-fit: contain; + } - .layoutShell__footerPopoverPanel { - position: absolute; - left: 0; - bottom: 30px; - margin-left: 8px; - width: 235px; - padding: 5px; - box-sizing: border-box; - background: #ffffff; - border-radius: 8px; - box-shadow: 2px 4px 15px rgba(0, 0, 0, 0.12); - border-left: 1px solid #e8e8e8; - z-index: 1200; - display: flex; - flex-direction: column; - overflow: visible; - } + .layoutShell__sideNav { + width: 70px; + height: 390px; + margin-top: 50px; + display: flex; + flex-direction: column; + gap: 10px; + } - .layoutShell__footerPopoverLink { - width: 100%; - height: 40px; - display: flex; - align-items: center; - font-size: 12px; - color: #666666; - padding-left: 10px; - box-sizing: border-box; - text-decoration: none; - border: none; - background: transparent; - cursor: pointer; - font-family: inherit; - text-align: left; - } + .layoutShell__sideItem { + position: relative; + width: 70px; + height: 70px; + padding: 10px 16px; + border: 0; + border-radius: 24px; + background: #ffffff; + box-sizing: border-box; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + color: #333333; + font-family: inherit; + text-align: center; + text-decoration: none; + cursor: pointer; + } - .layoutShell__footerPopoverLink:hover { - background: #f0f0f0; - } + .layoutShell__sideItem_active { + background: #f0f4f9; + } - .layoutShell__footerPopoverMeta { - padding: 16px 0 0 16px; - font-size: 10px; - color: #999999; - line-height: 1.5; - text-align: left; - margin-bottom: 20px; - } - } + .layoutShell__sideIcon { + position: relative; + width: 25px; + height: 25px; + flex-shrink: 0; + display: block; + } + + img.layoutShell__sideIcon { + object-fit: contain; + } + + .layoutShell__sideIconImage { + position: absolute; + inset: 0; + width: 25px; + height: 25px; + display: block; + object-fit: contain; + } + + .layoutShell__sideIconImage_active { + opacity: 0; + } + + .layoutShell__sideItem_active .layoutShell__sideIconImage_default { + opacity: 0; + } + + .layoutShell__sideItem_active .layoutShell__sideIconImage_active { + opacity: 1; + } + + .layoutShell__sideText { + min-width: 38px; + color: #333333; + font-size: 14px; + font-weight: 500; + line-height: 20px; + white-space: nowrap; + } + + .layoutShell__sideItem_active .layoutShell__sideText { + color: #6f47f5; + } + + .layoutShell__sideBadge { + position: absolute; + top: 10px; + right: -17px; + width: 27px; + height: 12px; + border: 1px solid #b59ce6; + border-radius: 7.5px; + background: linear-gradient(90deg, #f2def9 0%, #7f61d5 100%); + color: #ffffff; + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + font-size: 6px; + font-weight: 700; + line-height: 7px; + } + + .layoutShell__footer { + width: 70px; + height: 205px; + margin-top: auto; + position: relative; + display: flex; + flex-direction: column; + } + + .layoutShell__footerNav { + width: 70px; + display: flex; + flex-direction: column; + gap: 20px; + } + + .layoutShell__footerPopoverTrigger { + position: relative; + width: 25px; + height: 25px; + margin: 20px auto 0; + } + + .layoutShell__footerMenuBtn { + width: 25px; + height: 25px; + padding: 0; + border: 0; + background: #f9f9f9; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + + img { + width: 19.5px; + height: 14px; + display: block; + object-fit: contain; } + } - /* 内容区视口壳:不滚动;滚动与全屏弹窗分层(方案 A) */ - .layoutShell__contentFrame { - position: relative; - flex: 1; - min-width: 0; + .layoutShell__footerPopoverPanel { + position: absolute; + left: 20px; + bottom: 30px; + z-index: 1200; + width: 235px; + padding: 5px; + border-left: 1px solid #e8e8e8; + border-radius: 8px; + background: #ffffff; + box-shadow: 2px 4px 15px rgba(0, 0, 0, 0.12); + box-sizing: border-box; + display: flex; + flex-direction: column; + } + + .layoutShell__footerPopoverLink { + width: 100%; + height: 40px; + padding-left: 10px; + border: 0; + background: transparent; + color: #666666; + font-family: inherit; + font-size: 12px; + text-align: left; + text-decoration: none; + cursor: pointer; + box-sizing: border-box; + } + + .layoutShell__footerPopoverLink:hover { + background: #f0f0f0; + } + + .layoutShell__footerPopoverMeta { + margin-bottom: 20px; + padding: 16px 0 0 16px; + color: #999999; + font-size: 10px; + line-height: 1.5; + text-align: left; + } + + .layoutShell__contentFrame { + position: absolute; + top: var(--layout-topbar-height); + right: 0; + bottom: 0; + left: var(--layout-sidebar-width); + z-index: 20; + min-width: 0; + min-height: 0; + overflow: hidden; + background: #f2f2f2; + display: flex; + flex-direction: column; + transition: left var(--layout-sidebar-transition); + } + + .layoutShell__contentScroll { + position: relative; + flex: 1 1 0; + min-width: 0; + min-height: 0; + overflow: auto; + scrollbar-width: none; + -ms-overflow-style: none; + + > .createPage { + height: 100%; + max-height: 100%; min-height: 0; - margin-right: 14px; - margin-bottom: 14px; - border-radius: 20px; + } + + &.layoutShell__contentScroll_scrollLocked { overflow: hidden; + } + + &::-webkit-scrollbar { + display: none; + } + } + + .layoutShell__modalMount { + position: absolute; + inset: 0; + z-index: 20; + overflow: hidden; + pointer-events: none; + + .arco-modal-mask { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + max-width: 100%; + max-height: 100%; + box-sizing: border-box; + pointer-events: auto; + } + + .arco-modal-wrapper { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + max-width: 100%; + max-height: 100%; + box-sizing: border-box; display: flex; flex-direction: column; + align-items: stretch; + pointer-events: auto; + background: #f9f9f9; + overflow: hidden; + overscroll-behavior: contain; + } - > .layoutShell__contentScroll { - position: relative; - flex: 1 1 0; - min-height: 0; - min-width: 0; - overflow-y: auto; - overflow-x: hidden; - scrollbar-width: none; - -ms-overflow-style: none; - - /* Create 页自管内部滚动,占满滚动层高度 */ - > .createPage { - height: 100%; - max-height: 100%; - min-height: 0; - } - - &.layoutShell__contentScroll_scrollLocked { - overflow: hidden; - } + .arco-modal-wrapper.layoutFullscreenDetailModalWrap { + overflow: hidden !important; + scrollbar-width: none; + -ms-overflow-style: none; - &::-webkit-scrollbar { - display: none; - } + &::-webkit-scrollbar { + display: none; } - /* 全屏详情弹窗遮罩:与滚动层兄弟,absolute 铺满视口壳,不受 scrollTop 影响 */ - > .layoutShell__modalMount { - position: absolute; - inset: 0; - z-index: 20; - pointer-events: none; - border-radius: inherit; - overflow: hidden; - - .arco-modal-mask { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - max-width: 100%; - max-height: 100%; - box-sizing: border-box; - pointer-events: auto; - } - - .arco-modal-wrapper { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - max-width: 100%; - max-height: 100%; - box-sizing: border-box; - display: flex; - flex-direction: column; - align-items: stretch; - pointer-events: auto; - background: #f9f9f9; - overflow: hidden; - overflow-x: hidden; - overscroll-behavior: contain; + .character-detail-arco-modal, + .asset-details-arco-modal { + &.zoomModal-appear, + &.zoomModal-enter, + &.zoomModal-appear-active, + &.zoomModal-enter-active { + opacity: 1 !important; + transform: none !important; + transition: none !important; } + } + } - /* 全屏详情弹窗:覆盖 Arco 默认 overflow:auto,并关闭 zoom 进入动画避免首帧滚动条闪烁 */ - .arco-modal-wrapper.layoutFullscreenDetailModalWrap { - overflow: hidden !important; - scrollbar-width: none; - -ms-overflow-style: none; - - &::-webkit-scrollbar { - display: none; - } - - .character-detail-arco-modal, - .asset-details-arco-modal { - &.zoomModal-appear, - &.zoomModal-enter, - &.zoomModal-appear-active, - &.zoomModal-enter-active { - opacity: 1 !important; - transform: none !important; - transition: none !important; - } - } - } + .arco-modal-wrapper > * { + flex: 1 1 0; + min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; + } - .arco-modal-wrapper > * { - flex: 1 1 0; - min-height: 0; - min-width: 0; - display: flex; - flex-direction: column; - } + .arco-modal-wrapper .asset-details-arco-modal, + .arco-modal-wrapper .character-detail-arco-modal { + flex: 1 1 0; + min-width: 0; + min-height: 0; + max-width: 100%; + align-self: stretch; + overflow: hidden; + box-sizing: border-box; + display: flex; + flex-direction: column; + } - .arco-modal-wrapper .asset-details-arco-modal, - .arco-modal-wrapper .character-detail-arco-modal { - flex: 1 1 0; - min-height: 0; - min-width: 0; - max-width: 100%; - align-self: stretch; - display: flex; - flex-direction: column; - box-sizing: border-box; - overflow: hidden; - overflow-x: hidden; - } + .arco-modal-wrapper .asset-details-arco-modal.arco-modal > *, + .arco-modal-wrapper .character-detail-arco-modal.arco-modal > * { + flex: 1 1 0; + min-width: 0; + min-height: 0; + display: flex; + flex-direction: column; + align-items: stretch; + } - .arco-modal-wrapper .asset-details-arco-modal.arco-modal > *, - .arco-modal-wrapper .character-detail-arco-modal.arco-modal > * { - flex: 1 1 0; - min-height: 0; - min-width: 0; - display: flex; - flex-direction: column; - align-items: stretch; - } + .arco-modal-wrapper .asset-details-arco-modal .arco-modal-content, + .arco-modal-wrapper .character-detail-arco-modal .arco-modal-content { + flex: 1 1 0; + min-height: 0; + overflow: hidden; + display: flex; + flex-direction: column; + } - .arco-modal-wrapper .asset-details-arco-modal .arco-modal-content, - .arco-modal-wrapper .character-detail-arco-modal .arco-modal-content { - flex: 1 1 0; - min-height: 0; - overflow: hidden; - display: flex; - flex-direction: column; - } + .arco-modal-wrapper .asset-details-arco-modal .arco-modal-body, + .arco-modal-wrapper .character-detail-arco-modal .arco-modal-body { + flex: 1 1 0; + min-height: 0; + overflow: hidden; + } + } - .arco-modal-wrapper .asset-details-arco-modal .arco-modal-body, - .arco-modal-wrapper .character-detail-arco-modal .arco-modal-body { - flex: 1 1 0; - min-height: 0; - overflow: hidden; - } - } + .layoutShell__floatingComposerMount { + position: fixed; + left: 50%; + bottom: 30px; + z-index: 25; + width: 1040px; + max-width: calc(100vw - 40px); + transform: translateX(-50%); + + .layoutShell__composer { + width: 100%; + max-width: 100%; } - .layoutShell__floatingComposerMount { - width: 1040px; - max-width: calc(100vw - 40px); - position: fixed; - left: 50%; - transform: translateX(-50%); - bottom: 30px; - z-index: 15; - - .layoutShell__composer { - width: 100%; - max-width: 100%; - } + &:has(.layoutShell__composer_collapsed) { + width: 360px; + max-width: min(360px, calc(100vw - 40px)); + } + } +} - &:has(.layoutShell__composer_collapsed) { - width: 360px; - max-width: min(360px, calc(100vw - 40px)); - } +@media (max-width: 1200px) { + .layoutShell { + .layoutShell__promoBarInner { + gap: 24px; } } } -.layoutShell.layoutShell_sidebarCollapsed { - .layoutShell__topbarLeft { - width: 64px; - justify-content: center; +@media (max-width: 960px) { + .layoutShell { + --layout-promo-height: 0px; - .layoutShell__collapse img { - transform: rotate(180deg); + .layoutShell__promoBar { + display: none; } } +} + +@media (max-width: 768px) { + .layoutShell { + --layout-sidebar-width: 76px; + --layout-topbar-height: 70px; - .layoutShell__body { .layoutShell__sidebar { - width: 64px; - padding-left: 8px; - padding-right: 8px; + width: 76px; + padding: 14px 8px; } - .layoutShell__appDownload { - display: none; + &.layoutShell_sidebarCollapsed { + --layout-sidebar-width: 0px; + } + + &.layoutShell_sidebarCollapsed .layoutShell__sidebar { + transform: translateX(-76px); + } + + .layoutShell__sidebarCollapse { + top: 17px; + left: max(10px, calc(var(--layout-sidebar-width) - 18px)); + } + + &.layoutShell_sidebarCollapsed .layoutShell__sidebarCollapse { + top: calc(var(--layout-promo-height) + ((100vh - var(--layout-promo-height)) / 2)); + left: 0; + } + + .layoutShell__brand, + .layoutShell__sideNav, + .layoutShell__footer, + .layoutShell__footerNav { + width: 60px; + } + + .layoutShell__brand { + padding: 4px 10px; } .layoutShell__sideNav { - .layoutShell__sideItem { - justify-content: center; - padding: 0 8px; - gap: 0; + height: auto; + margin-top: 34px; + gap: 8px; + } - .layoutShell__sideText { - display: none; - } + .layoutShell__sideItem { + width: 60px; + height: 60px; + padding: 8px 10px; + border-radius: 20px; + } - .layoutShell__sideBadge { - display: none; - } - } + .layoutShell__sideText { + font-size: 12px; + line-height: 18px; } .layoutShell__footer { - .layoutShell__footerNav { - .layoutShell__sideItem { - justify-content: center; - padding: 0 8px; - gap: 0; - - .layoutShell__sideText { - display: none; - } - } - } + height: auto; } - } -} -@media (max-width: 768px) { - .layoutShell { - .layoutShell__topbar { - .layoutShell__topbarRight { - .layoutShell__avatarMenu { - .layoutShell__avatarDropdown { - right: -8px; - width: min(317px, calc(100vw - 16px)); - - .layoutShell__avatarUserCard, - .layoutShell__avatarMemberRow, - .layoutShell__avatarPointsCard, - .layoutShell__avatarDropdownItem { - width: 100%; - } - } - } - } + .layoutShell__footerNav { + gap: 10px; + } + + .layoutShell__footerPopoverTrigger { + margin-top: 10px; + } + + .layoutShell__topbarRight { + top: 14px; + right: 10px; + gap: 6px; + } + + .layoutShell__topbarTools { + gap: 6px; + } + + .goToSkillHub, + .layoutShell__userPoints { + display: none; + } + + .layoutShell__avatarMenu, + .layoutShell__avatar, + .layoutShell__avatar_guest, + .layoutShell__avatarBg { + width: 38px; + height: 38px; + } + + .layoutShell__avatarDropdown { + right: -8px; + width: min(317px, calc(100vw - 16px)); + } + + .layoutShell__avatarUserCard, + .layoutShell__avatarMemberRow, + .layoutShell__avatarPointsCard, + .layoutShell__avatarDropdownItem { + width: 100%; } } } diff --git a/src/layouts/Layout/Layout.tsx b/src/layouts/Layout/Layout.tsx index c51be78..fe16f5a 100644 --- a/src/layouts/Layout/Layout.tsx +++ b/src/layouts/Layout/Layout.tsx @@ -1,7 +1,6 @@ import type { MouseEvent as ReactMouseEvent, ReactNode } from "react"; import { useCallback, useEffect, useRef, useState } from "react"; -import { createPortal } from "react-dom"; -import { Dropdown, Message, Tooltip } from "@arco-design/web-react"; +import { Dropdown, Tooltip } from "@arco-design/web-react"; import { Link, useLocation, useNavigate } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { useShallow } from "zustand/react/shallow"; @@ -12,14 +11,12 @@ import { hasPendingExploreTemplateTabIntent } from "@/store/useExploreFeedTabSto import { isLayoutContentScrollLocked, isScrollEventFromLayoutModalMount, isScrollEventInLayoutContentScroll, setLayoutContentModalMount, setLayoutContentScrollRoot } from "@/layouts/layoutContentModalMount"; import { changeLanguage, type AppLanguage, SUPPORTED_LANGUAGES } from "@/i18n"; import { closeAssetDetailsModal, closeCharacterDetailModal, openGatewayApikeyDialog, openLoginDialog, openPointsDetailDialog, openPointsRechargeDialog } from "@/dialog"; -import { AliceLiveClassDialogOverlay } from "@/dialog/alice-live-class"; -import { AppMacDownloadDialogOverlay } from "@/dialog/app-download-mac"; import { canAccessRoute, guardRouteAccess } from "@/router/routeGuard"; import { useComposerStore } from "@/store/useComposerStore"; import { useUserStore } from "@/store/useUserStore"; import { getToken, removeToken } from "@/utils/auth"; import { openLatestPrivacyPolicyLink, PRIVACY_POLICY_KEY_AI_USE_NOTICE, PRIVACY_POLICY_KEY_PRIVACY_POLICY, PRIVACY_POLICY_KEY_USAGE_TUTORIAL, PRIVACY_POLICY_KEY_USER_TERMS } from "@/utils/openLatestPrivacyPolicyLink"; -import { getAppLatest, getParamConfig, logout } from "@/api/api"; +import { getParamConfig, logout } from "@/api/api"; import { useCopyText } from "@/hooks/useCopyText"; import { useLayoutViewportScale } from "@/hooks/useViewportScale"; import { memberLevelMap } from "@/constants"; @@ -34,44 +31,78 @@ interface LayoutSideNavItem { id: string; labelKey: string; iconSrc: string; + activeIconSrc?: string; to: string; - badge?: string; + badgeKey?: string; +} + +interface LayoutSideNavIconProps { + item: LayoutSideNavItem; } const CANVAS_BASE_URL = import.meta.env.VITE_CANVAS_URL as string | undefined; -// 侧栏图标(顺序:探索 → 角色 → 声音 → 创造 → Popi TV) +const LAYOUT_NAV_ASSETS = { + logo: new URL("@/assets/images/layout/nav/popi-logo.png", import.meta.url).href, + collapse: new URL("@/assets/images/layout/nav/collapse.png", import.meta.url).href, + explore: new URL("@/assets/images/layout/nav/explore.png", import.meta.url).href, + exploreActive: new URL("@/assets/images/layout/nav/explore_active.png", import.meta.url).href, + characters: new URL("@/assets/images/layout/nav/characters.png", import.meta.url).href, + charactersActive: new URL("@/assets/images/layout/nav/characters_active.png", import.meta.url).href, + create: new URL("@/assets/images/layout/nav/create.png", import.meta.url).href, + createActive: new URL("@/assets/images/layout/nav/create_active.png", import.meta.url).href, + canvas: new URL("@/assets/images/layout/nav/popi-tv.png", import.meta.url).href, + canvasActive: new URL("@/assets/images/layout/nav/popi-tv_active.png", import.meta.url).href, + voice: new URL("@/assets/images/layout/nav/voice.png", import.meta.url).href, + voiceActive: new URL("@/assets/images/layout/nav/voice_active.png", import.meta.url).href, + subscribe: new URL("@/assets/images/layout/nav/subscribe.png", import.meta.url).href, + subscribeActive: new URL("@/assets/images/layout/nav/subscribe_active.png", import.meta.url).href, + profile: new URL("@/assets/images/layout/nav/profile.png", import.meta.url).href, + profileActive: new URL("@/assets/images/layout/nav/profile_active.png", import.meta.url).href, + more: new URL("@/assets/images/layout/nav/more.png", import.meta.url).href, + topHelp: new URL("@/assets/images/layout/nav/top-language.png", import.meta.url).href, + topLanguage: new URL("@/assets/images/layout/nav/top-help.png", import.meta.url).href, + topService: new URL("@/assets/images/layout/nav/top-weixin.png", import.meta.url).href, + topSkill: new URL("@/assets/images/layout/nav/top-skill.png", import.meta.url).href, +} as const; + +// 侧栏图标顺序与 2026 7月官网 Figma 设计保持一致。 const LAYOUT_SIDE_NAV_ITEMS: readonly LayoutSideNavItem[] = [ { id: "explore", labelKey: "layout.nav.explore", - iconSrc: new URL("@/assets/images/menu/explore.png", import.meta.url).href, + iconSrc: LAYOUT_NAV_ASSETS.explore, + activeIconSrc: LAYOUT_NAV_ASSETS.exploreActive, to: ROUTES.EXPLORE, }, { id: "characters", labelKey: "layout.nav.characters", - iconSrc: new URL("@/assets/images/menu/characters.png", import.meta.url).href, + iconSrc: LAYOUT_NAV_ASSETS.characters, + activeIconSrc: LAYOUT_NAV_ASSETS.charactersActive, to: ROUTES.CHARACTERS, }, - { - id: "voice", - labelKey: "layout.nav.voice", - iconSrc: new URL("@/assets/images/menu/voice.png", import.meta.url).href, - to: ROUTES.VOICE, - }, { id: "create", labelKey: "layout.nav.create", - iconSrc: new URL("@/assets/images/menu/create.png", import.meta.url).href, + iconSrc: LAYOUT_NAV_ASSETS.create, + activeIconSrc: LAYOUT_NAV_ASSETS.createActive, to: ROUTES.CREATE, }, { id: "canvas", labelKey: "layout.nav.canvas", - iconSrc: new URL("@/assets/images/menu/canvas.png", import.meta.url).href, + iconSrc: LAYOUT_NAV_ASSETS.canvas, + activeIconSrc: LAYOUT_NAV_ASSETS.canvasActive, to: CANVAS_BASE_URL ?? "", - badge: "Beta", + badgeKey: "layout.nav.canvasBadge", + }, + { + id: "voice", + labelKey: "layout.nav.voice", + iconSrc: LAYOUT_NAV_ASSETS.voice, + activeIconSrc: LAYOUT_NAV_ASSETS.voiceActive, + to: ROUTES.VOICE, }, ]; @@ -82,7 +113,6 @@ const LANGUAGE_LABEL_KEYS: Record = { }; const LAYOUT_PROMO_BAR_DISMISSED_KEY = "popiart.layout.promoBar.dismissed"; -const LAYOUT_APP_DOWNLOAD_TITLE_ICON = new URL("@/assets/images/layout/app-download/ic-popi-logo.png", import.meta.url).href; function isLayoutPromoBarDismissed(): boolean { try { @@ -109,21 +139,43 @@ function buildCanvasUrl(): string | null { return url.toString(); } +function readServiceQrCode(value: unknown): string { + if (typeof value !== "object" || value == null || !("systemConfig" in value)) return ""; + const systemConfig = value.systemConfig; + if (typeof systemConfig !== "object" || systemConfig == null || !("serviceQrCode" in systemConfig)) return ""; + return typeof systemConfig.serviceQrCode === "string" ? systemConfig.serviceQrCode : ""; +} + const LAYOUT_FOOTER_NAV_ITEMS: readonly LayoutSideNavItem[] = [ { id: "subscribe", labelKey: "layout.footer.subscribe", - iconSrc: new URL("@/assets/images/menu/subscribe.png", import.meta.url).href, + iconSrc: LAYOUT_NAV_ASSETS.subscribe, + activeIconSrc: LAYOUT_NAV_ASSETS.subscribeActive, to: ROUTES.SUBSCRIBE, }, { id: "profile", labelKey: "layout.footer.profile", - iconSrc: new URL("@/assets/images/menu/profile.png", import.meta.url).href, + iconSrc: LAYOUT_NAV_ASSETS.profile, + activeIconSrc: LAYOUT_NAV_ASSETS.profileActive, to: ROUTES.PROFILE, }, ]; +function LayoutSideNavIcon({ item }: LayoutSideNavIconProps) { + if (!item.activeIconSrc) { + return ; + } + + return ( + + ); +} + /** * 主布局(与路由结合):顶部栏 + 左侧栏 + 右侧内容区 * @@ -147,10 +199,8 @@ export function Layout({ children }: LayoutProps) { const [topbarServiceQrOpen, setTopbarServiceQrOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [promoBarVisible, setPromoBarVisible] = useState(() => !isLayoutPromoBarDismissed()); - const [aliceLiveClassOpen, setAliceLiveClassOpen] = useState(false); const activePromptPanelHost = useComposerStore((s) => s.activePromptPanelHost); - const footerRef = useRef(null); const footerPopoverRef = useRef(null); const userMenuRef = useRef(null); const languageToolRef = useRef(null); @@ -231,35 +281,17 @@ export function Layout({ children }: LayoutProps) { }, []); const [serviceQrCode, setServiceQrCode] = useState(""); - const [macIntelDownloadUrl, setMacIntelDownloadUrl] = useState(""); - const [macArmDownloadUrl, setMacArmDownloadUrl] = useState(""); - const [windowsDownloadUrl, setWindowsDownloadUrl] = useState(""); - const [appMacDownloadOpen, setAppMacDownloadOpen] = useState(false); useEffect(() => { void getParamConfig() .then((res) => { - setServiceQrCode(res.systemConfig?.serviceQrCode ?? ""); + setServiceQrCode(readServiceQrCode(res)); }) .catch(() => { setServiceQrCode(""); }); }, []); - useEffect(() => { - void getAppLatest() - .then((data) => { - setMacIntelDownloadUrl(data.macIntel?.url ?? ""); - setMacArmDownloadUrl(data.macArm?.url ?? ""); - setWindowsDownloadUrl(data.windowsX64?.url ?? ""); - }) - .catch(() => { - setMacIntelDownloadUrl(""); - setMacArmDownloadUrl(""); - setWindowsDownloadUrl(""); - }); - }, []); - useEffect(() => { if (pathname === ROUTES.EXPLORE && hasPendingExploreTemplateTabIntent()) { return; @@ -413,12 +445,12 @@ export function Layout({ children }: LayoutProps) { {t("layout.promo.seedanceSuffix")}

+ {t("layout.promo.badgeHot")}

{t("layout.promo.discountPrefix")} {t("layout.promo.discountValue")} {t("layout.promo.discountSuffix")}

- {t("layout.promo.badgeHot")}

{t("layout.promo.pointsPrefix")} @@ -430,82 +462,45 @@ export function Layout({ children }: LayoutProps) { ) : null} +

-
- {!effectiveSidebarCollapsed ? navigate(ROUTES.INDEX)} src={new URL("@/assets/images/popiart.png", import.meta.url).href} /> : null} - -
-
- -
e.stopPropagation()}> - {t("layout.topbar.aliceMeetupBook")} -

{t("layout.topbar.aliceMeetupBook")}

-
-
-
{ const url = buildSkillHubUrl(); if (url) window.open(url, "_blank"); }} > - - SKILL -
- {/* 联系客服:顶栏定位浮层展示二维码 */} - {serviceQrCode ? ( -
- - {topbarServiceQrOpen ? ( -
e.stopPropagation()}> - {t("contactService.qrAlt")} -

{t("layout.topbar.scan_contact_service")}

-
- ) : null} -
- ) : null} + + {t("layout.topbar.skill")} + {/* 使用说明 */}
@@ -550,16 +545,40 @@ export function Layout({ children }: LayoutProps) { } > -
+ {/* 联系客服:顶栏定位浮层展示二维码 */} +
+ + {topbarServiceQrOpen ? ( +
e.stopPropagation()}> + {t("contactService.qrAlt")} +

{t("layout.topbar.scan_contact_service")}

+
+ ) : null} +
{/* 用户积分 */}
openPointsDetailDialog()}> -
{String(user?.allCoins ?? 0)}
{t("layout.nav.points")}
@@ -660,82 +679,40 @@ export function Layout({ children }: LayoutProps) { ) : null} ) : ( - )}
- {aliceLiveClassOpen - ? createPortal( - setAliceLiveClassOpen(false)} />, - document.body, - ) - : null} - - {appMacDownloadOpen - ? createPortal( - setAppMacDownloadOpen(false)} - />, - document.body, - ) - : null} -
{/* 左侧栏菜单 */}