zlwu 10 maanden geleden
bovenliggende
commit
46cdfff0da

+ 319 - 0
frontend/public/privacy-policy.html

@@ -0,0 +1,319 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+    <!-- 全终端适配 viewport 配置 -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
+    <!-- iOS Safari 优化 -->
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+    <!-- Android Chrome 优化 -->
+    <meta name="mobile-web-app-capable" content="yes" />
+    <meta name="theme-color" content="#667eea" />
+    <!-- 禁止格式检测 -->
+    <meta name="format-detection" content="telephone=no, email=no, address=no" />
+    
+    <title>隐私政策 - Lyrria</title>
+    <style>
+      * {
+        margin: 0;
+        padding: 0;
+        box-sizing: border-box;
+      }
+      
+      body {
+        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
+        line-height: 1.6;
+        color: #333;
+        background-color: #f5f5f5;
+        padding: 20px;
+        padding-bottom: 40px;
+      }
+      
+      .container {
+        max-width: 800px;
+        margin: 0 auto;
+        background-color: #fff;
+        padding: 30px;
+        border-radius: 8px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      }
+      
+      h1 {
+        font-size: 24px;
+        color: #1890ff;
+        margin-bottom: 20px;
+        text-align: center;
+        padding-bottom: 15px;
+        border-bottom: 2px solid #1890ff;
+      }
+      
+      h2 {
+        font-size: 18px;
+        color: #333;
+        margin-top: 25px;
+        margin-bottom: 15px;
+        padding-left: 10px;
+        border-left: 4px solid #1890ff;
+      }
+      
+      p {
+        margin-bottom: 12px;
+        text-align: justify;
+        font-size: 14px;
+      }
+      
+      .update-date {
+        text-align: right;
+        color: #999;
+        font-size: 12px;
+        margin-bottom: 20px;
+      }
+      
+      .section {
+        margin-bottom: 25px;
+      }
+      
+      ul {
+        margin-left: 20px;
+        margin-bottom: 12px;
+      }
+      
+      li {
+        margin-bottom: 8px;
+        font-size: 14px;
+      }
+      
+      .highlight {
+        color: #1890ff;
+        font-weight: 500;
+      }
+      
+      table {
+        width: 100%;
+        border-collapse: collapse;
+        margin: 15px 0;
+        font-size: 14px;
+      }
+      
+      table th,
+      table td {
+        border: 1px solid #ddd;
+        padding: 10px;
+        text-align: left;
+      }
+      
+      table th {
+        background-color: #f5f5f5;
+        font-weight: 500;
+      }
+      
+      @media (max-width: 768px) {
+        body {
+          padding: 15px;
+        }
+        
+        .container {
+          padding: 20px;
+        }
+        
+        h1 {
+          font-size: 20px;
+        }
+        
+        h2 {
+          font-size: 16px;
+        }
+        
+        p, li {
+          font-size: 13px;
+        }
+        
+        table {
+          font-size: 12px;
+        }
+        
+        table th,
+        table td {
+          padding: 8px;
+        }
+      }
+    </style>
+  </head>
+  <body>
+    <div class="container">
+      <h1>隐私政策</h1>
+      <div class="update-date">最后更新时间:2024年12月</div>
+      
+      <div class="section">
+        <p>
+          Lyrria(以下简称"我们")非常重视您的隐私保护。本《隐私政策》(以下简称"本政策")说明了我们如何收集、使用、存储、分享和保护您的个人信息。请您仔细阅读本政策,在使用我们的服务时,即表示您已理解并同意本政策的内容。
+        </p>
+      </div>
+      
+      <div class="section">
+        <h2>一、信息收集</h2>
+        <p>1.1 我们可能收集以下类型的个人信息:</p>
+        <table>
+          <thead>
+            <tr>
+              <th>信息类型</th>
+              <th>收集内容</th>
+              <th>收集目的</th>
+            </tr>
+          </thead>
+          <tbody>
+            <tr>
+              <td>账户信息</td>
+              <td>手机号、邮箱、微信/QQ账号、昵称、头像</td>
+              <td>账户注册、登录、身份验证</td>
+            </tr>
+            <tr>
+              <td>语音数据</td>
+              <td>录音文件、语音识别结果</td>
+              <td>提供语音识别和对话服务</td>
+            </tr>
+            <tr>
+              <td>对话内容</td>
+              <td>对话记录、学习进度、统计数据</td>
+              <td>提供个性化服务、改进产品</td>
+            </tr>
+            <tr>
+              <td>设备信息</td>
+              <td>设备型号、操作系统、浏览器类型、IP地址</td>
+              <td>优化服务体验、安全防护</td>
+            </tr>
+            <tr>
+              <td>使用信息</td>
+              <td>访问时间、使用时长、功能使用情况</td>
+              <td>分析用户行为、改进产品</td>
+            </tr>
+          </tbody>
+        </table>
+        <p>1.2 我们会在您使用服务时自动收集部分信息,部分信息需要您主动提供。</p>
+        <p>1.3 我们不会收集与提供服务无关的个人信息。</p>
+      </div>
+      
+      <div class="section">
+        <h2>二、信息使用</h2>
+        <p>2.1 我们使用收集的信息用于以下目的:</p>
+        <ul>
+          <li>提供、维护和改进我们的服务</li>
+          <li>处理您的请求和交易</li>
+          <li>发送服务通知、更新和安全提醒</li>
+          <li>进行数据分析,优化用户体验</li>
+          <li>检测、预防和解决技术问题或安全问题</li>
+          <li>遵守法律法规要求</li>
+        </ul>
+        <p>2.2 我们不会将您的个人信息用于本政策未说明的其他目的,除非获得您的明确同意。</p>
+      </div>
+      
+      <div class="section">
+        <h2>三、信息存储</h2>
+        <p>3.1 我们会在中华人民共和国境内存储您的个人信息。</p>
+        <p>3.2 我们采用行业标准的安全措施保护您的个人信息,包括但不限于:</p>
+        <ul>
+          <li>数据加密传输和存储</li>
+          <li>访问权限控制</li>
+          <li>安全审计和监控</li>
+          <li>定期安全评估</li>
+        </ul>
+        <p>3.3 我们仅在为实现本政策所述目的所必需的期间内保留您的个人信息。</p>
+        <p>3.4 当您注销账户时,我们会在合理期限内删除您的个人信息,法律法规另有规定的除外。</p>
+      </div>
+      
+      <div class="section">
+        <h2>四、信息分享</h2>
+        <p>4.1 我们不会向第三方出售、出租或交易您的个人信息。</p>
+        <p>4.2 在以下情况下,我们可能会分享您的个人信息:</p>
+        <ul>
+          <li><span class="highlight">获得您的明确同意</span>:在您同意的情况下,我们可能会与第三方分享您的信息</li>
+          <li><span class="highlight">服务提供商</span>:我们可能与为我们提供服务的第三方(如云服务、数据分析服务)分享信息,但这些第三方必须遵守严格的保密义务</li>
+          <li><span class="highlight">法律法规要求</span>:根据法律法规、司法程序、政府要求等,我们可能需要披露您的信息</li>
+          <li><span class="highlight">业务转让</span>:如发生合并、收购或资产转让,您的信息可能会被转移</li>
+        </ul>
+        <p>4.3 我们与第三方分享信息时,会要求其采取相应的安全措施保护您的信息。</p>
+      </div>
+      
+      <div class="section">
+        <h2>五、第三方服务</h2>
+        <p>5.1 我们的服务可能包含第三方服务或链接,如:</p>
+        <ul>
+          <li>阿里云 DashScope(语音识别、AI对话、语音合成)</li>
+          <li>微信、QQ 登录服务</li>
+          <li>短信、邮件服务</li>
+        </ul>
+        <p>5.2 这些第三方服务有其独立的隐私政策,我们不对其隐私做法负责。</p>
+        <p>5.3 我们建议您仔细阅读这些第三方的隐私政策。</p>
+      </div>
+      
+      <div class="section">
+        <h2>六、Cookie 和类似技术</h2>
+        <p>6.1 我们可能使用 Cookie、本地存储等类似技术来改善您的使用体验。</p>
+        <p>6.2 这些技术帮助我们:</p>
+        <ul>
+          <li>记住您的登录状态和偏好设置</li>
+          <li>分析服务使用情况</li>
+          <li>提供个性化内容</li>
+        </ul>
+        <p>6.3 您可以通过浏览器设置管理 Cookie,但这可能影响部分功能的正常使用。</p>
+      </div>
+      
+      <div class="section">
+        <h2>七、您的权利</h2>
+        <p>根据相关法律法规,您对自己的个人信息享有以下权利:</p>
+        <ul>
+          <li><span class="highlight">访问权</span>:您可以查看我们收集的您的个人信息</li>
+          <li><span class="highlight">更正权</span>:您可以更正不准确或不完整的个人信息</li>
+          <li><span class="highlight">删除权</span>:您可以要求我们删除您的个人信息</li>
+          <li><span class="highlight">撤回同意</span>:您可以撤回之前给予的同意</li>
+          <li><span class="highlight">限制处理</span>:您可以要求我们限制对您个人信息的处理</li>
+          <li><span class="highlight">数据可携权</span>:您可以要求我们以结构化、常用格式提供您的个人信息</li>
+        </ul>
+        <p>7.2 您可以通过账户设置或联系客服行使上述权利。</p>
+        <p>7.3 我们会在合理期限内响应您的请求。</p>
+      </div>
+      
+      <div class="section">
+        <h2>八、未成年人保护</h2>
+        <p>8.1 我们非常重视未成年人的个人信息保护。</p>
+        <p>8.2 如果您是未满 18 周岁的未成年人,请在监护人同意和指导下使用我们的服务。</p>
+        <p>8.3 如果我们发现自己在未事先获得可证实的监护人同意的情况下收集了未成年人的个人信息,我们会设法尽快删除相关数据。</p>
+      </div>
+      
+      <div class="section">
+        <h2>九、隐私政策更新</h2>
+        <p>9.1 我们可能会不时更新本隐私政策。</p>
+        <p>9.2 更新后的隐私政策会在本页面公布,并更新"最后更新时间"。</p>
+        <p>9.3 重大变更时,我们可能会通过显著方式(如弹窗、邮件等)通知您。</p>
+        <p>9.4 继续使用我们的服务即表示您接受更新后的隐私政策。</p>
+      </div>
+      
+      <div class="section">
+        <h2>十、联系我们</h2>
+        <p>10.1 如您对本隐私政策有任何疑问、意见或建议,或需要行使相关权利,请通过以下方式联系我们:</p>
+        <ul>
+          <li>客服邮箱:support@lyrria.com</li>
+          <li>客服电话:400-XXX-XXXX</li>
+          <li>在线客服:通过应用内客服功能联系我们</li>
+        </ul>
+        <p>10.2 我们会在收到您的请求后,在合理期限内予以回复。</p>
+      </div>
+      
+      <div class="section">
+        <h2>十一、其他</h2>
+        <p>11.1 本隐私政策的解释权归 Lyrria 所有。</p>
+        <p>11.2 本隐私政策受中华人民共和国法律管辖。</p>
+        <p>11.3 如本隐私政策与《用户使用协议》存在冲突,以本隐私政策为准。</p>
+      </div>
+      
+      <div class="section">
+        <p style="text-align: center; margin-top: 30px; color: #999; font-size: 12px;">
+          © 2024 Lyrria. All rights reserved.
+        </p>
+      </div>
+    </div>
+  </body>
+</html>
+

+ 226 - 0
frontend/public/user-agreement.html

@@ -0,0 +1,226 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+    <!-- 全终端适配 viewport 配置 -->
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
+    <!-- iOS Safari 优化 -->
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
+    <!-- Android Chrome 优化 -->
+    <meta name="mobile-web-app-capable" content="yes" />
+    <meta name="theme-color" content="#667eea" />
+    <!-- 禁止格式检测 -->
+    <meta name="format-detection" content="telephone=no, email=no, address=no" />
+    
+    <title>用户使用协议 - Lyrria</title>
+    <style>
+      * {
+        margin: 0;
+        padding: 0;
+        box-sizing: border-box;
+      }
+      
+      body {
+        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
+        line-height: 1.6;
+        color: #333;
+        background-color: #f5f5f5;
+        padding: 20px;
+        padding-bottom: 40px;
+      }
+      
+      .container {
+        max-width: 800px;
+        margin: 0 auto;
+        background-color: #fff;
+        padding: 30px;
+        border-radius: 8px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+      }
+      
+      h1 {
+        font-size: 24px;
+        color: #1890ff;
+        margin-bottom: 20px;
+        text-align: center;
+        padding-bottom: 15px;
+        border-bottom: 2px solid #1890ff;
+      }
+      
+      h2 {
+        font-size: 18px;
+        color: #333;
+        margin-top: 25px;
+        margin-bottom: 15px;
+        padding-left: 10px;
+        border-left: 4px solid #1890ff;
+      }
+      
+      p {
+        margin-bottom: 12px;
+        text-align: justify;
+        font-size: 14px;
+      }
+      
+      .update-date {
+        text-align: right;
+        color: #999;
+        font-size: 12px;
+        margin-bottom: 20px;
+      }
+      
+      .section {
+        margin-bottom: 25px;
+      }
+      
+      ul {
+        margin-left: 20px;
+        margin-bottom: 12px;
+      }
+      
+      li {
+        margin-bottom: 8px;
+        font-size: 14px;
+      }
+      
+      .highlight {
+        color: #1890ff;
+        font-weight: 500;
+      }
+      
+      @media (max-width: 768px) {
+        body {
+          padding: 15px;
+        }
+        
+        .container {
+          padding: 20px;
+        }
+        
+        h1 {
+          font-size: 20px;
+        }
+        
+        h2 {
+          font-size: 16px;
+        }
+        
+        p, li {
+          font-size: 13px;
+        }
+      }
+    </style>
+  </head>
+  <body>
+    <div class="container">
+      <h1>用户使用协议</h1>
+      <div class="update-date">最后更新时间:2024年12月</div>
+      
+      <div class="section">
+        <p>
+          欢迎使用 Lyrria(以下简称"本服务"或"我们")。在使用本服务之前,请您仔细阅读本《用户使用协议》(以下简称"本协议")。当您注册、登录、使用本服务时,即表示您已阅读、理解并同意接受本协议的全部内容。如您不同意本协议的任何内容,请立即停止使用本服务。
+        </p>
+      </div>
+      
+      <div class="section">
+        <h2>一、协议范围</h2>
+        <p>1.1 本协议是您与 Lyrria 之间关于使用本服务的法律协议。</p>
+        <p>1.2 本协议描述您在使用本服务时享有的权利和应承担的义务。</p>
+        <p>1.3 本协议包括但不限于本协议正文、隐私政策、以及我们可能不时发布的其他规则、声明、说明等。</p>
+      </div>
+      
+      <div class="section">
+        <h2>二、服务说明</h2>
+        <p>2.1 Lyrria 是一款基于人工智能技术的英语语音对话助手,旨在帮助用户提升英语口语能力。</p>
+        <p>2.2 我们提供的服务包括但不限于:</p>
+        <ul>
+          <li>实时语音识别与转写</li>
+          <li>AI 智能对话与反馈</li>
+          <li>语音合成与播放</li>
+          <li>学习进度记录与统计</li>
+        </ul>
+        <p>2.3 我们有权根据实际情况调整、变更或终止部分或全部服务内容,并会提前通知用户。</p>
+      </div>
+      
+      <div class="section">
+        <h2>三、用户账户</h2>
+        <p>3.1 您可以通过手机号、邮箱、微信、QQ 等方式注册账户。</p>
+        <p>3.2 您应当保证注册信息的真实性、准确性和完整性,并承担因信息不实导致的一切后果。</p>
+        <p>3.3 您有责任妥善保管账户信息及密码,对账户下的所有行为负责。</p>
+        <p>3.4 如发现账户被盗用或存在安全风险,请立即通知我们。</p>
+      </div>
+      
+      <div class="section">
+        <h2>四、使用规范</h2>
+        <p>4.1 您在使用本服务时,应当遵守相关法律法规,不得利用本服务从事违法违规活动。</p>
+        <p>4.2 您不得从事以下行为:</p>
+        <ul>
+          <li>发布、传播违法、有害、低俗、暴力、恐怖等不良信息</li>
+          <li>侵犯他人知识产权、隐私权、名誉权等合法权益</li>
+          <li>干扰、破坏本服务的正常运行或安全防护措施</li>
+          <li>使用自动化工具、脚本等方式批量操作或恶意刷量</li>
+          <li>将本服务用于商业用途或未经授权的第三方服务</li>
+          <li>其他违反法律法规或本协议的行为</li>
+        </ul>
+        <p>4.3 如您违反上述规定,我们有权立即终止向您提供服务,并保留追究法律责任的权利。</p>
+      </div>
+      
+      <div class="section">
+        <h2>五、知识产权</h2>
+        <p>5.1 本服务的所有知识产权,包括但不限于商标、专利、著作权、商业秘密等,均归我们所有。</p>
+        <p>5.2 您在使用本服务过程中产生的对话内容、学习记录等数据,其知识产权归您所有,但我们有权在提供服务过程中使用、存储这些数据。</p>
+        <p>5.3 未经我们书面许可,您不得复制、传播、展示、镜像、上传、下载本服务的任何内容。</p>
+      </div>
+      
+      <div class="section">
+        <h2>六、隐私保护</h2>
+        <p>6.1 我们高度重视您的隐私保护,具体内容请参见《隐私政策》。</p>
+        <p>6.2 您在使用本服务时,我们可能会收集、使用您的个人信息,包括但不限于:账户信息、语音数据、对话内容、设备信息等。</p>
+        <p>6.3 我们承诺按照法律法规要求和《隐私政策》的约定,保护您的个人信息安全。</p>
+      </div>
+      
+      <div class="section">
+        <h2>七、服务变更与终止</h2>
+        <p>7.1 我们有权根据业务发展需要,调整、变更或终止部分或全部服务。</p>
+        <p>7.2 如因不可抗力、技术故障等原因导致服务中断或终止,我们不承担责任,但会尽力减少对您的影响。</p>
+        <p>7.3 您有权随时停止使用本服务,但已产生的费用或数据不予退还。</p>
+      </div>
+      
+      <div class="section">
+        <h2>八、免责声明</h2>
+        <p>8.1 本服务基于人工智能技术,可能存在识别错误、理解偏差等情况,我们不对服务结果的准确性、完整性作任何保证。</p>
+        <p>8.2 因网络状况、设备故障、不可抗力等因素导致的服务中断、数据丢失等,我们不承担责任。</p>
+        <p>8.3 您因使用本服务产生的任何损失,包括但不限于直接损失、间接损失、利润损失等,我们不承担责任。</p>
+      </div>
+      
+      <div class="section">
+        <h2>九、协议修改</h2>
+        <p>9.1 我们有权根据法律法规变化、业务发展需要等,随时修改本协议。</p>
+        <p>9.2 协议修改后,我们会在服务页面公布修改内容,修改后的协议自公布之日起生效。</p>
+        <p>9.3 如您不同意修改后的协议,请停止使用本服务;继续使用视为接受修改后的协议。</p>
+      </div>
+      
+      <div class="section">
+        <h2>十、法律适用与争议解决</h2>
+        <p>10.1 本协议的订立、生效、解释、执行和争议解决均适用中华人民共和国法律。</p>
+        <p>10.2 如因本协议产生任何争议,双方应友好协商解决;协商不成的,任何一方均可向我们所在地有管辖权的人民法院提起诉讼。</p>
+      </div>
+      
+      <div class="section">
+        <h2>十一、其他</h2>
+        <p>11.1 如本协议的任何条款被认定为无效或不可执行,不影响其他条款的效力。</p>
+        <p>11.2 本协议的标题仅为方便阅读而设,不影响协议内容的解释。</p>
+        <p>11.3 如您对本协议有任何疑问,可通过客服渠道联系我们。</p>
+      </div>
+      
+      <div class="section">
+        <p style="text-align: center; margin-top: 30px; color: #999; font-size: 12px;">
+          © 2024 Lyrria. All rights reserved.
+        </p>
+      </div>
+    </div>
+  </body>
+</html>
+

+ 54 - 58
frontend/src/pages/BeginPage.css

@@ -28,21 +28,6 @@
   padding-bottom: env(safe-area-inset-bottom);
 }
 
-/* ========================================
-   背景层 - 全屏覆盖,消除空白
-   ======================================== */
-.welcome-bg {
-  /* position: absolute;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background-size: cover;
-  background-position: center;
-  background-repeat: no-repeat;
-  background-image: url('https://lyrria-pro.oss-cn-beijing.aliyuncs.com/chatbot-bg.svg');
-  z-index: 0; */
-}
 
 /* ========================================
    内容容器 - 基于 vw/vh 响应式适配
@@ -68,14 +53,17 @@
    ======================================== */
 .welcome-lumi {
   position: absolute;
-  /* 纵向位置:按高度 812px 等比缩放 */
-  top: 22.95vh;  /* 186.38 / 812 × 100 = 22.95vh */
-  /* 高度:按设计稿 520px 高度等比缩放 */
-  height: 64.04vh; /* 520 / 812 × 100 = 64.04vh */
-  /* 宽度自动,由图片比例决定,不直接跟随 vw 缩放 */
-  width: 100%;
-  /* 横向位置:仍按设计稿等比缩放,宽度封顶 600px 时不再继续偏移 */
-  /* left: calc(-133PX * min(100vw, 600PX) / 375PX); */
+  /* 纵向位置:基于 vw/vh 最小值,设计稿 185px */
+  /* vw: 185 / 375 × 100 = 49.33vw, vh: 185 / 812 × 100 = 22.78vh */
+  top: min(49.33vw, 22.78vh);
+  /* 横向位置:设置为 -10% */
+  left: -10%;
+  /* 宽度:基于 vw/vh 最小值,设计稿 423px */
+  /* vw: 423 / 375 × 100 = 112.8vw, vh: 423 / 812 × 100 = 52.09vh */
+  width: min(112.8vw, 52.09vh);
+  /* 高度:基于 vw/vh 最小值,设计稿 423px */
+  /* vw: 423 / 375 × 100 = 112.8vw, vh: 423 / 812 × 100 = 52.09vh */
+  height: min(112.8vw, 52.09vh);
   object-fit: contain;
 }
 
@@ -85,7 +73,9 @@
    ======================================== */
 .welcome-hello {
   position: absolute;
-  top: 17vh;    /* 138 / 812 × 100 = 17vh */
+  /* 纵向位置:基于 vw/vh 最小值,设计稿 138px */
+  /* vw: 138 / 375 × 100 = 36.8vw, vh: 138 / 812 × 100 = 17vh */
+  top: min(36.8vw, 17vh);
   /* 横向位置与宽度:
      - 视口宽度 ≤ 600px 时:按 375 设计稿等比缩放
      - 视口宽度 > 600px 时:按 600px 视口封顶,不再继续放大 */
@@ -120,20 +110,8 @@
   text-align: center;
   text-transform: capitalize;
   
-  /* 交互 */
-  cursor: pointer;
-  transition: opacity 0.2s ease, transform 0.2s ease;
+  /* 移除交互样式 */
   user-select: none;
-  -webkit-tap-highlight-color: transparent;
-  touch-action: manipulation;
-}
-
-.welcome-hello:hover {
-  opacity: 0.9;
-}
-
-.welcome-hello:active {
-  opacity: 0.8;
 }
 
 /* ========================================
@@ -407,43 +385,61 @@ video::-webkit-media-controls-overlay-play-button {
 }
 
 /* ========================================
-   协议文字 - 基于 vh 的响应式布局
-   设计稿: 812px 高度基准,top: 700px
+   协议文字 - 基于 vw/vh 最小值的响应式布局
+   设计稿: 375×812,top: 708px
    ======================================== */
 .agreement-container {
   position: absolute;
-  left: auto;
-  right: auto;
-  /* 改为使用 bottom + 安全区,避免被浏览器底栏遮挡 */
-  bottom: calc(env(safe-area-inset-bottom, 0px) + 24PX);
+  /* 左右居中 */
+  left: 50%;
+  transform: translateX(-50%);
+  /* 纵向位置:基于 vw/vh 最小值,设计稿 708px */
+  /* vw: 708 / 375 × 100 = 188.8vw, vh: 708 / 812 × 100 = 87.19vh */
+  top: min(188.8vw, 87.19vh);
   display: flex;
-  justify-content: flex-start;
+  justify-content: center;
   align-items: center;
   padding: 0;
   z-index: 2;
 }
 
-.agreement-login-tip {
+/* 登录按钮图片 - 已有账号 */
+.login-button-image {
   position: absolute;
-  left: auto;
-  right: auto;
-  bottom: calc(env(safe-area-inset-bottom, 0px) + 56PX); /* 在协议文字上方留出间距 */
-  display: inline-flex;
-  justify-content: flex-start;
-  align-items: center;
-  gap: 8PX;
-  font-size: 12PX; /* 与 .agreement-text 一致 */
-  line-height: 1.5;
-  color: rgba(245, 243, 240, 1); /* 白色文字,与协议文字一致 */
-  user-select: none;
+  /* 纵向位置:基于 vw/vh 最小值,设计稿 636px */
+  /* vw: 636 / 375 × 100 = 169.6vw, vh: 636 / 812 × 100 = 78.33vh */
+  top: min(169.6vw, 78.33vh);
+  height: auto;
   cursor: pointer;
+  user-select: none;
+  -webkit-tap-highlight-color: transparent;
   transition: opacity 0.2s ease;
+  z-index: 2;
+  object-fit: contain;
+}
+
+.login-button-image:active,
+.login-button-image:hover {
+  opacity: 0.8;
+}
+
+/* 命名按钮图片 - 首次访问 */
+.naming-button-image {
+  position: absolute;
+  /* 纵向位置:基于 vw/vh 最小值,设计稿 568px */
+  /* vw: 568 / 375 × 100 = 151.47vw, vh: 568 / 812 × 100 = 69.95vh */
+  top: min(151.47vw, 69.95vh);
+  height: auto;
+  cursor: pointer;
+  user-select: none;
   -webkit-tap-highlight-color: transparent;
+  transition: opacity 0.2s ease;
   z-index: 2;
+  object-fit: contain;
 }
 
-.agreement-login-tip:active,
-.agreement-login-tip:hover {
+.naming-button-image:active,
+.naming-button-image:hover {
   opacity: 0.8;
 }
 

+ 37 - 36
frontend/src/pages/BeginPage.tsx

@@ -27,7 +27,6 @@ export const BeginPage = () => {
   const [viewState, setViewState] = useState<WelcomeViewState>('welcome');
   const [isLoading, setIsLoading] = useState(false);
   const [agreementChecked, setAgreementChecked] = useState(true);
-  const [loginTipChecked, setLoginTipChecked] = useState(false);
   const isVideoReadyRef = useRef(false); // 视频是否已加载完成
   const loadingStartTimeRef = useRef<number | null>(null); // 加载开始时间
   const userInteractionRef = useRef(false); // 用户是否已交互(用于确保声音播放)
@@ -598,10 +597,6 @@ export const BeginPage = () => {
     }
   }, [navigate, viewState]); // 当播放状态改变时,确保事件已绑定
 
-  const handleHelloClick = async () => {
-    // 直接跳转到 /naming,不再播放视频或等待加载
-    navigate('/naming');
-  };
 
   return (
     <div className="welcome-page">
@@ -618,48 +613,54 @@ export const BeginPage = () => {
             className="welcome-lumi"
           />
           
-          {/* Hello 文字 - 可点击 */}
-          <div 
-            className="welcome-hello"
-            onClick={handleHelloClick}
-          >
+          {/* Hello 文字 */}
+          <div className="welcome-hello">
             Hello,
           </div>
           
-          {/* 已有账号提示 + 协议文字 */}
-          <div
-            className="agreement-login-tip"
+          {/* 已有账号图片按钮 - 跳转到 /login */}
+          <img
+            src="https://lyrria-pro.oss-cn-beijing.aliyuncs.com/have-an-account.png"
+            alt="已有账号"
+            className="login-button-image"
+            style={{
+              width: px2vmin(279),
+              left: '50%',
+              transform: 'translateX(-50%)',
+            }}
+            onClick={() => navigate('/login')}
             role="button"
             tabIndex={0}
-            style={{ left: px2vmin(50), right: 'auto' }}
-            onClick={() => {
-              setLoginTipChecked((prev) => {
-                const next = !prev;
-                if (next) {
-                  navigate('/login');
-                }
-                return next;
-              });
+            onKeyDown={(event) => {
+              if (event.key === 'Enter' || event.key === ' ') {
+                event.preventDefault();
+                navigate('/login');
+              }
             }}
+          />
+          
+          {/* 首次访问图片按钮 - 跳转到 /naming */}
+          <img
+            src="https://lyrria-pro.oss-cn-beijing.aliyuncs.com/fitst-time-here.png"
+            alt="首次访问"
+            className="naming-button-image"
+            style={{
+              width: px2vmin(279),
+              left: '50%',
+              transform: 'translateX(-50%)',
+            }}
+            onClick={() => navigate('/naming')}
+            role="button"
+            tabIndex={0}
             onKeyDown={(event) => {
               if (event.key === 'Enter' || event.key === ' ') {
                 event.preventDefault();
-                setLoginTipChecked((prev) => {
-                  const next = !prev;
-                  if (next) {
-                    navigate('/login');
-                  }
-                  return next;
-                });
+                navigate('/naming');
               }
             }}
-          >
-            <div className={`agreement-checkbox ${loginTipChecked ? 'checked' : ''}`} />
-            <div className="agreement-text">我已经完成注册,登陆继续和Lumi聊天</div>
-          </div>
+          />
           <div
             className="agreement-container"
-            style={{ left: px2vmin(50), right: 'auto' }}
           >
             <div className="agreement-content">
               <div
@@ -668,11 +669,11 @@ export const BeginPage = () => {
               />
               <div className="agreement-text">
                 我已阅读并同意
-                <span className="agreement-link" onClick={() => window.open('/terms', '_blank')}>
+                <span className="agreement-link" onClick={() => window.open('/user-agreement.html', '_blank')}>
                   《使用协议》
                 </span>
                 和
-                <span className="agreement-link" onClick={() => window.open('/privacy', '_blank')}>
+                <span className="agreement-link" onClick={() => window.open('/privacy-policy.html', '_blank')}>
                   《隐私政策》
                 </span>
               </div>

+ 283 - 82
frontend/src/pages/ProfilePage.tsx

@@ -53,10 +53,17 @@ export const ProfilePage = () => {
   const [showBotNameModal, setShowBotNameModal] = useState<boolean>(false);
   const [tempUsername, setTempUsername] = useState<string>('');
   const [tempBotName, setTempBotName] = useState<string>('');
+  const [isUsernameFocused, setIsUsernameFocused] = useState<boolean>(false);
+  const [isBotNameFocused, setIsBotNameFocused] = useState<boolean>(false);
   const usernameInputRef = useRef<HTMLInputElement>(null);
   const botNameInputRef = useRef<HTMLInputElement>(null);
   const [showEnergyStoneModal, setShowEnergyStoneModal] = useState<boolean>(false);
   const [energyStoneModalType, setEnergyStoneModalType] = useState<'mission' | 'conversation'>('mission');
+  
+  const cancelBtnImage = 'https://lyrria-pro.oss-cn-beijing.aliyuncs.com/cancel.png';
+  const changeBtnImage = 'https://lyrria-pro.oss-cn-beijing.aliyuncs.com/change.png';
+  const changeBtnActiveImage = 'https://lyrria-pro.oss-cn-beijing.aliyuncs.com/change-a.png';
+  const focusGradient = 'linear-gradient(90deg, #FFC900 11%, #13A6FF 32%, #806EFF 50%, #FF8EEC 66%, #FE4F51 89%)';
 
   // 获取能量石数量
   const fetchEnergyStoneCounts = useCallback(async (userId: string) => {
@@ -229,6 +236,9 @@ export const ProfilePage = () => {
     }, 0);
   }, [username]);
 
+  // 检查用户名是否改变
+  const isUsernameChanged = tempUsername.trim() !== username.trim();
+
   // 保存用户名
   const handleSaveUsername = useCallback(async () => {
     const userId = getUserId();
@@ -264,6 +274,9 @@ export const ProfilePage = () => {
     }, 0);
   }, [botName]);
 
+  // 检查 Lumi 名字是否改变
+  const isBotNameChanged = tempBotName.trim() !== botName.trim();
+
   // 保存 Lumi 名字
   const handleSaveBotName = useCallback(async () => {
     const userId = getUserId();
@@ -360,6 +373,20 @@ export const ProfilePage = () => {
   }
 
   return (
+    <>
+      <style>{`
+        @keyframes fadeIn {
+          from {
+            opacity: 0;
+          }
+          to {
+            opacity: 1;
+          }
+        }
+        .profile-modal-input::placeholder {
+          color: rgba(245, 243, 240, 0.45);
+        }
+      `}</style>
     <div
       style={{
         width: '100vw',
@@ -882,6 +909,7 @@ export const ProfilePage = () => {
 
         {/* 使用协议 */}
         <div
+          onClick={() => window.open('/user-agreement.html', '_blank')}
           style={{
             display: 'flex',
             alignItems: 'center',
@@ -890,6 +918,7 @@ export const ProfilePage = () => {
             borderTop: '1px solid #34313D',
             marginLeft: px2vmin(10),
             marginRight: px2vmin(10),
+            cursor: 'pointer',
           }}
         >
           <span
@@ -914,6 +943,7 @@ export const ProfilePage = () => {
 
         {/* 隐私政策 */}
         <div
+          onClick={() => window.open('/privacy-policy.html', '_blank')}
           style={{
             display: 'flex',
             alignItems: 'center',
@@ -922,6 +952,7 @@ export const ProfilePage = () => {
             borderTop: '1px solid #34313D',
             marginLeft: px2vmin(10),
             marginRight: px2vmin(10),
+            cursor: 'pointer',
           }}
         >
           <span
@@ -959,6 +990,7 @@ export const ProfilePage = () => {
               bottom: 0,
               backgroundColor: 'rgba(0, 0, 0, 0.5)',
               zIndex: 9998,
+              animation: 'fadeIn 0.3s ease-out',
             }}
           />
 
@@ -966,37 +998,77 @@ export const ProfilePage = () => {
           <div
             style={{
               position: 'fixed',
-              top: '50%',
+              bottom: 0,
               left: '50%',
-              transform: 'translate(-50%, -50%)',
-              width: px2vw(343),
+              transform: 'translateX(-50%)',
+              width: px2vw(375),
               backgroundColor: '#34313D',
-              borderRadius: px2vmin(28),
+              borderTopLeftRadius: px2vmin(32),
+              borderTopRightRadius: px2vmin(32),
               zIndex: 9999,
-              padding: px2vmin(32),
               display: 'flex',
               flexDirection: 'column',
-              gap: px2vmin(24),
+              alignItems: 'center',
+              paddingTop: px2vmin(12),
+              paddingBottom: px2vmin(32),
+              paddingLeft: px2vmin(32),
+              paddingRight: px2vmin(32),
             }}
           >
-            {/* 标题 */}
+            {/* 顶部拖拽指示条 */}
             <div
               style={{
-                fontSize: px2vmin(20),
+                width: px2vmin(32),
+                height: px2vmin(4),
+                backgroundColor: '#79747E',
+                borderRadius: px2vmin(4),
+              }}
+            />
+
+            {/* 文字标签 */}
+            <div
+              style={{
+                width: '100%',
+                fontSize: px2vmin(14),
                 color: '#fff',
-                fontWeight: 600,
-                textAlign: 'center',
+                fontWeight: 'bold',
+                marginTop: px2vmin(24),
+                marginBottom: px2vmin(16),
+                textAlign: 'left',
               }}
             >
-              编辑用户名
+              Change my name
             </div>
 
-            {/* 输入框 */}
+            {/* 输入框容器 */}
+            <div
+              style={{
+                width: '100%',
+                position: 'relative',
+                marginBottom: px2vmin(32),
+              }}
+            >
+              <div
+                style={{
+                  width: '100%',
+                  height: px2vmin(56),
+                  background: 'linear-gradient(180deg, #26232F 0%, #18151F 100%)',
+                  borderTopLeftRadius: '12PX',
+                  borderTopRightRadius: '12PX',
+                  borderBottomRightRadius: '2PX',
+                  borderBottomLeftRadius: '2PX',
+                  boxShadow:
+                    '0PX 1PX 0PX 0PX rgba(255, 255, 255, 0.18), inset 0PX 0PX 0PX 1PX rgba(0, 0, 0, 0.65), inset 0PX 4PX 8PX rgba(0, 0, 0, 0.55)',
+                  position: 'relative',
+                }}
+              >
             <input
               ref={usernameInputRef}
               type="text"
               value={tempUsername}
               onChange={(e) => setTempUsername(e.target.value)}
+                  onFocus={() => setIsUsernameFocused(true)}
+                  onBlur={() => setIsUsernameFocused(false)}
               onKeyDown={(e) => {
                 if (e.key === 'Enter') {
                   handleSaveUsername();
@@ -1005,58 +1077,102 @@ export const ProfilePage = () => {
                 }
               }}
               placeholder="请输入用户名"
+                  className="profile-modal-input"
               style={{
                 width: '100%',
-                fontSize: px2vmin(16),
-                color: '#fff',
-                backgroundColor: 'rgba(255, 255, 255, 0.1)',
-                border: '1px solid rgba(255, 255, 255, 0.3)',
-                borderRadius: px2vmin(12),
-                padding: px2vmin(16),
+                    height: '100%',
+                    fontSize: px2vmin(18),
+                    color: 'rgba(245,243,240,0.7)',
+                    backgroundColor: 'transparent',
+                    border: 'none',
                 outline: 'none',
+                    paddingLeft: px2vw(24),
+                    paddingRight: px2vw(48),
+                    fontFamily: 'inherit',
+                    fontWeight: 'normal',
+                    letterSpacing: '0.5px',
               }}
             />
+                {isUsernameFocused && tempUsername && (
+                  <button
+                    onClick={() => setTempUsername('')}
+                    style={{
+                      position: 'absolute',
+                      top: '50%',
+                      right: px2vw(12),
+                      transform: 'translateY(-50%)',
+                      width: px2vw(20),
+                      height: px2vw(20),
+                      background: 'transparent',
+                      border: 'none',
+                      cursor: 'pointer',
+                      display: 'flex',
+                      alignItems: 'center',
+                      justifyContent: 'center',
+                    }}
+                  >
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
+                      <circle cx="12" cy="12" r="11" stroke="rgba(191, 157, 248, 1)" strokeWidth="2.5" fill="none" />
+                      <path
+                        d="M8 8L16 16M16 8L8 16"
+                        stroke="rgba(191, 157, 248, 1)"
+                        strokeWidth="2.5"
+                        strokeLinecap="round"
+                      />
+                    </svg>
+                  </button>
+                )}
+              </div>
+              {/* 底部渐变条 */}
+              <div
+                style={{
+                  width: '100%',
+                  height: '2PX',
+                  background: isUsernameFocused ? focusGradient : 'rgba(103, 80, 164, 1)',
+                  transition: 'background 0.3s',
+                }}
+              />
+            </div>
 
             {/* 按钮组 */}
             <div
               style={{
                 display: 'flex',
-                gap: px2vmin(16),
-                justifyContent: 'flex-end',
+                gap: px2vmin(43),
+                justifyContent: 'center',
+                width: '100%',
               }}
             >
               <button
                 onClick={handleCloseUsernameModal}
                 style={{
-                  fontSize: px2vmin(16),
-                  color: '#fff',
-                  backgroundColor: 'rgba(255, 255, 255, 0.1)',
+                  width: px2vmin(90),
+                  height: px2vmin(48),
+                  backgroundImage: `url(${cancelBtnImage})`,
+                  backgroundSize: 'cover',
+                  backgroundRepeat: 'no-repeat',
+                  backgroundPosition: 'center',
                   border: 'none',
-                  padding: px2vmin(12),
-                  paddingLeft: px2vmin(24),
-                  paddingRight: px2vmin(24),
                   cursor: 'pointer',
-                  borderRadius: px2vmin(8),
+                  padding: 0,
                 }}
-              >
-                取消
-              </button>
+              />
               <button
                 onClick={handleSaveUsername}
                 style={{
-                  fontSize: px2vmin(16),
-                  color: '#fff',
-                  backgroundColor: 'rgb(153, 97, 248)',
+                  width: px2vmin(94),
+                  height: px2vmin(48),
+                  backgroundImage: `url(${isUsernameChanged ? changeBtnActiveImage : changeBtnImage})`,
+                  backgroundSize: 'cover',
+                  backgroundRepeat: 'no-repeat',
+                  backgroundPosition: 'center',
                   border: 'none',
-                  padding: px2vmin(12),
-                  paddingLeft: px2vmin(24),
-                  paddingRight: px2vmin(24),
-                  cursor: 'pointer',
-                  borderRadius: px2vmin(8),
+                  cursor: isUsernameChanged ? 'pointer' : 'default',
+                  padding: 0,
+                  opacity: isUsernameChanged ? 1 : 0.6,
                 }}
-              >
-                保存
-              </button>
+                disabled={!isUsernameChanged}
+              />
             </div>
           </div>
         </>
@@ -1076,6 +1192,7 @@ export const ProfilePage = () => {
               bottom: 0,
               backgroundColor: 'rgba(0, 0, 0, 0.5)',
               zIndex: 9998,
+              animation: 'fadeIn 0.3s ease-out',
             }}
           />
 
@@ -1083,37 +1200,76 @@ export const ProfilePage = () => {
           <div
             style={{
               position: 'fixed',
-              top: '50%',
+              bottom: 0,
               left: '50%',
-              transform: 'translate(-50%, -50%)',
-              width: px2vw(343),
+              transform: 'translateX(-50%)',
+              width: px2vw(375),
               backgroundColor: '#34313D',
-              borderRadius: px2vmin(28),
+              borderTopLeftRadius: px2vmin(32),
+              borderTopRightRadius: px2vmin(32),
               zIndex: 9999,
-              padding: px2vmin(32),
               display: 'flex',
               flexDirection: 'column',
-              gap: px2vmin(24),
+              alignItems: 'center',
+              paddingTop: px2vmin(12),
+              paddingBottom: px2vmin(32),
+              paddingLeft: px2vmin(32),
+              paddingRight: px2vmin(32),
             }}
           >
-            {/* 标题 */}
+            {/* 顶部拖拽指示条 */}
+            <div
+              style={{
+                width: px2vmin(32),
+                height: px2vmin(4),
+                backgroundColor: '#79747E',
+                borderRadius: px2vmin(4),
+              }}
+            />
+
+            {/* 文字标签 */}
             <div
               style={{
-                fontSize: px2vmin(20),
+                width: '100%',
+                fontSize: px2vmin(14),
                 color: '#fff',
-                fontWeight: 600,
-                textAlign: 'center',
+                marginTop: px2vmin(24),
+                marginBottom: px2vmin(16),
+                textAlign: 'left',
               }}
             >
-              编辑 Lumi 名字
+              Change Lumi's name
             </div>
 
-            {/* 输入框 */}
+            {/* 输入框容器 */}
+            <div
+              style={{
+                width: '100%',
+                position: 'relative',
+                marginBottom: px2vmin(32),
+              }}
+            >
+              <div
+                style={{
+                  width: '100%',
+                  height: px2vmin(56),
+                  background: 'linear-gradient(180deg, #26232F 0%, #18151F 100%)',
+                  borderTopLeftRadius: '12PX',
+                  borderTopRightRadius: '12PX',
+                  borderBottomRightRadius: '2PX',
+                  borderBottomLeftRadius: '2PX',
+                  boxShadow:
+                    '0PX 1PX 0PX 0PX rgba(255, 255, 255, 0.18), inset 0PX 0PX 0PX 1PX rgba(0, 0, 0, 0.65), inset 0PX 4PX 8PX rgba(0, 0, 0, 0.55)',
+                  position: 'relative',
+                }}
+              >
             <input
               ref={botNameInputRef}
               type="text"
               value={tempBotName}
               onChange={(e) => setTempBotName(e.target.value)}
+                  onFocus={() => setIsBotNameFocused(true)}
+                  onBlur={() => setIsBotNameFocused(false)}
               onKeyDown={(e) => {
                 if (e.key === 'Enter') {
                   handleSaveBotName();
@@ -1122,58 +1278,102 @@ export const ProfilePage = () => {
                 }
               }}
               placeholder="请输入 Lumi 名字"
+                  className="profile-modal-input"
               style={{
                 width: '100%',
-                fontSize: px2vmin(16),
-                color: '#fff',
-                backgroundColor: 'rgba(255, 255, 255, 0.1)',
-                border: '1px solid rgba(255, 255, 255, 0.3)',
-                borderRadius: px2vmin(12),
-                padding: px2vmin(16),
+                    height: '100%',
+                    fontSize: px2vmin(18),
+                    color: 'rgba(245,243,240,0.7)',
+                    backgroundColor: 'transparent',
+                    border: 'none',
                 outline: 'none',
+                    paddingLeft: px2vw(24),
+                    paddingRight: px2vw(48),
+                    fontFamily: 'inherit',
+                    fontWeight: 'normal',
+                    letterSpacing: '0.5px',
               }}
             />
+                {isBotNameFocused && tempBotName && (
+                  <button
+                    onClick={() => setTempBotName('')}
+                    style={{
+                      position: 'absolute',
+                      top: '50%',
+                      right: px2vw(12),
+                      transform: 'translateY(-50%)',
+                      width: px2vw(20),
+                      height: px2vw(20),
+                      background: 'transparent',
+                      border: 'none',
+                      cursor: 'pointer',
+                      display: 'flex',
+                      alignItems: 'center',
+                      justifyContent: 'center',
+                    }}
+                  >
+                    <svg width="20" height="20" viewBox="0 0 24 24" fill="none">
+                      <circle cx="12" cy="12" r="11" stroke="rgba(191, 157, 248, 1)" strokeWidth="2.5" fill="none" />
+                      <path
+                        d="M8 8L16 16M16 8L8 16"
+                        stroke="rgba(191, 157, 248, 1)"
+                        strokeWidth="2.5"
+                        strokeLinecap="round"
+                      />
+                    </svg>
+                  </button>
+                )}
+              </div>
+              {/* 底部渐变条 */}
+              <div
+                style={{
+                  width: '100%',
+                  height: '2PX',
+                  background: isBotNameFocused ? focusGradient : 'rgba(103, 80, 164, 1)',
+                  transition: 'background 0.3s',
+                }}
+              />
+            </div>
 
             {/* 按钮组 */}
             <div
               style={{
                 display: 'flex',
-                gap: px2vmin(16),
-                justifyContent: 'flex-end',
+                gap: px2vmin(43),
+                justifyContent: 'center',
+                width: '100%',
               }}
             >
               <button
                 onClick={handleCloseBotNameModal}
                 style={{
-                  fontSize: px2vmin(16),
-                  color: '#fff',
-                  backgroundColor: 'rgba(255, 255, 255, 0.1)',
+                  width: px2vmin(90),
+                  height: px2vmin(48),
+                  backgroundImage: `url(${cancelBtnImage})`,
+                  backgroundSize: 'cover',
+                  backgroundRepeat: 'no-repeat',
+                  backgroundPosition: 'center',
                   border: 'none',
-                  padding: px2vmin(12),
-                  paddingLeft: px2vmin(24),
-                  paddingRight: px2vmin(24),
                   cursor: 'pointer',
-                  borderRadius: px2vmin(8),
+                  padding: 0,
                 }}
-              >
-                取消
-              </button>
+              />
               <button
                 onClick={handleSaveBotName}
                 style={{
-                  fontSize: px2vmin(16),
-                  color: '#fff',
-                  backgroundColor: 'rgb(153, 97, 248)',
+                  width: px2vmin(94),
+                  height: px2vmin(48),
+                  backgroundImage: `url(${isBotNameChanged ? changeBtnActiveImage : changeBtnImage})`,
+                  backgroundSize: 'cover',
+                  backgroundRepeat: 'no-repeat',
+                  backgroundPosition: 'center',
                   border: 'none',
-                  padding: px2vmin(12),
-                  paddingLeft: px2vmin(24),
-                  paddingRight: px2vmin(24),
-                  cursor: 'pointer',
-                  borderRadius: px2vmin(8),
+                  cursor: isBotNameChanged ? 'pointer' : 'default',
+                  padding: 0,
+                  opacity: isBotNameChanged ? 1 : 0.6,
                 }}
-              >
-                保存
-              </button>
+                disabled={!isBotNameChanged}
+              />
             </div>
           </div>
         </>
@@ -1186,6 +1386,7 @@ export const ProfilePage = () => {
         stoneType={energyStoneModalType}
       />
     </div>
+    </>
   );
 };