From d60f9af758d8a3f3498a4fecfba200b69079548c Mon Sep 17 00:00:00 2001 From: seaislee1209 Date: Wed, 4 Feb 2026 15:33:02 +0800 Subject: [PATCH] feat: core code --- .gitignore | 22 + PRD.md | 348 ++++ agent-manage.html | 365 ++++ agreement.html | 170 ++ app.js | 65 + bluetooth.html | 469 +++++ clean_css.py | 228 +++ cleanup_css.py | 28 + collection-list.html | 217 +++ design_system.md | 99 + device-control.html | 1113 +++++++++++ find_css.py | 47 + fix_css.py | 198 ++ guide-feeding.html | 188 ++ help.html | 371 ++++ index.html | 57 + login.html | 754 ++++++++ notifications.html | 399 ++++ preview.html | 162 ++ privacy.html | 167 ++ products.html | 405 ++++ profile-info.html | 338 ++++ profile.html | 361 ++++ settings.html | 357 ++++ sharing-list.html | 192 ++ story-detail.html | 639 ++++++ story-loading.html | 73 + styles.css | 4408 ++++++++++++++++++++++++++++++++++++++++++ wifi-config.html | 502 +++++ 这里是说明.md | 1 + 30 files changed, 12743 insertions(+) create mode 100644 .gitignore create mode 100644 PRD.md create mode 100644 agent-manage.html create mode 100644 agreement.html create mode 100644 app.js create mode 100644 bluetooth.html create mode 100644 clean_css.py create mode 100644 cleanup_css.py create mode 100644 collection-list.html create mode 100644 design_system.md create mode 100644 device-control.html create mode 100644 find_css.py create mode 100644 fix_css.py create mode 100644 guide-feeding.html create mode 100644 help.html create mode 100644 index.html create mode 100644 login.html create mode 100644 notifications.html create mode 100644 preview.html create mode 100644 privacy.html create mode 100644 products.html create mode 100644 profile-info.html create mode 100644 profile.html create mode 100644 settings.html create mode 100644 sharing-list.html create mode 100644 story-detail.html create mode 100644 story-loading.html create mode 100644 styles.css create mode 100644 wifi-config.html create mode 100644 这里是说明.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..14cf026 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# System Files +.DS_Store +Thumbs.db +Desktop.ini + +# Python +__pycache__/ +*.py[cod] + +# Node +node_modules/ +dist/ + +# Project Specific +.agent/ +.gemini/ +tmp/ + +# Large Media Directories (Uncomment if they should be ignored to fix 413 error) +动态绘本/ +故事书封面图/ +*.mp4 diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..ffaf9da --- /dev/null +++ b/PRD.md @@ -0,0 +1,348 @@ +# Airhub App PRD (产品需求文档) + +## 产品概述 + +Airhub 是一款用于连接和控制智能硬件设备的移动应用,支持蓝牙配对、WiFi配网、NFC识别和设备控制。 + +--- + +## 支持的产品 + +| 产品 | 图标 | AI功能 | 连接方式 | 主题色 | 备注 | +|------|------|--------|----------|--------|------| +| **毛绒机芯** | 卡皮巴拉 + AI角标 | ✅ | 蓝牙 + WiFi | 暖棕色 | 完整流程 | +| **电子吧唧 AI版** | 圆形屏幕 + AI角标 | ✅ | 蓝牙 + WiFi | 科技蓝 | 完整流程 | +| **普通电子吧唧** | 圆形屏幕 (无角标) | ❌ | 仅蓝牙 | 浅灰蓝 | 仅蓝牙传图 | +| **AI手链** | 手绳图标 + AI角标 | ✅ | NFC扫描 | 活力橙 | RFID标签,无电子元件 | +| **洛天依** | 洛天依形象 | ✅ | 外部链接 | 青绿色 | 跳转下载专属APP | + +> **设备识别**:蓝牙设备通过SN码识别,AI手链通过NFC RFID识别 + +--- + +## 页面架构 + +``` +首页 (立即连接) + │ + ├─ 蓝牙搜索页 ──→ 发现设备 ──→ 设备详情页 + │ │ + │ ├─ 毛绒机芯/电子吧唧AI → WiFi配网 → 设备控制页 + │ └─ 普通电子吧唧 → 直接进入设备控制页 + │ + ├─ NFC扫描 (全局) ──→ AI手链详情页 + │ + └─ 产品选择页 (卡片列表) ──→ 各设备详情页 + └─ 洛天依 → 外部下载链接 +``` + +--- + +## 核心页面 + +### 0. APP启动逻辑 +``` +APP启动 + ├─ 首次使用 (无已连接设备) → 显示欢迎首页 + └─ 有已连接设备 → 显示最后操作的设备首页 +``` +> 使用 localStorage 记录 `lastActiveDevice`,每次操作设备时更新 + +### 1. 欢迎首页 ✅ 已完成 +- Logo (像素风) + 吉祥物 (浮动) + 渐变背景 + "立即连接" +- **仅首次使用或无设备时显示** + +### 2. 蓝牙搜索页 +- 马里奥问号箱子动画 → 发现设备变换为产品图标 +- SN码识别设备类型 + +### 3. 产品选择页 +- 卡片形式展示所有产品 +- 点击跳转对应设备页面 +- 洛天依卡片 → 外部下载链接 + +### 4. 设备控制页 (Device Control) + +#### 4.1 通用结构 +- **头部**:沉浸式 Header,包含切换设备入口、设备状态指示。 +- **底部**:悬浮式 Tab 导航栏 (Icon Only),提供核心功能入口。 + +#### 4.2 毛绒机芯 (卡皮巴拉) 首页 +- **UI布局**: + - **Header**: + - 左侧:🔄 切换按钮 (跳转产品列表) + - 右侧: + - 🟢 在线状态 (绿点 + "在线") + - 🔋 电量显示 (Icon + 百分比) + - ⚙️ 设置入口 + - **Main (视觉核心)**: + - 居中展示卡皮巴拉形象 (PNG) + - 交互:待机呼吸动画 + - **Footer (功能导航)**: + - 🏠 **首页 (Home)**:当前设备状态 + - 📖 **故事 (Story)**:AI故事生成入口 + - 🎵 **歌曲 (Music)**:音乐/音效控制 + - 👤 **我的 (User)**:设备专属个人中心 + + - **交互弹窗:故事生成器 (Story Generator)**: + - **入口**:点击底部的 [故事] Tab。 + - **逻辑**: + - **Tab分类**:角色 / 环境 / 道具。 + - **限制**:每类最多选3个,总数最多9个。 + - **自动跳转**:单类选满3个后,自动跳转下一 Tab。 + - **选中态**:右上角“毛绒徽章”对钩 (Apple Style),无顶部预览栏。 + +### 5. WiFi配网页 +- 选择网络 → 输入密码 → 配网进度 → 成功/失败 + +--- + +## 特殊逻辑 + +### NFC全局监听 +``` +APP任意页面 + 扫描AI手链NFC + ├─ APP未打开 → 拉起APP → 跳转AI手链页面 + └─ APP已打开 → 直接跳转AI手链页面 +``` + +### 切换设备 +- 所有设备详情页头部有 🔄 图标 +- 点击跳转到产品选择页 + +--- + +## 设计规范 (Visual Design System) + +### 🎨 双层主题色体系 + +Airhub 采用 **双层主题色体系**,确保品牌统一性的同时支持产品个性化: + +#### 1️⃣ App 级主视觉 (Global Theme) +> 适用范围:首页 → 蓝牙搜索 → WiFi配网 → 产品列表页 + +| 属性 | 值 | 说明 | +|------|-----|------| +| **主渐变** | `#22D3EE → #3B82F6 → #6366F1 → #8B5CF6` | 青 → 蓝 → 靛 → 紫,水平渐变 | +| **背景** | 粉紫流动渐变 | 柔和梦幻感,多层 radial-gradient 叠加 | +| **按钮** | 3D 发光胶囊 (见下方质感规范) | 所有主要 CTA 按钮 | + +#### 2️⃣ 产品级主题色 (Product Theme) +> 适用范围:UI主色调、卡片背景、按钮激活态。采用 **4色阶全息渐变** 以提升通透感与高级感。 + +| 产品 | 标识色 | 4色阶渐变定义 (Left → Right) | 风格描述 | +|------|------|----------|------| +| **毛绒机芯** | 暖杏褐 | `#E6B98D` (35%) `#E8C9A8` (30%) `#D4A373` (25%) `#B07D5A` | **醇厚**:左侧杏色起手不发白,右侧收于深褐,质感扎实。 | +| **电子吧唧AI** | 科技青 | `#22D3EE` (35%) `#60A5FA` (30%) `#818CF8` (25%) `#A78BFA` | **鲜艳**:左侧高亮青色(同首页按钮),右侧转紫,色彩跨度大。 | +| **普通吧唧** | 柔光紫 | `#C084FC` (35%) `#D8B4FE` (30%) `#C4B5FD` (25%) `#A78BFA` | **通透**:左侧亮紫,中间过渡到柔和淡紫,避免油漆感。 | +| **AI手链** | 活力橙 | `#FDBA74` (35%) `#FB923C` (30%) `#FBAF85` (25%) `#E07B54` | **轻盈**:左侧亮橙,右侧收于赤陶色,拒绝死板橘红。 | +| **洛天依** | 未来绿 | `#34D399` (35%) `#5EEAD4` (30%) `#22D3EE` (25%) `#2DD4BF` | **偏光**:翠绿 → 冰蓝 → 青色,极具未来感的冷色流动。 | + +> **规则**: +> 1. **左实右虚**:渐变左侧起始色必须有足够饱和度,严禁使用近白色。 +> 2. **4色阶**:必须包含4个色彩节点,模仿光线在玉石/光盘表面的折射效果。 +> 3. **通透尾部**:渐变右侧颜色稍微提亮或降低"实心感",保持呼吸感。 + +--- + +### ✨ 按钮质感规范 (Glowing Pill Button Standard) + +> **无论主题色如何变化,所有主要按钮的"质感"必须统一。** + +``` +┌─────────────────────────────────────────────┐ +│ ▓▓▓ 顶部高光层 (rgba(255,255,255,0.15)) ▓▓▓ │ +│ │ +│ 按钮文字 (白色) │ +│ │ +│ ▒▒▒ 底部暗边 (inset shadow) ▒▒▒ │ +└─────────────────────────────────────────────┘ + ↓↓↓ 外发光 (主题色, opacity 0.15~0.4) ↓↓↓ +``` + +| 质感属性 | CSS 实现 | +|----------|----------| +| **形状** | `border-radius: 50% of height` (胶囊形) | +| **3D 凸起** | `inset 0 1px 1px rgba(255,255,255,0.3)` 顶部高光 | +| **底部暗边** | `inset 0 -1px 2px rgba(0,0,0,0.1)` | +| **外发光** | `0 0 15px {主题色/0.35}, 0 0 30px {主题色/0.25}, 0 6px 20px {主题色/0.4}` | +| **扫光动画** | `shine` 动画,3s 周期 | +### 3. Visual System & Themes (New Standard) +* **Global / Home Page (AI Tech)**: + * **Theme**: High-Tech, futuristic, crisp. + * **Colors**: Purple, Blue, Pink Gradients. + * **Buttons**: Glassmorphism or Tech Gradient. +* **Device Page: Capybara (Warm Plush)**: + * **Theme**: Soft, organic, warm, friendly. + * **Colors**: "Plush Gradient" -> Soft Tan (`#ECCFA8`) to Warm Brown (`#C99672`). + * **Goal**: Match the physical product "Plush Machine Core" feel. + * **Note**: Avoid "Vibrant Tech Orange" (Safety/Alert color) for main actions here. + +### 4. Technical Architecture +* **Frontend**: Native JS, CSS Variables for theming. +| **按压反馈** | `transform: scale(0.98)` | + +#### CSS 变量定义 (建议) +```css +:root { + /* App 全局主题 */ + --theme-gradient: linear-gradient(90deg, #22D3EE 0%, #3B82F6 35%, #6366F1 65%, #8B5CF6 100%); + --theme-glow-1: rgba(34, 211, 238, 0.35); + --theme-glow-2: rgba(99, 102, 241, 0.25); + --theme-glow-3: rgba(99, 102, 241, 0.4); +} + +/* 产品主题覆盖 (示例:毛绒机芯) */ +.theme-plush-core { + --theme-gradient: linear-gradient(90deg, #D4A574 0%, #C08552 100%); + --theme-glow-1: rgba(212, 165, 116, 0.35); + --theme-glow-2: rgba(192, 133, 82, 0.25); + --theme-glow-3: rgba(192, 133, 82, 0.4); +} +``` + +--- + +### 🧩 视觉一致性规则 + +| 规则 | 说明 | +|------|------| +| **背景统一** | 所有页面使用相同的动态渐变背景系统 | +| **按钮质感统一** | 所有主要按钮遵循 "Glowing Pill" 规范,仅颜色变化 | +| **卡片风格** | 轻量玻璃拟态 (`bg-white/20~30 + blur`),带 `drop-shadow` 光晕 | +| **动作按钮** | **Glass Halo Capsule** (见下方详细规范) | +| **图标风格** | 像素风 SVG (Pixel Art),统一 24x24 viewBox | +| **动效时长** | 微交互 150-300ms,页面过渡 300-500ms | +| **边框策略** | 玻璃态元素使用 `1px rgba(255,255,255,0.3)` 弱边框 | + +--- + +### ✨ 毛玻璃光晕胶囊规范 (Glass Halo Capsule Standard) + +> **用于卡片内部或深色渐变背景上的次要/功能性操作按钮。** + +| 属性 | 实现方式 | +|------|----------| +| **背景** | `rgba(255, 255, 255, 0.2)` + `backdrop-filter: blur(8px)` | +| **边框** | `1px solid rgba(255, 255, 255, 0.3)` | +| **形状** | `border-radius: 20px` (胶囊形) | +| **文字/图标** | 针对不同操作设置颜色 (如 `#EF4444` 或 `#B07D5A`) | +| **防遮挡光晕** | **核心重点**:使用 `filter: drop-shadow(0 0 2px #FFF) drop-shadow(0 0 5px rgba(255,255,255,0.8))` 确保彩色内容在深色背景下不“吃字”。 | + +--- + +### 📜 渐变遮罩滚动页规范 (Gradient Mask Scroll Standard) + +> **用于设置页、法务文档(用户协议、隐私政策)等长文本滚动页面。** + +#### 核心逻辑 +- **Header**:背景**完全透明**,不使用毛玻璃,保持视觉纯净。 +- **Content**:内容区域使用 CSS `mask-image` 实现顶部淡出效果,滚动时文字优雅消失,而非被 Header 硬切。 + +#### 参数标准 + +| 属性 | 值 | 说明 | +|------|-----|------| +| **内容顶部间距** | `calc(safe-area + 120px)` | 内容初始位置,避开 Header | +| **遮罩透明区** | `0px ~ 100px` | 此区域完全透明(文字不可见) | +| **遮罩过渡区** | `100px ~ 130px` | 30px 渐变过渡至完全显示 | + +#### CSS 实现 +```css +.content-scroll { + padding-top: calc(env(safe-area-inset-top, 20px) + 120px); + + /* 淡出遮罩 */ + -webkit-mask-image: linear-gradient(to bottom, transparent 0px, transparent 100px, black 130px, black 100%); + mask-image: linear-gradient(to bottom, transparent 0px, transparent 100px, black 130px, black 100%); + + /* 隐藏滚动条 */ + scrollbar-width: none; +} +.content-scroll::-webkit-scrollbar { display: none; } +``` + +#### 适用页面 +- `privacy.html` - 隐私政策 +- `agreement.html` - 用户协议 +- `collection-list.html` - 个人信息收集清单 +- `sharing-list.html` - 第三方信息共享清单 +- `settings.html` - 设置页 (已应用) + +--- + +### 🔧 共享 UI 组件规范 + +> **所有页面必须使用以下共享组件**,定义在 `styles.css` 中,确保一处修改全局生效。 + +#### Layout Tokens (CSS 变量) +```css +:root { + --header-padding-top: calc(env(safe-area-inset-top, 20px) + 12px); + --page-padding-x: 20px; + --safe-area-bottom: env(safe-area-inset-bottom, 20px); +} +``` + +#### 共享组件类名 + +| 类名 | 用途 | 规格 | +|------|------|------| +| `.page-header` | 页面头部 | Grid 布局 (`40px 1fr 40px`) 实现完美居中 | +| `.back-btn` | 返回按钮 | 40×40px, 12px圆角, 简单玻璃磨砂 (首页/蓝牙同款) | +| `.page-title` | 页面标题 | 18px/700, 居中 (Grid Column 2) | +| `.primary-btn` | 主按钮 | Glowing Pill 质感, 高度 58px | +| `.scroll-container` | 滚动容器 | iOS 惯性滚动, 统一内边距 | + +#### 使用示例 +```html + + +
+ +
+``` + +--- + +### 字体规范 + +| 用途 | 字体 | 字重 | +|------|------|------| +| **正文/UI** | Inter | 400-600 | +| **Logo/像素元素** | Press Start 2P | 400 | +| **回退字体栈** | -apple-system, BlinkMacSystemFont, Roboto | - | + +> **为什么选择 Inter?** +> - 专为数字屏幕设计,高可读性 +> - 被 GitHub、Figma、Linear 等知名产品使用 +> - 支持多语言,中英文混排效果好 +> - 免费开源,无授权问题 + +--- + +## 技术接口预留 + +```javascript +// 蓝牙设备发现回调 +onBluetoothDeviceFound(device) { /* SN码解析 */ } + +// NFC扫描回调 +onNFCTagRead(tagId) { /* RFID识别 */ } + +// 设备类型枚举 +DEVICE_TYPES = { + PLUSH_CORE: 'plush_core', // 毛绒机芯 + BADGE_AI: 'badge_ai', // 电子吧唧AI + BADGE_BASIC: 'badge_basic', // 普通电子吧唧 + BRACELET_AI: 'bracelet_ai', // AI手链 + LUOTIANYI: 'luotianyi' // 洛天依 +} +``` + +--- + +*文档最后更新: 2026-02-04* diff --git a/agent-manage.html b/agent-manage.html new file mode 100644 index 0000000..f34fbd7 --- /dev/null +++ b/agent-manage.html @@ -0,0 +1,365 @@ + + + + + + + Airhub - 角色记忆 + + + + + + +
+
+
+
+ + + +
+ +
+
2025/01/15
+
+
🧠
+
+
Airhub_Mem_01
+
+
+
已绑定:Airhub_5G
+
角色昵称:小毛球
+ +
+ + +
+
2024/08/22
+
+
🐾
+
+
Airhub_Mem_02
+
+
+
已绑定:未绑定设备
+
角色昵称:豆豆
+ +
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/agreement.html b/agreement.html new file mode 100644 index 0000000..e41ec9a --- /dev/null +++ b/agreement.html @@ -0,0 +1,170 @@ + + + + + + + Airhub - 用户协议 + + + + + + +
+
+
+
+ +
+

欢迎您使用 Airhub 产品及服务!

+

特别提示: 在您开始使用 Airhub + 产品(以下简称"本产品")及相关服务之前,请您务必仔细阅读本《用户协议》(以下简称"本协议")。特别是涉及免除或者限制责任的条款、法律适用和争议解决条款等,请您重点阅读。 +

+ +

1. 服务说明

+

1.1 Airhub Team(以下简称"我们")向用户提供包括但不限于设备连接控制、AI 语音交互、角色记忆存储、云端同步等服务(以下简称"本服务")。

+

1.2 本服务的具体内容由我们根据实际情况提供,我们有权随时变更、中断或终止部分或全部服务。

+

1.3 用户理解并同意,本服务仅供用户个人非商业性质的使用。用户不得利用本服务进行销售或其他商业用途。

+ +

2. 账号注册与使用

+

2.1 用户在使用本服务时需要注册一个 Airhub 账号。用户应保证注册信息的真实性、准确性和完整性。

+

2.2 用户有责任妥善保管注册账号信息及密码安全。因用户保管不善可能导致账号被盗及其后果,由用户自行承担。

+

2.3 如发现任何未经授权使用您账号登录、使用本服务的情况,您应立即通知我们。您理解我们对您的任何请求采取行动需要合理时间,我们对在采取行动前已经产生的后果不承担责任。

+ +

3. 用户行为规范

+

用户在使用本服务过程中,应当遵守法律法规,不得从事下列行为:

+
    +
  • 发布、传送、传播、储存危害国家安全、破坏社会稳定、违反公序良俗的内容;
  • +
  • 发布、传送、传播、储存侮辱、诽谤、淫秽、暴力、赌博等违法违规内容;
  • +
  • 利用 AI 功能生成虚假信息、诈骗信息或用于非法用途;
  • +
  • 对 AI 角色进行性骚扰、辱骂或诱导生成不当内容;
  • +
  • 进行任何危害计算机网络安全的行为,包括但不限于攻击、侵入他人系统。
  • +
+ +

4. 个人信息保护

+

4.1 保护用户个人信息是我们的基本原则。我们将按照本协议及《隐私政策》的规定收集、使用、存储和分享您的个人信息。

+

4.2 您在注册账号或使用本服务的过程中,可能需要填写一些必要的信息。若国家法律法规有特殊规定的,您需要填写真实的身份信息。若您填写的信息不完整,则无法使用本服务或在使用过程中受到限制。

+ +

5. AI 服务特别说明

+

5.1 本产品提供的 AI 交互功能基于深度学习模型。AI 生成的内容(包括语音、文本)具有随机性,不代表我们的立场或观点。

+

5.2 角色记忆功能存储的数据为您的个人数字资产,我们会采取严格的加密措施进行保护。

+

5.3 您知悉并同意,由于技术的局限性,AI 生成的内容可能存在错误或不准确,您应自行判断其真实性与可靠性。

+ +

6. 知识产权

+

6.1 我们在本服务中提供的内容(包括但不限于软件、技术、程序、网页、文字、图片、音频、视频、图表、版面设计、电子文档等)的知识产权属于我们所有。

+

6.2 未经我们明确书面同意,您不得对上述内容进行复制、修改、出租、出借、出售、分发或创建衍生作品。

+ +

7. 免责声明

+

7.1 鉴于网络服务的特殊性,我们不保证服务不会中断,对服务的及时性、安全性、准确性也不作保证。

+

7.2 对于因不可抗力或我们不能控制的原因造成的网络服务中断或其它缺陷,我们不承担任何责任,但将尽力减少因此而给用户造成的损失和影响。

+ +

8. 协议的变更

+

我们要根据互联网的发展和法律法规的变化,在必要时修改本协议的条款。您可以在相关服务页面查阅最新版本的协议条款。本协议条款变更后,如果您继续使用本服务,即视为您已接受修改后的协议。

+ +

更新日期:2025年1月15日

+
+
+ + + \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..fcd632a --- /dev/null +++ b/app.js @@ -0,0 +1,65 @@ +/** + * Airhub App - Main JavaScript + * Premium IoT Device Control Center + */ + +// ======================================== +// App State +// ======================================== +const AppState = { + currentPage: 'home', + isConnecting: false, + connectedDevice: null, +}; + +// ======================================== +// Page Navigation +// ======================================== +function navigateTo(pageId) { + const pages = document.querySelectorAll('.page'); + pages.forEach(page => page.classList.remove('active')); + + const targetPage = document.getElementById(`page-${pageId}`); + if (targetPage) { + targetPage.classList.add('active'); + AppState.currentPage = pageId; + } +} + +// ======================================== +// Connect Button Handler +// ======================================== +function handleConnect() { + if (AppState.isConnecting) return; + + AppState.isConnecting = true; + const btn = document.getElementById('connect-btn'); + const btnText = btn.querySelector('.btn-text'); + + // Add brief feedback before navigating + btn.style.pointerEvents = 'none'; + btnText.textContent = '正在搜索...'; + + // Navigate to Bluetooth search page + setTimeout(() => { + window.location.href = 'bluetooth.html'; + }, 300); +} + +// ======================================== +// Initialization +// ======================================== +document.addEventListener('DOMContentLoaded', () => { + console.log('Airhub App initialized'); + + // Add touch feedback for mobile + const buttons = document.querySelectorAll('button'); + buttons.forEach(btn => { + btn.addEventListener('touchstart', () => { + btn.style.transform = 'scale(0.98)'; + }); + btn.addEventListener('touchend', () => { + btn.style.transform = ''; + }); + }); +}); diff --git a/bluetooth.html b/bluetooth.html new file mode 100644 index 0000000..6d8a150 --- /dev/null +++ b/bluetooth.html @@ -0,0 +1,469 @@ + + + + + + + Airhub - 搜索设备 + + + + + + + +
+
+
+
+
+ +
+
+ +

搜索设备

+
+
+ +
+ +
正在搜索...
+ + +
+ + +
+
+
+ ? +
+
+
+

正在搜索附近设备

+
+
+ + +
+
+ + AI +
+
Device A
+
Type A
+
+ + +
+
+ + AI +
+
Device B
+
Type B
+
+ +
+ + + +
+ +
+ + +
+
+ +
+ + + + + + \ No newline at end of file diff --git a/clean_css.py b/clean_css.py new file mode 100644 index 0000000..78acf72 --- /dev/null +++ b/clean_css.py @@ -0,0 +1,228 @@ +import os + +css_path = r"d:\Airhub\styles.css" + +try: + with open(css_path, "rb") as f: + content_bytes = f.read() + content = content_bytes.decode("utf-8", errors="ignore") + + # Strategy: Find the FIRST occurrence of my Settings Page injections + # Valid markers: + # "/* --- Settings Page" + # "/* --- HEADER HEIGHT CORRECTION" + + markers = ["/* --- Settings Page", "/* --- HEADER HEIGHT"] + cutoff_idx = -1 + + for m in markers: + idx = content.find(m) + if idx != -1: + if cutoff_idx == -1 or idx < cutoff_idx: + cutoff_idx = idx + + if cutoff_idx != -1: + print(f"Found marker at {cutoff_idx}. Truncating duplicate tails.") + clean_content = content[:cutoff_idx] + else: + # Fallback: Find .custom-toast and cut after + print("Markers not found. Falling back to .custom-toast.") + idx = content.find(".custom-toast.active") + if idx != -1: + end_brace = content.find("}", idx) + 1 + clean_content = content[:end_brace] + else: + print("CRITICAL: No anchor found. Aborting to avoid data loss.") + exit(1) + + # V5 CSS - The Definitive Version + new_css = """ +/* --- Settings Page (V5 - Modals & Fixes) --- */ +.settings-view { + position: fixed; + top: 0; left: 0; width: 100%; height: 100%; + /* Warm Capybara Theme Background */ + background: linear-gradient(135deg, #FEF5EC 0%, #FDF2F8 100%); + z-index: 2000; + display: flex; flex-direction: column; + animation: slideUp 0.3s cubic-bezier(0.2, 0.8, 0.2, 1); + overflow: hidden; +} + +@keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } } + +/* Header - Matching Main Page Header Height */ +.settings-header { + background: transparent !important; + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; + border-bottom: none !important; + + /* Match main page: padding-top: calc(safe-area + 48px) */ + padding-top: calc(env(safe-area-inset-top, 20px) + 48px) !important; + padding-bottom: 16px !important; + position: absolute !important; + top: 0; left: 0; right: 0; + z-index: 9999; + + display: flex !important; + align-items: center !important; + justify-content: space-between !important; + padding-left: 20px !important; + padding-right: 20px !important; +} + +.settings-title { + font-size: 16px !important; + font-weight: 600 !important; + color: #1F2937 !important; +} + +/* Content Area - Gradient Fading Mask */ +.settings-content { + flex: 1; + overflow-y: auto; + /* Header is about safe-area + 48px + 16px bottom padding + 44px content = ~108px total */ + padding-top: calc(env(safe-area-inset-top, 20px) + 100px) !important; + padding-left: 20px; padding-right: 20px; padding-bottom: 100px; + background: transparent; + scrollbar-width: none; + -webkit-mask-image: linear-gradient(to bottom, transparent 0px, transparent 80px, black 110px, black 100%); + mask-image: linear-gradient(to bottom, transparent 0px, transparent 80px, black 110px, black 100%); + pointer-events: auto !important; + position: relative; + z-index: 1; +} +.settings-content::-webkit-scrollbar { display: none; } + +/* Settings Group */ +.settings-group-title { + font-size: 12px; color: #8B5E3C; + margin-bottom: 8px; margin-left: 16px; margin-top: 24px; + font-weight: 500; +} + +.settings-group { + background: rgba(255, 255, 255, 0.8); + border-radius: 20px; + margin-bottom: 20px; + box-shadow: 0 4px 16px rgba(139, 94, 60, 0.04); + overflow: hidden; +} + +.settings-item { + display: flex; align-items: center; justify-content: space-between; + padding: 12px 16px; min-height: 48px; + background: transparent; + border-bottom: 1px solid rgba(0,0,0,0.05); +} +.settings-item:last-child { border-bottom: none; } +.settings-item.clickable { cursor: pointer; } +.settings-item.clickable:active { background: rgba(255,255,255,0.5); } + +/* Typography */ +.item-label { font-size: 15px; color: #4B5563; font-weight: 400; } +.item-value { font-size: 14px; color: #9CA3AF; display: flex; align-items: center; gap: 6px; } +.arrow { font-size: 18px; color: #D1D5DB; } + +.item-text-col { display: flex; flex-direction: column; justify-content: center; gap: 4px; } +.item-desc { font-size: 11px; color: #9CA3AF; line-height: 1.3; } +.settings-item.warning .item-label { color: #EF4444; } + +/* Toggle */ +.toggle-switch { + width: 50px; height: 30px; + background: #E5E7EB; border-radius: 15px; + position: relative; cursor: pointer; transition: background 0.3s; +} +.toggle-switch.active { background: #FFB088; } +.toggle-knob { + width: 26px; height: 26px; background: white; border-radius: 50%; + position: absolute; top: 2px; left: 2px; + transition: transform 0.3s; box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +.toggle-switch.active .toggle-knob { transform: translateX(20px); } + +/* Volume Slider */ +.settings-item.column { flex-direction: column; align-items: stretch; gap: 12px; padding-bottom: 16px; } +.volume-row { display: flex; align-items: center; width: 100%; height: 32px; } +.volume-slider { + flex: 1; -webkit-appearance: none; appearance: none; + height: 24px; margin: 0 8px; background: transparent; cursor: grab; + width: 100%; +} +.volume-slider::-webkit-slider-runnable-track { width: 100%; height: 4px; background: #E5E5EA; border-radius: 2px; } +.volume-slider::-webkit-slider-thumb { + -webkit-appearance: none; height: 24px; width: 24px; + background: white; border-radius: 50%; box-shadow: 0 2px 6px rgba(139,94,60,0.2); + margin-top: -10px; /* (4-24)/2 = -10 */ +} +.vol-icon { font-size: 16px; color: #9CA3AF; width: 24px; text-align: center; } + +/* --- MODALS (FIXED Z-INDEX) --- */ +.modal-overlay { + position: fixed; + top: 0; left: 0; width: 100%; height: 100%; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(4px); + z-index: 2200 !important; /* Must be > settings-view (2000) */ + display: none; + justify-content: center; align-items: center; +} +.modal-overlay.active { + display: flex !important; + animation: fadeIn 0.2s ease forwards; +} +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } + +.glass-modal { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; padding: 24px; + width: 80%; max-width: 320px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); + text-align: center; + transform: scale(1); + animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; + position: relative; + z-index: 2210; + pointer-events: auto; +} +@keyframes popIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } } + +.modal-title { font-size: 18px; font-weight: 600; margin-bottom: 16px; color: #1F2937; } +.modal-desc { font-size: 14px; color: #6B7280; margin-bottom: 24px; line-height: 1.5; } + +.modal-actions { display: flex; gap: 12px; justify-content: center; } +.modal-btn { + flex: 1; padding: 12px 0; border-radius: 12px; border: none; font-size: 16px; font-weight: 500; cursor: pointer; + position: relative; + z-index: 2220; +} +.modal-btn.cancel { background: #F3F4F6; color: #6B7280; } +.modal-btn.confirm { background: linear-gradient(135deg, #FFB088 0%, #FF8E53 100%); color: white; } +.modal-btn.danger { background: #EF4444; color: white; } +.modal-btn.secondary { background: #F3F4F6; color: #4B5563; } + +.modal-input { + width: 100%; padding: 12px; margin-bottom: 20px; box-sizing: border-box; + border: 1px solid #E5E7EB; border-radius: 12px; + font-size: 16px; background: #F9FAFB; outline: none; +} +.modal-input:focus { border-color: #FFB088; background: white; } + +/* Unbind Styles */ +.unbind-header { display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 12px; } +.warn-icon svg { width: 24px; color: #EF4444; } +.unbind-title { font-size: 18px; font-weight: 600; color: #1F2937; } +.highlight-text { color: #EF4444; font-weight: 600; } +""" + + final_content = clean_content + "\n" + new_css + + with open(css_path, "w", encoding="utf-8") as f: + f.write(final_content) + + print(f"Success: Repaired CSS. Original size: {len(content)}, New size: {len(final_content)}") + +except Exception as e: + print(f"Error: {e}") diff --git a/cleanup_css.py b/cleanup_css.py new file mode 100644 index 0000000..14d7a68 --- /dev/null +++ b/cleanup_css.py @@ -0,0 +1,28 @@ +import re + +# Read the file +with open('d:/Airhub/styles.css', 'r', encoding='utf-8') as f: + content = f.read() + +original_size = len(content) + +# 1. Replace multiple \r with single \r +content = re.sub(r'\r+', '\r', content) + +# 2. Remove consecutive blank lines (keep at most 1 blank line) +content = re.sub(r'(\r?\n){3,}', '\n\n', content) + +# 3. Remove trailing whitespace on each line +content = re.sub(r'[ \t]+(\r?\n)', r'\1', content) + +# 4. Ensure file ends with single newline +content = content.rstrip() + '\n' + +# Write back +with open('d:/Airhub/styles.css', 'w', encoding='utf-8') as f: + f.write(content) + +new_size = len(content) +print(f"Original size: {original_size} bytes") +print(f"New size: {new_size} bytes") +print(f"Reduced by: {original_size - new_size} bytes ({(1 - new_size/original_size)*100:.1f}%)") diff --git a/collection-list.html b/collection-list.html new file mode 100644 index 0000000..132a5fc --- /dev/null +++ b/collection-list.html @@ -0,0 +1,217 @@ + + + + + + + Airhub - 个人信息收集清单 + + + + + + +
+
+
+
+ +
+

为了向您提供 Airhub + 的核心服务,我们需要收集以下类型的个人信息。我们将严格遵守法律法规,保护您的个人信息安全。

+ +
+
基础功能服务
+
+ 收集信息类型 + 手机号码、登录密码 +
+
+ 使用目的 + 用于账号注册、登录、找回密码及身份认证 +
+
+ 收集场景 + 用户注册或登录 APP 时 +
+
+ +
+
硬件连接与控制
+
+ 收集信息类型 + Wi-Fi信息(SSID/BSSID)、蓝牙信息、设备序列号(SN)、MAC地址 +
+
+ 使用目的 + 用于发现附近设备、建立蓝牙/Wi-Fi连接、设备配网及固件升级 +
+
+ 收集场景 + 绑定设备、连接设备或使用设备控制功能时 +
+
+ +
+
AI 语音交互业务
+
+ 收集信息类型 + 语音录音、对话文本、交互时间 +
+
+ 使用目的 + 将语音转换为文本以理解指令、生成 AI 回复、优化语音识别模型 +
+
+ 收集场景 + 使用语音对话功能与 AI 角色互动时 +
+
+ +
+
应用安全保障
+
+ 收集信息类型 + 设备IMSI/IMEI、Android ID、IP地址、操作日志 +
+
+ 使用目的 + 风控验证、安全防范、故障排查与分析 +
+
+ 收集场景 + APP 运行期间(包括后台运行) +
+
+ +

更新日期:2025年1月15日

+
+
+ + + \ No newline at end of file diff --git a/design_system.md b/design_system.md new file mode 100644 index 0000000..0cf33d0 --- /dev/null +++ b/design_system.md @@ -0,0 +1,99 @@ +# Airhub 设计规范 (视觉标准) 🎨 + +> [!IMPORTANT] +> 本文档严格区分 **App 全局规范** 与 **产品专属规范**。 +> 开发人员 **必须** 在应用样式前确认当前页面所属的上下文。 + +--- + +## 1. App 全局规范 (科技主题) 🌐 +**适用范围**: `index.html` (首页), `bluetooth.html` (蓝牙搜索), `wifi-config.html` (配网) +**核心风格**: 未来感、洁净、科技、蓝紫色调。 + +### 首要行动点 (Global Primary Action) +用途:主连接按钮、配网流程的“下一步”。 +- **渐变色**: `linear-gradient(90deg, #22D3EE 0%, #3B82F6 35%, #6366F1 65%, #8B5CF6 100%)` + *(青 -> 蓝 -> 靛 -> 紫)* +- **阴影**: `box-shadow: 0 0 15px rgba(34, 211, 238, 0.35)` + +### 导航与头部 +- **背景**: 透明或深度磨砂玻璃 (Glassmorphism)。 +- **图标**: 细描边风格,冷灰色 `#6B7280`。 + +--- + +## 2. 产品专属规范:毛绒机芯 (Capybara) 🐹 +**适用范围**: `device-control.html` (设备控), `products.html` (卡片), 故事生成弹窗。 +**核心风格**: 温暖、毛绒感、舒适、杏褐色调。 + +> [!NOTE] +> 所有与控制“毛绒机芯”设备相关的页面/弹窗,必须强制遵循此主题。 + +### 首要行动点 (Capybara Context) +用途:“创作新故事”、“开始生成”、设备页内的主要交互。 +- **渐变色**: `linear-gradient(135deg, #ECCFA8 0%, #C99672 100%)` + *(浅杏色 -> 暖褐色)* +- **阴影**: `box-shadow: 0 0 15px rgba(201, 150, 114, 0.35), 0 0 30px rgba(201, 150, 114, 0.25)` (复合光晕) +- **边框**: 无 (依靠阴影和光感定义边界) + +### 背景色系统 +- **设备页底色**: `#FDF9F3` (暖沙色 Warm Sand) - **禁止使用纯白或渐变**。 +- **卡片背景**: `#FFFFFF` (奶白色) 配柔和阴影。 +- **选中高亮**: `#FFF7ED` (极淡的橙色叠底)。 + +### 字体颜色 (Capybara Context) +- **标题**: `#4B2404` (黑巧棕)。 +- **正文**: `#4B5563` (暖灰)。 + +### 选中交互模式 (Apple Style) 🍏 +- **操作方式**: 直接点击卡片。 +- **指示器**: **右上角复选徽章 (Checkmark Badge)**。 + - 样式:杏褐色渐变实心圆 + 白色对钩。 +- **状态**: 摒弃“已选列表栏”,视觉反馈完全集中在卡片本体上。 + +--- + +## 3. 开发执行规则 📏 +1. **确认上下文**: 拿到设计稿先判断:这是 App 公共页面(用科技蓝)还是某个具体产品的页面(用产品色)? +2. **按钮混用禁令**: + - 公共页 -> 必须用科技渐变 (Tech Gradient)。 + - 卡皮巴拉页 -> 必须用毛绒渐变 (Plush Gradient)。 + - 一致性: 严禁混搭。卡皮巴拉页面里绝不应该出现“科技蓝”的按钮,除非是系统级的报错弹窗。 + +--- + +### 2.1 设置页规范 (Settings Page - Overlay) ⚙️ +**交互模式**: 全屏覆盖层 (Full-screen Overlay),从底部滑入。 + +#### 核心布局 (Layout & Metrics) +- **容器层级**: `z-index: 2000` (覆盖此时的主页)。 +- **背景**: 暖色渐变 `linear-gradient(135deg, #FEF5EC 0%, #FDF2F8 100%)`。 +- **进出动画**: `slideUp 0.3s cubic-bezier(0.2, 0.8, 0.2, 1)`。 + +#### 头部规范 (Header) +为了与主页保持视觉一致性,头部必须遵循以下严格计算: +- **定位**: `position: absolute` (悬浮于内容之上)。 +- **高度**: 自然高度 (Auto),不固定。 +- **上边距 (Padding-Top)**: `calc(env(safe-area-inset-top, 20px) + 48px)` —— **严格对齐首页**。 +- **底边距 (Padding-Bottom)**: `16px`。 +- **左右边距**: `20px`。 + +#### 内容区域 (Content) +- **遮罩效果**: 顶部使用渐变遮罩 (Gradient Mask) 实现内容在头部下方平滑消失,而非简单的背景色遮挡。 + - `mask-image: linear-gradient(to bottom, transparent 0, transparent 80px, black 110px, ...)` +- **上边距**: `calc(env(safe-area-inset-top) + 100px)` (确保内容不被头部遮挡)。 + +#### 弹窗规范 (Modals) 🚨 +设置页内的二级操作(改名、解绑)必须使用以下弹窗样式: +- **层级**: `z-index: 2200` (必须高于设置页)。 +- **背景**: `rgba(255, 255, 255, 0.95)` (高透明度)。 +- **动画**: 必须添加 `forwards` 属性防止闪退。 + - `animation: popIn 0.3s ... forwards` +- **交互**: + - 必须设置 `pointer-events: auto`。 + - 必须设置 `position: relative` 和独立 `z-index` 给内容元素。 +- **点击阻断**: item 点击事件必须加 `event.stopPropagation()` 防止冒泡。 + +#### 组件样式 +- **开关 (Toggle)**: 激活色 `#FFB088` (暖橙)。 +- **滑动条 (Slider)**: 纯白滑块 + 投影,轨道背景 `#E5E5EA`。 diff --git a/device-control.html b/device-control.html new file mode 100644 index 0000000..bf7049c --- /dev/null +++ b/device-control.html @@ -0,0 +1,1113 @@ + + + + + + + Airhub - 设备控制 + + + + + + +
+ +
+
+
+ + + + + + + + +
+
+ + Capybara +
+
+
+ + + + + +
+ +
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/find_css.py b/find_css.py new file mode 100644 index 0000000..fb3ef43 --- /dev/null +++ b/find_css.py @@ -0,0 +1,47 @@ +import re + +css_path = r"d:\Airhub\styles.css" + +try: + with open(css_path, "r", encoding="utf-8", errors="ignore") as f: + content = f.read() + + # regex for .dc-header { ... } (simplified) + # Matches .classname { ... } roughly + + def find_block(name, text): + print(f"--- Searching for {name} ---") + idx = text.find(name) + if idx == -1: + print("Not found.") + return + + # Find opening brace + open_brace = text.find("{", idx) + if open_brace == -1: + print("No opening brace.") + return + + # Find closing brace (count nesting) + close_brace = -1 + balance = 0 + for i in range(open_brace, len(text)): + if text[i] == '{': + balance += 1 + elif text[i] == '}': + balance -= 1 + if balance == 0: + close_brace = i + break + + if close_brace != -1: + print(text[idx:close_brace+1]) + else: + print("Block not closed properly.") + + find_block(".dc-header", content) + find_block(".glass-modal", content) + find_block(".status-pill", content) + +except Exception as e: + print(e) diff --git a/fix_css.py b/fix_css.py new file mode 100644 index 0000000..18ce9ec --- /dev/null +++ b/fix_css.py @@ -0,0 +1,198 @@ +import os + +css_path = r"d:\Airhub\styles.css" + +try: + with open(css_path, "rb") as f: + content_bytes = f.read() + content = content_bytes.decode("utf-8", errors="ignore") + + # Cutoff at .custom-toast.active + idx = content.find(".custom-toast.active") + if idx == -1: + print("Could not find anchor point.") + exit(1) + + cutoff_idx = content.find("}", idx) + if cutoff_idx == -1: cutoff_idx = idx + 100 + else: cutoff_idx += 1 + + clean_content = content[:cutoff_idx] + + new_css = """ +/* --- Settings Page (V5 - Modals & Fixes) --- */ +.settings-view { + position: fixed; + top: 0; left: 0; width: 100%; height: 100%; + /* Warm Capybara Theme Background */ + background: linear-gradient(135deg, #FEF5EC 0%, #FDF2F8 100%); + z-index: 2000; + display: flex; flex-direction: column; + animation: slideUp 0.3s cubic-bezier(0.2, 0.8, 0.2, 1); + overflow: hidden; +} + +@keyframes slideUp { from { transform: translateY(100%); } to { transform: translateY(0); } } + +/* Header - Transparent & Gradient Mask Logic */ +.settings-header { + background: transparent !important; + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; + border-bottom: none !important; + + height: 44px !important; + padding-top: max(32px, env(safe-area-inset-top, 20px)) !important; + position: absolute !important; + top: 0; left: 0; right: 0; + z-index: 9999; + + display: flex !important; + align-items: center !important; + justify-content: space-between !important; + padding-left: 16px !important; + padding-right: 16px !important; +} + +.settings-title { + font-size: 16px !important; + font-weight: 600 !important; + color: #1F2937 !important; +} + +/* Content Area - Gradient Fading Mask */ +.settings-content { + flex: 1; + overflow-y: auto; + padding-top: calc(max(32px, env(safe-area-inset-top, 20px)) + 50px) !important; + padding-left: 20px; padding-right: 20px; padding-bottom: 100px; + background: transparent; + scrollbar-width: none; + -webkit-mask-image: linear-gradient(to bottom, transparent 0px, transparent 60px, black 90px, black 100%); + mask-image: linear-gradient(to bottom, transparent 0px, transparent 60px, black 90px, black 100%); +} +.settings-content::-webkit-scrollbar { display: none; } + +/* Settings Group */ +.settings-group-title { + font-size: 12px; color: #8B5E3C; + margin-bottom: 8px; margin-left: 16px; margin-top: 24px; + font-weight: 500; +} + +.settings-group { + background: rgba(255, 255, 255, 0.8); + border-radius: 20px; + margin-bottom: 20px; + box-shadow: 0 4px 16px rgba(139, 94, 60, 0.04); + overflow: hidden; +} + +.settings-item { + display: flex; align-items: center; justify-content: space-between; + padding: 12px 16px; min-height: 48px; + background: transparent; + border-bottom: 1px solid rgba(0,0,0,0.05); +} +.settings-item:last-child { border-bottom: none; } +.settings-item.clickable { cursor: pointer; } +.settings-item.clickable:active { background: rgba(255,255,255,0.5); } + +/* Typography */ +.item-label { font-size: 15px; color: #4B5563; font-weight: 400; } +.item-value { font-size: 14px; color: #9CA3AF; display: flex; align-items: center; gap: 6px; } +.arrow { font-size: 18px; color: #D1D5DB; } + +.item-text-col { display: flex; flex-direction: column; justify-content: center; gap: 4px; } +.item-desc { font-size: 11px; color: #9CA3AF; line-height: 1.3; } +.settings-item.warning .item-label { color: #EF4444; } + +/* Toggle */ +.toggle-switch { + width: 50px; height: 30px; + background: #E5E7EB; border-radius: 15px; + position: relative; cursor: pointer; transition: background 0.3s; +} +.toggle-switch.active { background: #FFB088; } +.toggle-knob { + width: 26px; height: 26px; background: white; border-radius: 50%; + position: absolute; top: 2px; left: 2px; + transition: transform 0.3s; box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +.toggle-switch.active .toggle-knob { transform: translateX(20px); } + +/* Volume Slider */ +.settings-item.column { flex-direction: column; align-items: stretch; gap: 12px; padding-bottom: 16px; } +.volume-row { display: flex; align-items: center; width: 100%; height: 32px; } +.volume-slider { + flex: 1; -webkit-appearance: none; appearance: none; + height: 24px; margin: 0 8px; background: transparent; cursor: grab; + width: 100%; +} +.volume-slider::-webkit-slider-runnable-track { width: 100%; height: 4px; background: #E5E5EA; border-radius: 2px; } +.volume-slider::-webkit-slider-thumb { + -webkit-appearance: none; height: 24px; width: 24px; + background: white; border-radius: 50%; box-shadow: 0 2px 6px rgba(139,94,60,0.2); + margin-top: -10px; /* (4-24)/2 = -10 */ +} +.vol-icon { font-size: 16px; color: #9CA3AF; width: 24px; text-align: center; } + +/* --- MODALS (FIXED Z-INDEX) --- */ +.modal-overlay { + position: fixed; + top: 0; left: 0; width: 100%; height: 100%; + background: rgba(0, 0, 0, 0.4); + backdrop-filter: blur(4px); + z-index: 2200 !important; /* Must be > settings-view (2000) */ + display: none; + justify-content: center; align-items: center; + animation: fadeIn 0.2s ease; +} +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } + +.glass-modal { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; padding: 24px; + width: 80%; max-width: 320px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); + text-align: center; + transform: scale(1); + animation: popIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); +} +@keyframes popIn { from { transform: scale(0.9); opacity: 0; } to { transform: scale(1); opacity: 1; } } + +.modal-title { font-size: 18px; font-weight: 600; margin-bottom: 16px; color: #1F2937; } +.modal-desc { font-size: 14px; color: #6B7280; margin-bottom: 24px; line-height: 1.5; } + +.modal-actions { display: flex; gap: 12px; justify-content: center; } +.modal-btn { + flex: 1; padding: 12px 0; border-radius: 12px; border: none; font-size: 16px; font-weight: 500; cursor: pointer; +} +.modal-btn.cancel { background: #F3F4F6; color: #6B7280; } +.modal-btn.confirm { background: linear-gradient(135deg, #FFB088 0%, #FF8E53 100%); color: white; } +.modal-btn.danger { background: #EF4444; color: white; } +.modal-btn.secondary { background: #F3F4F6; color: #4B5563; } + +.modal-input { + width: 100%; padding: 12px; margin-bottom: 20px; box-sizing: border-box; + border: 1px solid #E5E7EB; border-radius: 12px; + font-size: 16px; background: #F9FAFB; outline: none; +} +.modal-input:focus { border-color: #FFB088; background: white; } + +/* Unbind Styles */ +.unbind-header { display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 12px; } +.warn-icon svg { width: 24px; color: #EF4444; } +.unbind-title { font-size: 18px; font-weight: 600; color: #1F2937; } +.highlight-text { color: #EF4444; font-weight: 600; } +""" + + final_content = clean_content + "\n" + new_css + + with open(css_path, "w", encoding="utf-8") as f: + f.write(final_content) + + print("Success: CSS Repaired with Modals") + +except Exception as e: + print(f"Error: {e}") diff --git a/guide-feeding.html b/guide-feeding.html new file mode 100644 index 0000000..ccd340e --- /dev/null +++ b/guide-feeding.html @@ -0,0 +1,188 @@ + + + + + + + Airhub - 喂养指南 + + + + + + + +
+ +
+
+
+
+ + + + + +
+ +
+
+ + Eating Capybara +
+ +
+
如何喂食你的电子宠物?
+
当你的毛绒机芯显示“饿了”的图标时,它需要补充能量!
+
1. 打开 APP 首页,点击右下角的 [能量] 按钮。
+
2. 从列表中选择它喜欢的食物(胡萝卜、西瓜或干草饼干)。
+
3. 点击“投喂”,观察它的反应!
+ +
+ 💡 小贴士: 不同的食物会增加不同的心情值哦!西瓜会让它超级开心。 +
+
+ +
+
心情与成长
+
保持饱腹感可以提升心情值。心情值越高,它的互动反应就越丰富。
+
如果你连续 3 天忘记喂食,它可能会变得懒洋洋的,不愿理人哦... 💤
+
+ +
+
特殊互动
+
在喂食的时候,试着抚摸它的头(在屏幕上滑动),它会发出满意的咕噜声!
+
+
+ +
+
+ + + \ No newline at end of file diff --git a/help.html b/help.html new file mode 100644 index 0000000..c3dd575 --- /dev/null +++ b/help.html @@ -0,0 +1,371 @@ + + + + + + + Airhub - 帮助中心 + + + + + + +
+
+
+
+ + + +
+
+
帮助 Q&A
+
更新日期:2025年1月15日
+
+ + +
+
📖
+
+
喂养指南
+
详细的角色养成方法和日常照顾指南
+
+ +
+ + +
+
设备连接与管理
+
+
+
+ 手机连接设备时"未扫描到设备" + +
+
+

请检查设备是否在配网模式下(双击设备电源键按钮,直至呈现Wi-Fi图标),请确保设备和手机距离在10m内,点击【重新扫描】。

+
+
+ +
+
+ 手机连接设备时"连接设备失败" + +
+
+

可能为服务超时造成的异常,请保持设备处于配网模式下,点击【再试一次】。

+
+
+ +
+
+ 如何添加多个 Wi-Fi 网络? + +
+
+

进入设备控制页 → 设置 → 配置网络,按提示添加备用网络。设备会自动切换到信号最强的网络。

+
+
+
+
+ + +
+
角色养成
+
+
+
+ 什么是角色记忆? + +
+
+

角色记忆是您与 AI 互动过程中产生的人格数据,包含对话风格、喜好偏好等信息。角色记忆可以在不同设备间迁移,让您的 AI 伙伴始终如一。 +

+
+
+ +
+
+ 如何将角色记忆迁移到新设备? + +
+
+

进入「我的」→「角色记忆」,找到需要迁移的记忆,点击「注入设备」,选择目标设备即可完成迁移。

+
+
+
+
+ + +
+
常见问题
+
+
+
+ 设备离线怎么办? + +
+
+

请检查设备电源和网络连接。如果问题持续,尝试重启设备或重新配网。

+
+
+ +
+
+ 如何联系客服? + +
+
+

您可以通过「我的」→「意见反馈」联系我们,或发送邮件至 support@airhub.com。

+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..2e67e49 --- /dev/null +++ b/index.html @@ -0,0 +1,57 @@ + + + + + + + Airhub + + + + + + + + +
+ +
+
+
+
+
+ + +
+ +
+ +
+ + +
+
+ Airhub Spirit +
+
+
+ + +
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/login.html b/login.html new file mode 100644 index 0000000..9c83707 --- /dev/null +++ b/login.html @@ -0,0 +1,754 @@ + + + + + + + 登录 - Airhub + + + + + + + + + +
+ +
+
+
+
+
+ + + + + +
+
+
+
+ +
+ +
+ +
+ +
+

欢迎使用 Airhub

+

请输入您的手机号验证登录

+ +
+
+ +86 + +
+
+ +
+
+ + +
+
+ + +
+
+
+ + +
+ + +
+
+
服务协议
+
+ 请先阅读并同意《用户协议》《隐私政策》,以便为您提供更好的服务。 +
+
+ + +
+
+
+ + + + + \ No newline at end of file diff --git a/notifications.html b/notifications.html new file mode 100644 index 0000000..503c34d --- /dev/null +++ b/notifications.html @@ -0,0 +1,399 @@ + + + + + + + Airhub - 消息通知 + + + + + + +
+ +
+
+
+
+ + + + + +
+
+ + +
+
+
+ + + + + + +
+
+
+ 系统更新 + 10:30 +
+
Airhub V1.2.0 版本更新已准备就绪
+
+ +
+
+
+
+

Airhub V1.2.0 版本更新说明:

+
    +
  • 新增"喂养指南"功能,现在您可以查看详细的电子宠物养成手册了。
  • +
  • 优化了设备连接的稳定性,修复了部分机型搜索不到设备的问题。
  • +
  • 提升了整体界面的流畅度,增加了更多微交互动画。
  • +
+

建议您连接 Wi-Fi 后进行更新,以获得最佳体验。

+
+
+
+ + +
+
+
+ 🎁 +
+
+
+ 新春活动 + 昨天 +
+
领取您的新春限定水豚皮肤"招财进宝"
+
+ +
+
+
+

🎉 新春限定皮肤上线啦!

+

为了庆祝即将到来的春节,我们特别推出了水豚的"招财进宝"限定皮肤。

+ Event Image +

活动亮点:

+
    +
  • 限定版红色唐装外观
  • +
  • 专属的春节互动音效
  • +
  • 限时免费领取的节庆道具
  • +
+

活动截止日期: 2月15日

+
+
+
+ + +
+
+
+ + + + + + +
+
+
+ 新设备绑定 + 1月20日 +
+
您的新设备"Airhub_5G"已成功绑定
+
+ +
+
+
+

恭喜!您已成功绑定新设备 Airhub_5G

+

接下来的几步可以帮助您快速上手:

+
    +
  • 前往角色记忆页面,注入您喜欢的角色人格。
  • +
  • 进入设置页面配置您的偏好设置。
  • +
  • 查看帮助中心的入门指南,解锁更多互动玩法。
  • +
+

祝您开启一段奇妙的 AI 陪伴旅程!

+
+
+
+ +
+
+
+ + + + + \ No newline at end of file diff --git a/preview.html b/preview.html new file mode 100644 index 0000000..e1d6ae6 --- /dev/null +++ b/preview.html @@ -0,0 +1,162 @@ + + + + + + + Airhub - iPhone 16 预览 + + + + +
+
📱 iPhone 16 (393 × 852)
+ +
+ + + +
+ +
+
+ +
+
+ +

+ 💡 这是 iPhone 真实尺寸预览。
+ 修改代码后刷新此页面即可看到更新效果。 +

+
+ + + + + \ No newline at end of file diff --git a/privacy.html b/privacy.html new file mode 100644 index 0000000..da551f7 --- /dev/null +++ b/privacy.html @@ -0,0 +1,167 @@ + + + + + + + Airhub - 隐私政策 + + + + + + +
+
+
+
+ +
+

Airhub (以下简称"我们")非常重视您的隐私。本隐私政策(以下简称"本政策")旨在向您说明我们在您使用 Airhub 产品及服务时如何收集、使用、保存、共享和转让您的个人信息,以及您所享有的相关权利。 +

+

请您在使用我们的服务前,仔细阅读并了解本政策。

+ +

1. 我们如何收集您的个人信息

+

为了向您提供优质的服务,我们会按照合法、正当、必要的原则收集您的信息:

+
    +
  • 账号注册信息: 当您注册 Airhub 账号时,我们会收集您的手机号码或电子邮箱地址,用于验证身份及为您提供服务。
  • +
  • 设备连接信息: 当您使用 Airhub 硬件设备时,我们会收集设备的 MAC 地址、SN 序列号、固件版本、IP 地址、Wi-Fi + 信号强度等信息,以便实现设备连接、控制及固件升级功能。
  • +
  • 语音交互数据: 当您使用语音功能与 AI 角色互动时,我们会收集您的语音指令及对话内容。这些数据将用于生成 AI 回复并优化模型效果。您可以选择不保留历史对话记录。 +
  • +
  • 角色记忆数据: 您的 AI 角色养成数据(如亲密度、性格标签、记忆库)存储于云端,以便支持跨设备无缝迁移体验。
  • +
  • 日志信息: 为保障服务安全及运行稳定,我们会收集您的操作日志、错误日志等。
  • +
+ +

2. 我们如何使用您的个人信息

+

我们将收集的信息用于以下用途:

+
    +
  • 提供各项服务: 包括设备配网、远程控制、AI 语音对话等核心功能。
  • +
  • 产品优化: 分析用户使用习惯,改善产品功能和用户体验。
  • +
  • 安全保障: 监测账号异常状态,防范欺诈风险,保障系统安全。
  • +
  • 个性化推荐: 基于您的角色记忆,为您提供更符合您偏好的 AI 个性化回复。
  • +
+ +

3. 信息的共享、转让与公开披露

+

3.1 共享: 我们不会向任何第三方共享您的个人信息,但以下情况除外:

+
    +
  • 获得您的明确同意;
  • +
  • 为了实现核心功能需要与合作伙伴(如云服务提供商、语音识别技术提供商)共享必要信息;
  • +
  • 法律法规规定的情形。
  • +
+

3.2 转让: 我们不会将您的个人信息转让给任何第三方,除非发生合并、收购或破产清算,我们将要求受让方继续受本政策约束。

+ +

4. 信息的存储与保护

+

4.1 存储地点: 我们依照法律法规的规定,将收集的个人信息存储于中华人民共和国境内。

+

4.2 存储期限: 我们仅在实现服务目的所必需的时间内保留您的个人信息。账号注销后,我们将对您的个人信息进行删除或匿名化处理。

+

4.3 安全措施: 我们采用 SSL 加密传输、AES 数据加密存储、严格的访问权限控制等技术措施保护您的信息安全。

+ +

5. 您的权利

+

5.1 访问与更正: 您有权登录 APP 查阅或修改您的个人信息。

+

5.2 删除: 您可以通过【我的-设置-账号安全】申请注销账号。注销后,我们将删除您的所有数据且不可恢复。

+

5.3 撤回同意: 您可以通过设备系统设置关闭相关权限(如麦克风权限),撤回您的授权。

+ +

6. 联系我们

+

如您对本隐私政策有任何疑问或投诉,请发送邮件至 privacy@airhub.com 联系我们。

+ +

更新日期:2025年1月15日

+
+
+ + + \ No newline at end of file diff --git a/products.html b/products.html new file mode 100644 index 0000000..107452c --- /dev/null +++ b/products.html @@ -0,0 +1,405 @@ + + + + + + + Airhub - 选择产品 + + + + + + + +
+
+
+
+
+ + + +
+ +
+
+ AI + 毛绒机芯 +
+
+
毛绒机芯
+
+ + 已连接 +
+
+ + + +
+ + +
+
+ AI + 电子吧唧 +
+
+
电子吧唧 AI
+
+ + 离线 +
+
+ + + +
+ + +
+
+ 普通吧唧 +
+
+
普通吧唧
+
+ + 未配对 +
+
+ + + +
+ + +
+
+ AI + AI手链 +
+
+
AI 手链
+
+ + 点击扫描 +
+
+ + + +
+ + +
+
+ 洛天依 +
+
+
洛天依
+
去下载专属 APP →
+
+ + + +
+
+ + + + + \ No newline at end of file diff --git a/profile-info.html b/profile-info.html new file mode 100644 index 0000000..7b9a749 --- /dev/null +++ b/profile-info.html @@ -0,0 +1,338 @@ + + + + + + + Airhub - 个人信息 + + + + + + +
+
+
+
+ + + +
+
+
+ Avatar +
+
+ + + + + +
+
+ +
+
+ 昵称 + +
+
+ 性别 + + +
+
+ 生日 + 1994-12-09 + +
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/profile.html b/profile.html new file mode 100644 index 0000000..356ab29 --- /dev/null +++ b/profile.html @@ -0,0 +1,361 @@ + + + + + + + Airhub - 我的 + + + + + + +
+ +
+
+
+ + +
+
+ 我的 + +
+ + +
+ +
+
+ Avatar +
+ + +
+ + + +
+ + +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/settings.html b/settings.html new file mode 100644 index 0000000..5216d1e --- /dev/null +++ b/settings.html @@ -0,0 +1,357 @@ + + + + + + + Airhub - 设置 + + + + + + +
+
+
+
+ + + +
+ +
+
账号安全
+
+
+ 📱 + 绑定手机 + 138****3069 + +
+
+ 🔐 + 账号密码 + +
+
+ 📦 + 设备管理 + +
+
+ 🔔 + 推送通知权限 + 已开启 + +
+
+
+ + +
+
关于
+
+
+ 🔄 + 检查更新 + 当前最新 1.0.0 + +
+
+ 💻 + 硬件信息 + +
+
+ 📄 + 用户协议 + +
+
+ 🔒 + 隐私政策 + +
+
+ 📋 + 个人信息收集清单 + +
+
+ 🔗 + 第三方信息共享清单 + +
+
+
+ + +
+
+
+ 🚪 + 退出登录 + +
+
+ ⚠️ + 账号注销 + +
+
+
+ +
+ Airhub v1.0.0
+ © 2025 Airhub Team +
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/sharing-list.html b/sharing-list.html new file mode 100644 index 0000000..28a48b0 --- /dev/null +++ b/sharing-list.html @@ -0,0 +1,192 @@ + + + + + + + Airhub - 第三方共享清单 + + + + + + +
+
+
+
+ +
+

为保障 Airhub + 的相关功能实现与应用安全稳定运行,我们可能会接入第三方提供的软件开发包(SDK)或服务。我们将审慎评估合作方的安全保障能力,并要求其遵守严格的保密协议。

+ + + + + + + +

更新日期:2025年1月15日

+
+
+ + + \ No newline at end of file diff --git a/story-detail.html b/story-detail.html new file mode 100644 index 0000000..564db0e --- /dev/null +++ b/story-detail.html @@ -0,0 +1,639 @@ + + + + + + + Airhub - 故事详情 + + + + + + + + + + + + + +
+ ✨ 操作成功 +
+ + +
+ + + + + + + + +
+ +
+

+ 在遥远的银河系边缘,有一个被星云包裹的神秘茶馆。今天,这里迎来了两位特殊的客人:刚执行完火星探测任务的宇航员波波,和正在追捕暗影怪兽的忍者小次郎。 +

+

+ “这儿的重力好像有点不对劲?”波波飘在半空中,试图抓住飞来飞去的茶杯。小次郎则冷静地倒挂在天花板上,手里紧握着一枚手里剑——其实那是用来切月饼的。 +

+

+ 突然,桌上的魔法茶壶“噗”地一声喷出了七彩烟雾,一只会说话的卡皮巴拉钻了出来:“别打架,别打架,喝了这杯‘银河气泡茶’,我们都是好朋友!” +

+

+ 于是,宇宙中最奇怪的组合诞生了。他们决定,下一站,去黑洞边缘钓星星。 +

+
+ + +
+ + + + +
+ + +
+
+ + + + +
+
+ + +
+
+
+ +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/story-loading.html b/story-loading.html new file mode 100644 index 0000000..ae5068c --- /dev/null +++ b/story-loading.html @@ -0,0 +1,73 @@ + + + + + + + Airhub - 故事生成中 + + + + + + + +
+ + Writing Capybara + + +
构思故事中...
+ + +
+
+
+
+ + + + + \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..4f64601 --- /dev/null +++ b/styles.css @@ -0,0 +1,4408 @@ +/* ======================================== + + Airhub App - Global Styles + + Design: Minimalist, Premium, Apple-inspired + + ======================================== */ + +/* CSS Variables - Design Tokens */ + +:root { + + /* Gradient Colors - Soft pastels matching 首页背景.png */ + + --gradient-pink: #FEF0F5; + + --gradient-lavender: #F5F0FE; + + --gradient-blue: #EEF8FC; + + --gradient-mint: #F0FCFA; + + /* Primary Colors */ + + --primary-purple: #A78BFA; + + --primary-blue: #93C5FD; + + --primary-pink: #F9A8D4; + + --primary-indigo: #6366F1; + + /* Text Colors */ + + --text-primary: #1F2937; + + --text-secondary: #6B7280; + + --text-light: #9CA3AF; + + /* Background */ + + --bg-base: #FAFBFC; + + --bg-card: rgba(255, 255, 255, 0.7); + + /* Shadows */ + + --shadow-soft: 0 4px 24px rgba(0, 0, 0, 0.04); + + --shadow-medium: 0 8px 32px rgba(0, 0, 0, 0.08); + + --shadow-button: 0 8px 32px rgba(167, 139, 250, 0.3); + + /* Animation Timing */ + + --ease-smooth: cubic-bezier(0.4, 0, 0.2, 1); + + --ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1); + + /* ======================================== + + Layout Tokens - Unified Spacing + + ======================================== */ + + --header-padding-top: calc(env(safe-area-inset-top, 20px) + 12px); + + --page-padding-x: 20px; + + --safe-area-bottom: env(safe-area-inset-bottom, 20px); + + --scroll-top-offset: calc(env(safe-area-inset-top, 20px) + 80px); + + /* ======================================== + + Button Quality - Glowing Pill Standard + + ======================================== */ + + --btn-primary-gradient: linear-gradient(90deg, #22D3EE 0%, #3B82F6 35%, #6366F1 65%, #8B5CF6 100%); + + --btn-primary-glow: + + 0 0 15px rgba(34, 211, 238, 0.35), + + 0 0 30px rgba(99, 102, 241, 0.25), + + 0 6px 20px rgba(99, 102, 241, 0.4), + + inset 0 1px 1px rgba(255, 255, 255, 0.3), + + inset 0 -1px 2px rgba(0, 0, 0, 0.1); + +} + +/* Reset & Base */ + +* { + + margin: 0; + + padding: 0; + + box-sizing: border-box; + +} + +html, + +body { + + height: 100%; + + overflow: hidden; + +} + +body { + + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + + background: var(--bg-base); + + color: var(--text-primary); + + -webkit-font-smoothing: antialiased; + + -moz-osx-font-smoothing: grayscale; + + font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; + +} + +/* ======================================== + + App Container + + ======================================== */ + +.app-container { + + width: 100%; + + height: 100vh; + + position: relative; + + overflow: hidden; + + display: flex; + + flex-direction: column; + + z-index: 1; + + /* Above .gradient-bg which is z-index: 0 */ + +} + +/* ======================================== + + Animated Gradient Background - Organic Flow + + ======================================== */ + +.gradient-bg { + + position: fixed; + + top: 0; + + left: 0; + + width: 100%; + + height: 100%; + + z-index: 0; + + overflow: hidden; + + /* Base: soft white with hint of warmth */ + + background: #FEFEFE; + +} + +.gradient-layer { + + position: absolute; + + width: 200%; + + height: 200%; + + top: -50%; + + left: -50%; + + opacity: 1; + + mix-blend-mode: normal; + +} + +/* Layer 1: Soft pink flowing from bottom-left */ + +.layer-1 { + + background: + + radial-gradient(ellipse 120% 80% at 20% 80%, rgba(255, 200, 220, 0.6) 0%, transparent 50%), + + radial-gradient(ellipse 80% 60% at 30% 60%, rgba(255, 180, 200, 0.4) 0%, transparent 40%); + + animation: organicFlow1 20s ease-in-out infinite; + +} + +/* Layer 2: Mint/Cyan flowing from top-right */ + +.layer-2 { + + background: + + radial-gradient(ellipse 100% 70% at 80% 20%, rgba(180, 240, 240, 0.5) 0%, transparent 45%), + + radial-gradient(ellipse 70% 90% at 70% 40%, rgba(200, 245, 245, 0.4) 0%, transparent 40%); + + animation: organicFlow2 25s ease-in-out infinite; + +} + +/* Layer 3: Lavender/Purple flowing diagonally */ + +.layer-3 { + + background: + + radial-gradient(ellipse 90% 80% at 60% 50%, rgba(230, 210, 250, 0.45) 0%, transparent 45%), + + radial-gradient(ellipse 60% 50% at 40% 70%, rgba(240, 220, 255, 0.35) 0%, transparent 35%); + + animation: organicFlow3 30s ease-in-out infinite; + +} + +/* Additional flowing curves using pseudo-elements */ + +.gradient-bg::before { + + content: ''; + + position: absolute; + + width: 150%; + + height: 150%; + + top: -25%; + + left: -25%; + + background: + + radial-gradient(ellipse 60% 40% at 15% 30%, rgba(255, 230, 240, 0.5) 0%, transparent 40%), + + radial-gradient(ellipse 50% 70% at 85% 60%, rgba(220, 250, 250, 0.4) 0%, transparent 35%); + + animation: organicFlow4 35s ease-in-out infinite; + +} + +.gradient-bg::after { + + content: ''; + + position: absolute; + + width: 120%; + + height: 120%; + + top: -10%; + + left: -10%; + + background: + + radial-gradient(ellipse 70% 50% at 50% 90%, rgba(255, 210, 230, 0.35) 0%, transparent 40%), + + radial-gradient(ellipse 40% 60% at 90% 10%, rgba(200, 240, 255, 0.3) 0%, transparent 35%); + + animation: organicFlow5 28s ease-in-out infinite; + +} + +/* Organic Flow Animations */ + +@keyframes organicFlow1 { + + 0%, + + 100% { + + transform: translate(0%, 0%) rotate(0deg) scale(1); + + } + + 25% { + + transform: translate(5%, -8%) rotate(2deg) scale(1.02); + + } + + 50% { + + transform: translate(-3%, 5%) rotate(-1deg) scale(0.98); + + } + + 75% { + + transform: translate(8%, 3%) rotate(1deg) scale(1.01); + + } + +} + +@keyframes organicFlow2 { + + 0%, + + 100% { + + transform: translate(0%, 0%) rotate(0deg) scale(1); + + } + + 33% { + + transform: translate(-8%, 6%) rotate(-2deg) scale(1.03); + + } + + 66% { + + transform: translate(6%, -5%) rotate(2deg) scale(0.97); + + } + +} + +@keyframes organicFlow3 { + + 0%, + + 100% { + + transform: translate(0%, 0%) rotate(0deg) scale(1); + + } + + 20% { + + transform: translate(4%, 7%) rotate(1deg) scale(1.02); + + } + + 40% { + + transform: translate(-6%, -4%) rotate(-2deg) scale(0.98); + + } + + 60% { + + transform: translate(7%, -6%) rotate(2deg) scale(1.01); + + } + + 80% { + + transform: translate(-4%, 5%) rotate(-1deg) scale(0.99); + + } + +} + +@keyframes organicFlow4 { + + 0%, + + 100% { + + transform: translate(0%, 0%) rotate(0deg); + + } + + 50% { + + transform: translate(5%, 5%) rotate(3deg); + + } + +} + +@keyframes organicFlow5 { + + 0%, + + 100% { + + transform: translate(0%, 0%) rotate(0deg); + + } + + 50% { + + transform: translate(-4%, -4%) rotate(-2deg); + + } + +} + +/* ======================================== + + Page Structure + + ======================================== */ + +.page { + + position: absolute; + + top: 0; + + left: 0; + + width: 100%; + + height: 100%; + + display: flex; + + flex-direction: column; + + z-index: 1; + + opacity: 0; + + visibility: hidden; + + transition: opacity 0.5s var(--ease-smooth), visibility 0.5s var(--ease-smooth); + +} + +.page.active { + + opacity: 1; + + visibility: visible; + +} + +/* ======================================== + + Home Page - 2026 Design Optimization + + ======================================== */ + +.home-page { + + justify-content: space-between; + + align-items: center; + + padding: 72px 28px 56px; + +} + +/* Header - Logo */ + +.home-header { + + flex-shrink: 0; + + display: flex; + + justify-content: center; + + align-items: center; + + padding-top: env(safe-area-inset-top, 8px); + +} + +/* Pixel Logo - Tamagotchi/电子宠物机风?*/ + +.pixel-logo { + + position: relative; + + text-align: center; + + opacity: 0; + + animation: fadeInDown 0.8s var(--ease-smooth) 0.3s forwards; + +} + +.pixel-text { + + font-family: 'Press Start 2P', cursive; + + font-size: 28px; + + color: #4B5563; + + letter-spacing: 2px; + + /* Compensate for letter-spacing on last character */ + + margin-right: -6px; + + /* Ensure proper centering */ + + display: inline-block; + + /* Crisp pixel rendering */ + + -webkit-font-smoothing: none; + + -moz-osx-font-smoothing: unset; + + font-smooth: never; + + /* Balanced retro shadow effect (symmetric) */ + + text-shadow: + + 1px 1px 0px rgba(139, 92, 246, 0.25), + + 2px 2px 0px rgba(139, 92, 246, 0.15); + +} + +.pixel-glow { + + position: absolute; + + top: 50%; + + left: 50%; + + transform: translate(-50%, -50%); + + width: 120%; + + height: 200%; + + background: radial-gradient(ellipse, rgba(139, 92, 246, 0.15) 0%, transparent 70%); + + pointer-events: none; + + animation: pixelPulse 3s ease-in-out infinite; + +} + +@keyframes pixelPulse { + + 0%, + + 100% { + + opacity: 0.5; + + } + + 50% { + + opacity: 0.8; + + } + +} + +@keyframes fadeInDown { + + from { + + opacity: 0; + + transform: translateY(-20px); + + } + + to { + + opacity: 1; + + transform: translateY(0); + + } + +} + +/* Main Content - Mascot */ + +.home-content { + + flex: 1; + + display: flex; + + justify-content: center; + + align-items: center; + + position: relative; + +} + +.mascot-container { + + position: relative; + + display: flex; + + justify-content: center; + + align-items: center; + +} + +.mascot { + + width: min(320px, 75vw); + + height: auto; + + object-fit: contain; + + position: relative; + + z-index: 2; + + opacity: 0; + + /* Offset RIGHT 5% to center the body (tail extends to right) */ + + transform: translateX(5%); + + animation: + + fadeInScale 1s var(--ease-bounce) 0.5s forwards, + + float 4s ease-in-out 1.5s infinite; + + filter: drop-shadow(0 24px 48px rgba(167, 139, 250, 0.15)); + +} + +/* Floating animation - 漂浮效果 */ + +@keyframes float { + + 0%, + + 100% { + + transform: translateX(5%) translateY(0); + + } + + 50% { + + transform: translateX(5%) translateY(-12px); + + } + +} + +@keyframes fadeInScale { + + from { + + opacity: 0; + + transform: translateX(5%) scale(0.85); + + } + + to { + + opacity: 1; + + transform: translateX(5%) scale(1); + + } + +} + +.mascot-glow { + + position: absolute; + + width: 240px; + + height: 240px; + + /* Offset to match mascot body center */ + + transform: translateX(5%); + + background: radial-gradient(circle, rgba(249, 168, 212, 0.25) 0%, transparent 70%); + + border-radius: 50%; + + z-index: 1; + + animation: pulseGlow 5s ease-in-out infinite; + +} + +@keyframes pulseGlow { + + 0%, + + 100% { + + transform: scale(1); + + opacity: 0.5; + + } + + 50% { + + transform: scale(1.2); + + opacity: 0.8; + + } + +} + +/* Footer - Connect Button */ + +.home-footer { + + flex-shrink: 0; + + display: flex; + + justify-content: center; + + align-items: center; + + padding-bottom: env(safe-area-inset-bottom, 20px); + +} + +.connect-btn { + + position: relative; + + width: min(300px, 82vw); + + height: 58px; + + border: none; + + border-radius: 29px; + + /* Cyan to purple gradient */ + + background: linear-gradient(90deg, + + #22D3EE 0%, + + /* Cyan */ + + #3B82F6 35%, + + /* Blue */ + + #6366F1 65%, + + /* Indigo */ + + #8B5CF6 100% + /* Purple */ + + ); + + border: none; + + color: #FFFFFF; + + font-family: inherit; + + font-size: 17px; + + font-weight: 600; + + letter-spacing: 0.5px; + + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); + + cursor: pointer; + + overflow: hidden; + + /* Soft glow + inner depth for 3D effect */ + + box-shadow: + + 0 0 15px rgba(34, 211, 238, 0.35), + + 0 0 30px rgba(99, 102, 241, 0.25), + + 0 6px 20px rgba(99, 102, 241, 0.4), + + inset 0 1px 1px rgba(255, 255, 255, 0.3), + + inset 0 -1px 2px rgba(0, 0, 0, 0.1); + + opacity: 0; + + animation: fadeInUp 0.8s var(--ease-smooth) 0.8s forwards; + + transition: transform 0.3s var(--ease-bounce), box-shadow 0.3s var(--ease-smooth); + +} + +/* Subtle top highlight for depth */ + +.connect-btn::before { + + content: ''; + + position: absolute; + + top: 0; + + left: 0; + + right: 0; + + height: 50%; + + background: linear-gradient(180deg, rgba(255, 255, 255, 0.15) 0%, transparent 100%); + + border-radius: 29px 29px 50% 50%; + + pointer-events: none; + +} + +.connect-btn:hover { + + transform: translateY(-2px) scale(1.02); + + box-shadow: + + 0 0 20px rgba(34, 211, 238, 0.45), + + 0 0 40px rgba(99, 102, 241, 0.35), + + 0 10px 30px rgba(99, 102, 241, 0.45), + + inset 0 1px 1px rgba(255, 255, 255, 0.3), + + inset 0 -1px 2px rgba(0, 0, 0, 0.1); + +} + +.connect-btn:active { + + transform: translateY(0) scale(0.98); + +} + +.btn-text { + + position: relative; + + z-index: 50; + + /* Ensure above story section */ + +} + +.btn-shine { + + position: absolute; + + top: 0; + + left: -100%; + + width: 100%; + + height: 100%; + + background: linear-gradient(90deg, + + transparent 0%, + + rgba(255, 255, 255, 0.3) 50%, + + transparent 100%); + + z-index: 1; + + animation: shine 3s ease-in-out infinite; + +} + +@keyframes fadeInUp { + + from { + + opacity: 0; + + transform: translateY(20px); + + } + + to { + + opacity: 1; + + transform: translateY(0); + + } + +} + +@keyframes shine { + + 0% { + + left: -100%; + + } + + 100% { + + left: 100%; + + } + +} + +body { + + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + + background: var(--bg-base); + + color: var(--text-primary); + + -webkit-font-smoothing: antialiased; + + -moz-osx-font-smoothing: grayscale; + + font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; + +} + +/* ======================================== + + Responsive Design + + ======================================== */ + +@media (max-width: 375px) { + + .home-page { + + padding: 48px 20px 40px; + + } + + .logo { + + height: 28px; + + } + + .mascot { + + width: min(240px, 65vw); + + } + + .connect-btn { + + width: min(260px, 85vw); + + height: 52px; + + font-size: 16px; + + } + +} + +@media (min-height: 800px) { + + .home-page { + + padding-top: 80px; + + padding-bottom: 60px; + + } + +} + +/* ======================================== + + Layout Tokens - Unified Spacing + + ======================================== */ + +/* Exactly matching bluetooth.html logic */ + +:root { + + --header-padding-top: calc(env(safe-area-inset-top, 20px) + 48px); + + --page-padding-x: 20px; + + --safe-area-bottom: env(safe-area-inset-bottom, 20px); + + --scroll-top-offset: calc(env(safe-area-inset-top, 20px) + 110px); + + --footer-padding-bottom: calc(env(safe-area-inset-bottom, 20px) + 60px); + + /* ======================================== + + Button Quality - Standard Design + + ======================================== */ + + --btn-primary-gradient: linear-gradient(90deg, #22D3EE 0%, #3B82F6 35%, #6366F1 65%, #8B5CF6 100%); + + --btn-primary-glow: + + 0 0 15px rgba(34, 211, 238, 0.35), + + 0 0 30px rgba(99, 102, 241, 0.25), + + 0 6px 20px rgba(99, 102, 241, 0.4), + + inset 0 1px 1px rgba(255, 255, 255, 0.3), + + inset 0 -1px 2px rgba(0, 0, 0, 0.1); + +} + +/* Reset & Base */ + +* { + + margin: 0; + + padding: 0; + + box-sizing: border-box; + + -webkit-tap-highlight-color: transparent; + +} + +html, + +body { + + height: 100%; + + overflow: hidden; + +} + +body { + + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; + + background: var(--bg-base); + + color: var(--text-primary); + + -webkit-font-smoothing: antialiased; + + -moz-osx-font-smoothing: grayscale; + + font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; + +} + +/* ======================================== + + Shared UI Components - MASTER TEMPLATE + + ======================================== */ + +/* 1. Standard Page Header - CLEAN TRANSPARENT */ + +.page-header { + + position: fixed; + + top: 0; + + left: 0; + + right: 0; + + display: flex; + + align-items: center; + + justify-content: space-between; + + padding: 16px var(--page-padding-x); + + padding-top: var(--header-padding-top); + + z-index: 100; + + /* Removed glass effect as requested */ + + background: transparent; + + backdrop-filter: none; + + -webkit-backdrop-filter: none; + + border-bottom: none; + + pointer-events: none; + +} + +.page-header>* { + + pointer-events: auto; + +} + +/* 2. Standard Back Button */ + +.back-btn { + + width: 40px; + + height: 40px; + + border: none; + + background: rgba(255, 255, 255, 0.6); + + backdrop-filter: blur(10px); + + border-radius: 12px; + + display: flex; + + align-items: center; + + justify-content: center; + + cursor: pointer; + + color: #4B5563; + + transition: all 0.2s var(--ease-smooth); + +} + +.back-btn:hover { + + background: rgba(255, 255, 255, 0.8); + + transform: scale(1.05); + +} + +.back-btn:active { + + transform: scale(0.95); + +} + +/* 3. Standard Page Title */ + +.page-title { + + font-size: 17px; + + font-weight: 600; + + color: #1F2937; + + text-align: center; + + flex: 1; + +} + +/* 4. Layout Spacer */ + +.header-spacer { + + width: 40px; + +} + +/* 5. Standard Page Footer */ + +.page-footer { + + position: fixed; + + bottom: 0; + + left: 0; + + right: 0; + + padding: 20px var(--page-padding-x) var(--footer-padding-bottom); + + display: flex; + + justify-content: center; + + gap: 16px; + + z-index: 100; + + background: transparent; + + pointer-events: none; + +} + +.page-footer>* { + + pointer-events: auto; + +} + +/* ======================================== + + Capybara Theme - Product Specific Specs + + ======================================== */ + +.btn-capybara-primary { + + position: relative; + + pointer-events: auto; + + /* Fix: Overcome parent's pointer-events: none */ + + width: 100%; + + height: 50px; + + border: none; + + border-radius: 25px; + + /* Apricot to Warm Brown Gradient */ + + background: linear-gradient(135deg, #ECCFA8 0%, #C99672 100%); + + color: #4B2404; + + /* Dark Chocolate Text */ + + font-family: inherit; + + font-size: 16px; + + font-weight: 600; + + letter-spacing: 0.5px; + + cursor: pointer; + + overflow: hidden; + + /* Soft Warm Shadow */ + + box-shadow: + + 0 4px 15px rgba(201, 150, 114, 0.35), + + 0 2px 5px rgba(201, 150, 114, 0.2); + + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + + display: flex; + + align-items: center; + + justify-content: center; + + gap: 8px; + +} + +.btn-capybara-primary:active { + + transform: scale(0.98); + + box-shadow: + + 0 2px 8px rgba(201, 150, 114, 0.35); + +} + +/* Modal Confirm Button Override */ + +.glass-modal .modal-btn.confirm { + + background: linear-gradient(135deg, #ECCFA8 0%, #C99672 100%) !important; + + color: #4B2404 !important; + + box-shadow: 0 4px 12px rgba(201, 150, 114, 0.3) !important; + +} + +/* Text Colors */ + +.text-capybara-title { + + color: #4B2404; + +} + +.text-capybara-body { + + color: #4B5563; + +} + +/* 6. Primary Action Button */ + +.primary-btn { + + position: relative; + + padding: 16px 48px; + + border: none; + + border-radius: 29px; + + background: var(--btn-primary-gradient); + + color: white; + + font-size: 17px; + + font-weight: 600; + + cursor: pointer; + + overflow: hidden; + + box-shadow: var(--btn-primary-glow); + + transition: all 0.3s var(--ease-bounce); + + display: flex; + + align-items: center; + + justify-content: center; + +} + +.primary-btn::before { + + content: ''; + + position: absolute; + + top: 0; + + left: 0; + + right: 0; + + height: 50%; + + background: linear-gradient(180deg, rgba(255, 255, 255, 0.15) 0%, transparent 100%); + + border-radius: 29px 29px 50% 50%; + + pointer-events: none; + +} + +.primary-btn:hover { + + transform: translateY(-2px) scale(1.02); + +} + +.primary-btn:active { + + transform: translateY(0) scale(0.98); + +} + +/* 7. Secondary Action Button */ + +.secondary-btn { + + padding: 14px 32px; + + border: 1px solid #E5E7EB; + + background: rgba(255, 255, 255, 0.8); + + backdrop-filter: blur(10px); + + border-radius: 25px; + + font-size: 16px; + + font-weight: 500; + + color: #6B7280; + + cursor: pointer; + + transition: all 0.2s var(--ease-smooth); + +} + +.secondary-btn:hover { + + background: rgba(255, 255, 255, 1); + + border-color: #D1D5DB; + +} + +/* 8. Scroll Container - NO SCROLLBAR + FADE MASK */ + +.scroll-container { + + padding: var(--scroll-top-offset) var(--page-padding-x); + + padding-bottom: calc(var(--safe-area-bottom) + 120px); + + overflow-y: auto; + + -webkit-overflow-scrolling: touch; + + scroll-behavior: smooth; + + height: 100vh; + + /* Hide scrollbar */ + + scrollbar-width: none; + + -ms-overflow-style: none; + + /* Remove mask as user requested "card itself fades out" */ + + mask-image: none; + + -webkit-mask-image: none; + +} + +.scroll-container::-webkit-scrollbar { + + display: none; + +} + +/* Scroll-Driven Animation for Product Cards */ + +.product-card { + + /* Ensure hardware acceleration */ + + will-change: opacity, transform; + + /* Animation: Fade out and scale down as it exits the top */ + + animation: card-exit linear both; + + animation-timeline: view(); + + /* Start fading when top of card is 120px from top (near header), finish when fully overlapping header */ + + animation-range: exit-crossing calc(var(--header-padding-top) + 60px) exit-crossing 0px; + +} + +@keyframes card-exit { + + to { + + opacity: 0; + + transform: scale(0.95); + + } + +} + +.scroll-container::-webkit-scrollbar { + + display: none; + + /* Chrome/Safari */ + +} + +/* ======================================== + + Bluetooth Search Page (RESTORED) + + ======================================== */ + +.bluetooth-page { + + display: flex; + + flex-direction: column; + + min-height: 100vh; + + min-height: 100dvh; + + position: relative; + + z-index: 1; + +} + +.bt-header { + + display: flex; + + align-items: center; + + justify-content: space-between; + + padding: 16px 20px; + + padding-top: calc(env(safe-area-inset-top, 20px) + 48px); + +} + +.bt-title { + + font-size: 17px; + + font-weight: 600; + + color: #1F2937; + +} + +.bt-footer { + + padding: 20px 20px 60px 20px; + + padding-bottom: calc(env(safe-area-inset-bottom, 20px) + 60px); + + margin-top: auto; + + display: flex; + + justify-content: center; + + gap: 16px; + + width: 100%; + + box-sizing: border-box; + +} + +.cancel-btn { + + padding: 14px 32px; + + border: 1px solid #E5E7EB; + + background: rgba(255, 255, 255, 0.8); + + backdrop-filter: blur(10px); + + border-radius: 25px; + + font-size: 16px; + + font-weight: 500; + + color: #6B7280; + + cursor: pointer; + + transition: all 0.2s var(--ease-smooth); + +} + +.connect-device-btn { + + position: relative; + + padding: 16px 48px; + + border: none; + + border-radius: 29px; + + background: linear-gradient(90deg, #22D3EE 0%, #3B82F6 35%, #6366F1 65%, #8B5CF6 100%); + + color: white; + + font-size: 17px; + + font-weight: 600; + + cursor: pointer; + + overflow: hidden; + + box-shadow: var(--btn-primary-glow); + + transition: all 0.3s var(--ease-bounce); + + display: flex; + + align-items: center; + + justify-content: center; + +} + +/* ======================================== + + Bluetooth Search & Feature Components + + (Reusable feature-level components) + + ======================================== */ + +.bt-content { + + flex: 1; + + display: flex; + + flex-direction: column; + + align-items: center; + + justify-content: flex-start; + + padding: 20px; + + padding-top: 40px; + + gap: 24px; + +} + +/* Mystery Box Container */ + +.mystery-box-container { + + position: relative; + + width: 160px; + + height: 160px; + + display: flex; + + align-items: center; + + justify-content: center; + +} + +/* Pixel Mystery Box */ + +.mystery-box { + + width: 120px; + + height: 120px; + + background: transparent; + + position: relative; + + display: flex; + + align-items: center; + + justify-content: center; + + animation: boxIdle 2s ease-in-out infinite; + +} + +.mystery-box-icon { + + width: 120px; + + height: 120px; + + image-rendering: pixelated; + + filter: drop-shadow(0 8px 20px rgba(245, 158, 11, 0.5)); + +} + +.mystery-box.searching { + + animation: boxBounce 0.5s ease-in-out infinite; + +} + +@keyframes boxIdle { + + 0%, + + 100% { + + transform: translateY(0); + + } + + 50% { + + transform: translateY(-5px); + + } + +} + +@keyframes boxBounce { + + 0%, + + 100% { + + transform: translateY(0) scale(1); + + } + + 50% { + + transform: translateY(-15px) scale(1.05); + + } + +} + +@keyframes questionPulse { + + 0%, + + 100% { + + opacity: 1; + + } + + 50% { + + opacity: 0.7; + + } + +} + +@keyframes glowPulse { + + 0%, + + 100% { + + opacity: 0.5; + + transform: scale(1); + + } + + 50% { + + opacity: 0.8; + + transform: scale(1.1); + + } + +} + +/* Device Icon Container */ + +.device-icon-container { + + display: flex; + + align-items: center; + + justify-content: center; + + animation: deviceReveal 0.5s var(--ease-bounce) forwards; + +} + +.device-icon { + + position: relative; + + width: 120px; + + height: 120px; + + /* No card background - standalone pixel icon like mystery box */ + + background: transparent; + + display: flex; + + align-items: center; + + justify-content: center; + + animation: iconFloat 3s ease-in-out infinite; + +} + +.device-icon img { + + width: 120px; + + height: 120px; + + object-fit: contain; + + image-rendering: pixelated; + + filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.15)); + +} + +@keyframes iconFloat { + + 0%, + + 100% { + + transform: translateY(0); + + } + + 50% { + + transform: translateY(-8px); + + } + +} + +.ai-badge { + + position: absolute; + + top: -4px; + + right: -12px; + + background: linear-gradient(135deg, #8B5CF6 0%, #6366F1 100%); + + color: white; + + font-size: 11px; + + font-weight: 700; + + padding: 5px 10px; + + border-radius: 10px; + + box-shadow: 0 4px 12px rgba(139, 92, 246, 0.5); + + z-index: 10; + +} + +@keyframes deviceReveal { + + 0% { + + opacity: 0; + + transform: scale(0.5); + + } + + 100% { + + opacity: 1; + + transform: scale(1); + + } + +} + +/* Search Status */ + +.search-status { + + text-align: center; + + display: flex; + + flex-direction: column; + + align-items: center; + + gap: 12px; + +} + +.status-text { + + font-size: 16px; + + color: #6B7280; + +} + +.loading-dots { + + display: flex; + + gap: 6px; + +} + +.loading-dots span { + + width: 8px; + + height: 8px; + + background: #A78BFA; + + border-radius: 50%; + + animation: dotBounce 1.4s ease-in-out infinite; + +} + +.loading-dots span:nth-child(2) { + + animation-delay: 0.2s; + +} + +.loading-dots span:nth-child(3) { + + animation-delay: 0.4s; + +} + +@keyframes dotBounce { + + 0%, + + 80%, + + 100% { + + transform: scale(0.6); + + opacity: 0.5; + + } + + 40% { + + transform: scale(1); + + opacity: 1; + + } + +} + +/* Found Device Info */ + +.found-device { + + text-align: center; + + animation: fadeInUp 0.5s var(--ease-smooth) forwards; + +} + +.device-name { + + font-size: 20px; + + font-weight: 600; + + color: #1F2937; + + margin-bottom: 4px; + +} + +.device-type { + + font-size: 14px; + + color: #6B7280; + +} + +/* WiFi Config specific styles remains in HTML for now as they are very specific */ + +/* ======================================== + + Story UI (Capybara) + + ======================================== */ + +/* Story Section Container */ + +.story-section { + + flex: 0 0 auto !important; + + /* NEVER expand in parent flex context */ + + display: flex; + + flex-direction: column; + + padding: 0; + + width: 100%; + + height: auto !important; + + /* Only as tall as content */ + + position: relative; + + overflow: visible; + + /* Changed from hidden to allow proper flow */ + + z-index: 5; + + /* Ensure above background */ + + animation: fadeIn 0.3s var(--ease-smooth); + +} + +@keyframes fadeIn { + + from { + + opacity: 0; + + } + + to { + + opacity: 1; + + } + +} + +.story-header-spacer { + + height: 40px; + + /* Reduced from 60px+safe-area since header is hidden */ + + width: 100%; + +} + +/* Bookshelf Container (Horizontal Scroll) */ + +.bookshelf-container { + + /* REMOVED flex: 1 to prevent expansion */ + + display: flex; + + align-items: stretch; + + padding: 30px 20px; + + gap: 20px; + + overflow-x: auto; + + overflow-y: hidden; + + scroll-snap-type: x mandatory; + + scrollbar-width: none; + + height: 600px !important; + + max-height: 600px !important; + + flex: none !important; + +} + +.bookshelf-container::-webkit-scrollbar { + + display: none; + +} + +/* Bookshelf Slide - Each card wrapper */ + +.bookshelf-slide { + + flex: 0 0 85%; + + /* Show 85% to peek next item */ + + scroll-snap-align: center; + + position: relative; + + height: 100%; + + display: flex; + + flex-direction: column; + +} + +/* Add Book Placeholder (Unlock New Bookshelf) */ + +.add-book-placeholder { + + width: 100%; + + height: 100%; + + /* Match parent slide height */ + + border: 2px dashed rgba(201, 150, 114, 0.5); + + border-radius: 20px; + + display: flex; + + flex-direction: column; + + /* Content positioned at LEFT edge so it's visible when peeking */ + + align-items: flex-start; + + justify-content: center; + + padding-left: 8px; + + /* Minimal padding so text is flush with edge */ + + background: rgba(255, 255, 255, 0.4); + + cursor: pointer; + + transition: all 0.3s ease; + +} + +.placeholder-content { + + display: flex; + + flex-direction: column; + + align-items: center; + + /* Center children (icon and text) */ + + gap: 4px; + +} + +/* When placeholder is scrolled into view, center the content */ + +.add-book-placeholder.centered { + + align-items: center; + + padding-left: 0; + +} + +/* After user has unlocked once, hide text when peeking (they know the feature) */ + +.add-book-placeholder.hint-seen .placeholder-text { + + display: none; + +} + +.add-book-placeholder.hint-seen.centered .placeholder-text { + + display: block; + + /* Show text again when fully in view */ + +} + +.add-book-placeholder:hover { + + background: rgba(255, 255, 255, 0.6); + + border-color: rgba(201, 150, 114, 0.8); + +} + +.add-book-placeholder:active { + + transform: scale(0.98); + +} + +.placeholder-text { + + color: #9CA3AF; + + font-size: 11px; + + /* Smaller to fit in peek area */ + + font-weight: 600; + + text-align: center; + + line-height: 1.3; + + white-space: nowrap; + + /* Prevent wrapping */ + +} + +.add-icon { + + font-size: 18px; + + color: #9CA3AF; + + font-weight: 300; + +} + +/* Story Book Card */ + +.story-book { + + min-width: 300px; + + height: 100% !important; + + /* FIX: Force match container height */ + + max-height: 100% !important; + + background: rgba(255, 255, 255, 0.55); + + /* Increased opacity, removed blur */ + + /* backdrop-filter: blur(20px); - REMOVED to prevent edge artifacts */ + + /* -webkit-backdrop-filter: blur(20px); - REMOVED */ + + border: 1px solid rgba(255, 255, 255, 0.6); + + border-radius: 24px; + + padding: 24px; + + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.03); + + /* Reduced shadow */ + + scroll-snap-align: center; + + display: flex; + + flex-direction: column; + + position: relative; + + overflow: hidden; + + /* Clip any overflow */ + +} + +.book-cover { + + display: flex; + + justify-content: space-between; + + align-items: center; + + margin-bottom: 20px; + +} + +.book-title { + + font-size: 18px; + + font-weight: 700; + + color: #1F2937; + +} + +.book-count { + + font-size: 13px; + + font-weight: 600; + + color: #6B7280; + + background: rgba(255, 255, 255, 0.5); + + padding: 4px 10px; + + border-radius: 12px; + +} + +/* Story Grid (2 cols x 5 rows) */ + +.story-grid { + + display: grid; + + grid-template-columns: repeat(2, 1fr); + + grid-template-rows: repeat(5, minmax(0, 1fr)); + + /* Force rows to fit even if content is huge */ + + gap: 12px; + + flex: 1; + + /* Occupy remaining space instead of fixed 100% */ + + height: auto; + + min-height: 0; + + /* Prevent flex overflow */ + +} + +.story-slot { + + background: rgba(255, 255, 255, 0.6); + + border-radius: 12px; + + display: flex; + + align-items: center; + + justify-content: center; + + font-size: 13px; + + color: #9CA3AF; + + font-weight: 600; + + position: relative; + + /* Anchor for absolute children */ + + transition: all 0.2s; + + border: 1px dashed rgba(0, 0, 0, 0.05); + + overflow: hidden; + + /* Ensure content never spills */ + +} + +/* Filled State */ + +/* Filled State (Cover Art) */ + +.story-slot.filled { + + background: white; + + border: none; + + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + + padding: 0; + + /* Full bleed image */ + + overflow: hidden; + + position: relative; + + cursor: pointer; + +} + +/* Empty Interactions */ + +.story-slot.clickable { + + cursor: pointer; + + background: rgba(255, 255, 255, 0.4); + + /* Slightly clearer */ + + position: relative; + + display: flex; + + align-items: center; + + justify-content: center; + +} + +.story-slot.clickable:hover { + + background: rgba(255, 255, 255, 0.6); + + border-color: rgba(255, 255, 255, 0.5); + + transform: translateY(-2px); + + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + +} + +.empty-plus { + + font-size: 24px; + + color: #9CA3AF; + + /* Match placeholder icon color */ + + font-weight: 300; + + opacity: 0.7; + + transition: all 0.2s; + +} + +.story-slot.clickable:hover .empty-plus { + + opacity: 1; + + color: #6B7280; + + /* Darker on hover */ + + transform: scale(1.1); + +} + +.story-cover-img { + + position: absolute; + + /* Remove from flow to prevent pushing height */ + + top: 0; + + left: 0; + + width: 100%; + + height: 100%; + + object-fit: cover; + + /* Scale to fill while maintaining aspect ratio */ + +} + +.story-title-bar { + + position: absolute; + + bottom: 0; + + left: 0; + + right: 0; + + background: rgba(0, 0, 0, 0.6); + + color: white; + + font-size: 10px; + + padding: 4px 6px; + + white-space: nowrap; + + overflow: hidden; + + text-overflow: ellipsis; + + text-align: center; + + backdrop-filter: blur(4px); + +} + +/* Story Actions Container (for centered buttons) */ + +/* Story Actions Container (for centered buttons) */ + +.story-actions-wrapper { + + position: fixed; + + /* Back to Fixed for stability */ + + bottom: 130px; + + left: 0; + + right: 0; + + display: none; + + /* Hidden by default, shown via JS */ + + justify-content: center; + + align-items: center; + + pointer-events: none; + + z-index: 90; + + /* Ensure above content but below modals (100+) */ + + padding: 0 20px; + + transition: opacity 0.3s; + +} + +.story-actions-wrapper.active { + + display: flex; + + pointer-events: auto; + + /* Allow clicks when active */ + +} + +/* Create Button (FAB) - Standardized Style Matching .primary-btn */ + +.create-story-btn { + + pointer-events: auto; + + position: relative; + + /* In flow */ + + bottom: auto; + + left: auto; + + right: auto; + + transform: none; + + /* Standard Structure matching .primary-btn */ + + padding: 16px 48px; + + border: none; + + border-radius: 29px; + + font-size: 17px; + + font-weight: 600; + + cursor: pointer; + + overflow: hidden; + + display: flex; + + align-items: center; + + gap: 8px; + + transition: all 0.3s var(--ease-bounce); + + /* Capybara Theme: "Plush Core" Gradient */ + + background: linear-gradient(135deg, #ECCFA8 0%, #C99672 100%); + + box-shadow: + + 0 0 15px rgba(201, 150, 114, 0.35), + + 0 0 30px rgba(201, 150, 114, 0.25), + + 0 6px 20px rgba(201, 150, 114, 0.4), + + inset 0 1px 1px rgba(255, 255, 255, 0.3), + + inset 0 -1px 2px rgba(0, 0, 0, 0.1); + + color: white; + +} + +/* Top Shine Effect matching .primary-btn::before */ + +.create-story-btn::before { + + content: ''; + + position: absolute; + + top: 0; + + left: 0; + + right: 0; + + height: 50%; + + background: linear-gradient(180deg, rgba(255, 255, 255, 0.15) 0%, transparent 100%); + + border-radius: 29px 29px 50% 50%; + + pointer-events: none; + +} + +.create-story-btn:active { + + transform: scale(0.98); + +} + +.btn-icon { + + font-size: 18px; + + font-weight: 700; + +} + +/* Playback Bar Removed */ + +/* Generator Modal */ + +.generator-modal { + + position: absolute; + + top: 0; + + left: 0; + + width: 100%; + + height: 100%; + + background: rgba(0, 0, 0, 0.4); + + backdrop-filter: blur(4px); + + z-index: 100; + + display: none; + + align-items: flex-end; + + /* Slide from bottom */ + +} + +.generator-modal.active { + + display: flex; + + animation: fadeIn 0.2s ease-out; + +} + +.modal-content { + + width: 100%; + + height: 90%; + + background: #FDF9F3; + + /* Warm Sand/Beige Background */ + + border-radius: 24px 24px 0 0; + + padding: 0; + + box-shadow: 0 -10px 40px rgba(0, 0, 0, 0.1); + + display: flex; + + flex-direction: column; + + overflow: hidden; + + animation: slideUp 0.3s var(--ease-smooth); + +} + +.modal-header { + + padding: 20px 24px 10px; + + display: flex; + + justify-content: space-between; + + align-items: center; + + flex-shrink: 0; + +} + +.modal-header h3 { + + margin: 0; + + font-size: 20px; + + color: #374151; + +} + +.close-btn { + + background: none; + + border: none; + + font-size: 28px; + + color: #9CA3AF; + + cursor: pointer; + + padding: 0; + +} + +/* Selection Preview Removed per User Request */ + +/* Tabs */ + +.generator-tabs { + + display: flex; + + justify-content: space-around; + + padding: 0 24px; + + border-bottom: 1px solid rgba(0, 0, 0, 0.05); + + background: #FDF9F3; + + flex-shrink: 0; + + z-index: 5; + + /* Ensure above content if scrolling */ + +} + +/* Tab Text Stability: Reserve space for bold text to prevent jitter */ + +.gen-tab { + + padding: 12px 16px; + + background: none; + + border: none; + + font-size: 16px; + + color: #9CA3AF; + + font-weight: 500; + + cursor: pointer; + + position: relative; + + transition: color 0.2s; + +} + +/* Bold text simulation using pseudo-element to reserve width */ + +.gen-tab::after { + + display: block; + + content: attr(data-text); + + font-weight: 700; + + height: 1px; + + color: transparent; + + overflow: hidden; + + visibility: hidden; + +} + +.gen-tab.active { + + color: #4B2404; + + font-weight: 700; + +} + +.gen-tab.active::after { + + display: none; + + /* Hide spacer when active, though it's 0 height */ + +} + +/* Active indicator line */ + +.gen-tab.active::before { + + content: ''; + + position: absolute; + + bottom: -1px; + + left: 16px; + + right: 16px; + + height: 3px; + + background: #4B2404; + + border-radius: 3px 3px 0 0; + +} + +/* Scrollable Content */ + +.generator-scroll-content { + + flex: 1; + + overflow-y: auto; + + padding: 24px; + + /* Padding inside the scroll area */ + + padding-bottom: 100px; + + /* Stability Fix: Prevent collapse during tab switch */ + + min-height: 300px; + +} + +.element-section { + + margin-top: 24px; + +} + +.element-section h4 { + + margin: 0 0 12px; + + font-size: 15px; + + color: #6B7280; + + font-weight: 600; + +} + +/* 4-Column Grid */ + +.element-grid-4col { + + display: grid; + + grid-template-columns: repeat(4, 1fr); + + gap: 12px; + +} + +/* Card Style (Reference Match) */ + +.element-card { + + aspect-ratio: 0.85; + + /* Taller rectangle */ + + background: #FFF; + + /* Cream/White */ + + border: none; + + border-radius: 16px; + + display: flex; + + flex-direction: column; + + align-items: center; + + justify-content: center; + + gap: 8px; + + cursor: pointer; + + transition: all 0.2s; + + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.02); + + position: relative; + + /* For badge positioning */ + +} + +/* Hover: Slight lift, no scale */ + +.element-card:hover { + + transform: translateY(-2px); + + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + +} + +/* Remove Focus scaling to prevent "Sticky Zoom" */ + +.element-card:focus { + + outline: none; + + transform: none; + +} + +/* Selected: Scale UP and Persist */ + +.element-card.selected { + + background: #FFF7ED; + + box-shadow: 0 0 0 2px #EA9A3E; + + /* Highlight border */ + + transform: scale(1.05); + + /* Only selected items are big */ + +} + +/* Checkmark Badge (Apple Style - Plush Gradient) */ + +.check-badge { + + position: absolute; + + top: 8px; + + right: 8px; + + width: 20px; + + height: 20px; + + /* Plush Gradient Background */ + + background: linear-gradient(135deg, #ECCFA8 0%, #C99672 100%); + + border-radius: 50%; + + display: flex; + + align-items: center; + + justify-content: center; + + opacity: 0; + + transform: scale(0.5); + + transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1); + + /* Bouncy pop */ + + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + +} + +.element-card.selected .check-badge { + + opacity: 1; + + transform: scale(1); + +} + +.check-badge svg { + + width: 12px; + + height: 12px; + + stroke-width: 3; + +} + +.card-icon { + + font-size: 32px; + + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1)); + +} + +.card-name { + + font-size: 12px; + + color: #4B5563; + + font-weight: 500; + +} + +/* 4-Column Grid */ + +.element-grid-4col { + + display: grid; + + grid-template-columns: repeat(4, 1fr); + + gap: 12px; + +} + +.element-btn { + + aspect-ratio: 1; + + background: white; + + border: 1px solid #E5E7EB; + + border-radius: 16px; + + display: flex; + + flex-direction: column; + + align-items: center; + + justify-content: center; + + gap: 6px; + + cursor: pointer; + + transition: all 0.2s; + +} + +.element-btn:hover { + + border-color: #D1D5DB; + + background: #F9FAFB; + +} + +.element-btn.selected { + + border-color: #EA9A3E; + + background: #FFF7ED; + + box-shadow: 0 0 0 2px rgba(234, 154, 62, 0.2); + +} + +.e-icon { + + font-size: 24px; + +} + +.e-name { + + font-size: 12px; + + color: #4B5563; + +} + +/* Footer & Generate Button */ + +.generator-footer { + + position: absolute; + + bottom: 0; + + left: 0; + + right: 0; + + padding: 20px 24px 30px; + + /* Safe area padding */ + + background: linear-gradient(to top, #FDF9F3 80%, rgba(253, 249, 243, 0)); + + display: flex; + + justify-content: center; + + pointer-events: none; + + /* Let clicks pass through transparent top part */ + +} + +/* Standard Structure matching .create-story-btn */ + +.generate-btn { + + pointer-events: auto; + + width: 100%; + + /* Capybara Theme: "Plush Core" Gradient */ + + background: linear-gradient(135deg, #ECCFA8 0%, #C99672 100%); + + box-shadow: + + 0 0 15px rgba(201, 150, 114, 0.35), + + 0 0 30px rgba(201, 150, 114, 0.25), + + 0 6px 20px rgba(201, 150, 114, 0.4), + + inset 0 1px 1px rgba(255, 255, 255, 0.3), + + inset 0 -1px 2px rgba(0, 0, 0, 0.1); + + border: none; + + color: white; + + padding: 16px 0; + + border-radius: 29px; + + font-size: 18px; + + font-weight: 700; + + display: flex; + + align-items: center; + + justify-content: center; + + gap: 8px; + + cursor: pointer; + + overflow: hidden; + + /* For shine */ + + position: relative; + + transition: transform 0.1s ease-out; + +} + +/* Secondary Button (Outline) - Used for "Rewrite" */ + +.btn-secondary { + + pointer-events: auto; + + width: 100%; + + background: #FFFFFF; + + border: 1px solid #D1D5DB; + + /* Warm Grey Border */ + + color: #4B5563; + + padding: 16px 0; + + border-radius: 29px; + + font-size: 17px; + + font-weight: 600; + + display: flex; + + align-items: center; + + justify-content: center; + + cursor: pointer; + + transition: all 0.2s; + +} + +.btn-secondary:active { + + background: #F3F4F6; + + transform: scale(0.98); + +} + +/* Button Group (2 Columns) */ + +.btn-group-2col { + + display: grid; + + grid-template-columns: 1fr 1.5fr; + + /* 1:1.5 ratio for Rewrite : Save */ + + gap: 16px; + + width: 100%; + +} + +/* Top Shine Effect */ + +.generate-btn::before { + + content: ''; + + position: absolute; + + top: 0; + + left: 0; + + right: 0; + + height: 50%; + + background: linear-gradient(180deg, rgba(255, 255, 255, 0.15) 0%, transparent 100%); + + border-radius: 29px 29px 50% 50%; + + pointer-events: none; + +} + +.generate-btn:active { + + transform: scale(0.98); + +} + +/* --- CORRECTED LOADING & STORY STYLES --- */ + +/* Loading Page */ + +.loading-container { + + display: flex; + + flex-direction: column; + + align-items: center; + + justify-content: center; + + height: 100vh; + + padding: 40px; + + text-align: center; + +} + +.kapi-writing-img { + + width: 200px; + + height: auto; + + margin-bottom: 40px; + + filter: drop-shadow(0 10px 20px rgba(75, 36, 4, 0.15)); + + animation: float 3s ease-in-out infinite; + +} + +.loading-text { + + font-size: 18px; + + font-weight: 600; + + color: #4B2404; + + margin-bottom: 24px; + + min-height: 27px; + + /* Prevent layout shift */ + +} + +.progress-track { + + width: 100%; + + max-width: 280px; + + height: 12px; + + background: rgba(201, 150, 114, 0.2); + + border-radius: 6px; + + overflow: hidden; + +} + +.progress-fill { + + height: 100%; + + width: 0%; + + background: linear-gradient(90deg, #ECCFA8 0%, #C99672 100%); + + border-radius: 6px; + + transition: width 0.3s linear; + +} + +/* Story Detail Page */ + +.story-paper { + + background: #FFFFFF; + + border-radius: 20px; + + padding: 32px 24px; + + margin-top: 24px; + + box-shadow: + + 0 4px 6px rgba(0, 0, 0, 0.02), + + 0 10px 24px rgba(75, 36, 4, 0.08); + + /* Warm shadow */ + + font-size: 16px; + + line-height: 1.8; + + color: #374151; + + text-align: justify; + + position: relative; + +} + +.story-header { + + text-align: center; + + margin-bottom: 24px; + +} + +.story-title { + + font-size: 20px; + + font-weight: 700; + + color: #4B2404; + + margin-bottom: 8px; + +} + +.story-tags { + + display: flex; + + justify-content: center; + + gap: 8px; + + flex-wrap: wrap; + +} + +.story-tag { + + font-size: 12px; + + color: #9CA3AF; + + background: #F9FAFB; + + padding: 4px 8px; + + border-radius: 4px; + +} + +@keyframes slideUp { + + from { + + transform: translateY(100%); + + } + + to { + + transform: translateY(0); + + } + +} + +/* ======================================== + + Magic Animations (Story Save) + + ======================================== */ + +/* 1. Detail Page: Genie Suck Effect */ + +/* 1. Detail Page: Genie Suck Effect */ + +@keyframes genieSuck { + + 0% { + + transform: translate(0px, 0px) scale(1); + + opacity: 1; + + filter: blur(0px); + + } + + 20% { + + /* Anticipation: slightly grow */ + + transform: translate(0px, 0px) scale(1.05); + + } + + 100% { + + /* Suck into bottom-center (assuming button is there) */ + + /* Translate FIRST, then Scale, to ensure full distance movement */ + + transform: translate(var(--tx, 0px), var(--ty, 500px)) scale(0.05); + + opacity: 0; + + filter: blur(10px); + + } + +} + +.genie-effect { + + animation: genieSuck 0.8s cubic-bezier(0.6, -0.28, 0.735, 0.045) forwards; + + transform-origin: center center; + +} + +/* 2. Shelf Page: Book Pop Effect */ + +@keyframes bookPop { + + 0% { + + transform: scale(0); + + opacity: 0; + + } + + 60% { + + transform: scale(1.2); + + opacity: 1; + + } + + 80% { + + transform: scale(0.9); + + } + + 100% { + + transform: scale(1); + + opacity: 1; + + } + +} + +.book-pop-in { + + animation: bookPop 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; + +} + +/* 2b. Magic Dust Particles */ + +@keyframes sparkleFloat { + + 0% { + + transform: translate(0, 0) scale(0); + + opacity: 0; + + } + + 50% { + + opacity: 1; + + } + + 100% { + + transform: translate(var(--tx), var(--ty)) scale(0); + + opacity: 0; + + } + +} + +/* 3. Picture Book Video Styles */ + +.tab-switch-container { + + display: flex; + + justify-content: center; + + margin-bottom: 16px; + + z-index: 10; + + position: relative; + +} + +.tab-switch { + + background: white; + + padding: 4px; + + border-radius: 20px; + + display: flex; + + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05); + +} + +.switch-btn { + + padding: 6px 16px; + + border-radius: 16px; + + border: none; + + background: transparent; + + font-size: 14px; + + font-weight: 600; + + color: #6B7280; + + cursor: pointer; + + transition: all 0.2s; + +} + +.switch-btn.active { + + background: #FFF1F2; + + /* Pink-50 */ + + color: #BE123C; + + /* Rose-700 */ + + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + +} + +.video-view-container { + + width: 100%; + + aspect-ratio: 3 / 4; + + background: #F3F4F6; + + /* Changed from #000 to match light theme */ + + border-radius: 16px; + + overflow: hidden; + + position: relative; + + display: none; + + /* Hidden by default */ + + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + +} + +.story-video { + + width: 100%; + + height: 100%; + + object-fit: cover; + + display: block; + +} + +.video-overlay { + + position: absolute; + + top: 0; + + left: 0; + + width: 100%; + + height: 100%; + + display: flex; + + align-items: center; + + justify-content: center; + + background: rgba(0, 0, 0, 0.2); + + transition: opacity 0.3s; + + cursor: pointer; + +} + +.video-overlay.hidden { + + opacity: 0; + + pointer-events: none; + + /* Let clicks pass to video only if native controls used, but we are using container click */ + +} + +.play-btn-circle { + + width: 64px; + + height: 64px; + + background: rgba(255, 255, 255, 0.25); + + backdrop-filter: blur(4px); + + border-radius: 50%; + + display: flex; + + align-items: center; + + justify-content: center; + + color: white; + + font-size: 24px; + + border: 1px solid rgba(255, 255, 255, 0.5); + + transition: transform 0.2s; + +} + +.play-btn-circle:active { + + transform: scale(0.95); + +} + +/* Loading Mask */ + +.loading-mask { + + position: fixed; + + top: 0; + + left: 0; + + width: 100%; + + height: 100%; + + background: rgba(255, 255, 255, 0.9); + + z-index: 9999; + + display: flex; + + flex-direction: column; + + align-items: center; + + justify-content: center; + + opacity: 0; + + pointer-events: none; + + transition: opacity 0.3s; + +} + +.loading-mask.active { + + opacity: 1; + + pointer-events: auto; + +} + +.loader-spinner { + + width: 40px; + + height: 40px; + + border: 4px solid #E5E7EB; + + border-top: 4px solid #F43F5E; + + border-radius: 50%; + + animation: spin 1s linear infinite; + + margin-bottom: 16px; + +} + +@keyframes spin { + + 0% { + + transform: rotate(0deg); + + } + + 100% { + + transform: rotate(360deg); + + } + +} + +/* 4. Custom Glass Modal */ + +.modal-overlay { + + position: fixed; + + top: 0; + + left: 0; + + width: 100%; + + height: 100%; + + background: rgba(0, 0, 0, 0.4); + + backdrop-filter: blur(4px); + + z-index: 10000; + + display: flex; + + align-items: center; + + justify-content: center; + + opacity: 0; + + pointer-events: none; + + transition: opacity 0.3s; + +} + +.modal-overlay.active { + + opacity: 1; + + pointer-events: auto; + +} + +.glass-modal { + + width: 80%; + + max-width: 320px; + + background: rgba(255, 255, 255, 0.95); + + border-radius: 20px; + + padding: 24px; + + text-align: center; + + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2); + + transform: scale(0.9); + + transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); + +} + +.modal-overlay.active .glass-modal { + + transform: scale(1); + +} + +.modal-title { + + font-size: 18px; + + font-weight: 700; + + color: #4B2404; + + margin-bottom: 12px; + +} + +.modal-desc { + + font-size: 14px; + + color: #6B7280; + + margin-bottom: 24px; + + line-height: 1.5; + +} + +.modal-actions { + + display: flex; + + gap: 12px; + +} + +.modal-btn { + + flex: 1; + + padding: 10px; + + border-radius: 12px; + + border: none; + + font-size: 14px; + + font-weight: 600; + + cursor: pointer; + +} + +.modal-btn.cancel { + + background: #F3F4F6; + + color: #6B7280; + +} + +.modal-btn.confirm { + + background: linear-gradient(135deg, #FF9A9E 0%, #FECFEF 100%); + + color: #B91C1C; + +} + +/* Toast Message */ + +.custom-toast { + + position: fixed; + + top: 50%; + + left: 50%; + + transform: translate(-50%, -50%) scale(0.9); + + background: rgba(0, 0, 0, 0.8); + + color: white; + + padding: 12px 24px; + + border-radius: 30px; + + font-size: 14px; + + z-index: 10001; + + opacity: 0; + + pointer-events: none; + + transition: all 0.3s; + +} + +.custom-toast.active { + + opacity: 1; + + transform: translate(-50%, -50%) scale(1); + +} + +/* --- Settings Page (V5 - Modals & Fixes) --- */ +.settings-view { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + /* Warm Capybara Theme Background */ + background: linear-gradient(135deg, #FEF5EC 0%, #FDF2F8 100%); + z-index: 2000; + display: flex; + flex-direction: column; + animation: slideUp 0.3s cubic-bezier(0.2, 0.8, 0.2, 1); + overflow: hidden; +} + +@keyframes slideUp { + from { + transform: translateY(100%); + } + + to { + transform: translateY(0); + } +} + +/* Header - Matching Main Page Header Height */ +.settings-header { + background: transparent !important; + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; + border-bottom: none !important; + + /* Match main page: padding-top: calc(safe-area + 48px) */ + padding-top: calc(env(safe-area-inset-top, 20px) + 48px) !important; + padding-bottom: 16px !important; + position: absolute !important; + top: 0; + left: 0; + right: 0; + z-index: 9999; + pointer-events: none; + /* 让点击穿透 header 背景 */ + + display: flex !important; + align-items: center !important; + justify-content: space-between !important; + padding-left: 20px !important; + padding-right: 20px !important; +} + +/* 确保 header 内的按钮和标题可点击 */ +.settings-header>* { + pointer-events: auto; +} + +.settings-title { + font-size: 16px !important; + font-weight: 600 !important; + color: #1F2937 !important; +} + +/* Content Area - Gradient Fading Mask (Standard: PRD 3.3.2) */ +.settings-content { + flex: 1; + overflow-y: auto; + /* Gradient Mask Scroll Standard: padding-top = safe-area + 120px */ + padding-top: calc(env(safe-area-inset-top, 20px) + 120px) !important; + padding-left: 20px; + padding-right: 20px; + padding-bottom: 100px; + background: transparent; + scrollbar-width: none; + /* Gradient Mask Scroll Standard: transparent 0-100px, fade 100-130px */ + -webkit-mask-image: linear-gradient(to bottom, transparent 0px, transparent 100px, black 130px, black 100%); + mask-image: linear-gradient(to bottom, transparent 0px, transparent 100px, black 130px, black 100%); + pointer-events: auto !important; + position: relative; + z-index: 1; +} + +.settings-content::-webkit-scrollbar { + display: none; +} + +/* Settings Group */ +.settings-group-title { + font-size: 12px; + color: #8B5E3C; + margin-bottom: 8px; + margin-left: 16px; + margin-top: 24px; + font-weight: 500; +} + +.settings-group { + background: rgba(255, 255, 255, 0.8); + border-radius: 20px; + margin-bottom: 20px; + box-shadow: 0 4px 16px rgba(139, 94, 60, 0.04); + overflow: hidden; +} + +.settings-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + min-height: 48px; + background: transparent; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} + +.settings-item:last-child { + border-bottom: none; +} + +.settings-item.clickable { + cursor: pointer; +} + +.settings-item.clickable:active { + background: rgba(255, 255, 255, 0.5); +} + +/* Typography */ +.item-label { + font-size: 15px; + color: #4B5563; + font-weight: 400; +} + +.item-value { + font-size: 14px; + color: #9CA3AF; + display: flex; + align-items: center; + gap: 6px; +} + +.arrow { + font-size: 18px; + color: #D1D5DB; +} + +.item-text-col { + display: flex; + flex-direction: column; + justify-content: center; + gap: 4px; +} + +.item-desc { + font-size: 11px; + color: #9CA3AF; + line-height: 1.3; +} + +.settings-item.warning .item-label { + color: #EF4444; +} + +/* Toggle */ +.toggle-switch { + width: 50px; + height: 30px; + background: #E5E7EB; + border-radius: 15px; + position: relative; + cursor: pointer; + transition: background 0.3s; +} + +.toggle-switch.active { + background: #FFB088; +} + +.toggle-knob { + width: 26px; + height: 26px; + background: white; + border-radius: 50%; + position: absolute; + top: 2px; + left: 2px; + transition: transform 0.3s; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.toggle-switch.active .toggle-knob { + transform: translateX(20px); +} + +/* Volume Slider */ +.settings-item.column { + flex-direction: column; + align-items: stretch; + gap: 12px; + padding-bottom: 16px; +} + +.volume-row { + display: flex; + align-items: center; + width: 100%; + height: 32px; +} + +.volume-slider { + flex: 1; + -webkit-appearance: none; + appearance: none; + height: 24px; + margin: 0 8px; + background: transparent; + cursor: grab; + width: 100%; +} + +.volume-slider::-webkit-slider-runnable-track { + width: 100%; + height: 4px; + background: #E5E5EA; + border-radius: 2px; +} + +.volume-slider::-webkit-slider-thumb { + -webkit-appearance: none; + height: 24px; + width: 24px; + background: white; + border-radius: 50%; + box-shadow: 0 2px 6px rgba(139, 94, 60, 0.2); + margin-top: -10px; + /* (4-24)/2 = -10 */ +} + +.vol-icon { + font-size: 16px; + color: #9CA3AF; + width: 24px; + text-align: center; +} + +/* --- Settings Page Additional Modal Styles --- */ +/* Note: Base .modal-overlay and .glass-modal are defined earlier in file */ + +/* Settings Modal Button Overrides */ +.modal-btn { + flex: 1; + padding: 12px 0; + border-radius: 12px; + border: none; + font-size: 16px; + font-weight: 500; + cursor: pointer; + position: relative; + z-index: 2220; +} + +.modal-btn.cancel { + background: #F3F4F6; + color: #6B7280; +} + +.modal-btn.confirm { + background: linear-gradient(135deg, #FFB088 0%, #FF8E53 100%); + color: white; +} + +.modal-btn.danger { + background: #EF4444; + color: white; +} + +.modal-btn.secondary { + background: #F3F4F6; + color: #4B5563; +} + +.modal-input { + width: 100%; + padding: 12px; + margin-bottom: 20px; + box-sizing: border-box; + border: 1px solid #E5E7EB; + border-radius: 12px; + font-size: 16px; + background: #F9FAFB; + outline: none; +} + +.modal-input:focus { + border-color: #FFB088; + background: white; +} + +/* Unbind Styles */ +.unbind-header { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin-bottom: 12px; +} + +.warn-icon svg { + width: 24px; + color: #EF4444; +} + +.unbind-title { + font-size: 18px; + font-weight: 600; + color: #1F2937; +} + +.highlight-text { + color: #EF4444; + font-weight: 600; +} \ No newline at end of file diff --git a/wifi-config.html b/wifi-config.html new file mode 100644 index 0000000..a7396a2 --- /dev/null +++ b/wifi-config.html @@ -0,0 +1,502 @@ + + + + + + + Airhub - WiFi配网 + + + + + + + + + +
+
+
+
+
+ +
+
+ +

WiFi配网

+
+
+ +
+ +
+
+
+
+
+
+ + +
+ + + + + + +

选择WiFi网络

+

设备需要连接WiFi以使用AI功能

+ +
+
+ Home_5G + Strong +
+
+ Office_WiFi + Good +
+
+ Guest_Network + Weak +
+
+
+ + +
+ + + + + +

网络名称

+

请输入WiFi密码

+ + +
+ + +
+ + + + + + +

正在配网...

+ +
+
+
+
+

正在连接WiFi...

+
+
+ + +
+ +
+ Device +
+
+

配网成功!

+

设备已成功连接到网络

+
+
+ +
+ + +
+
+ + + + + + + \ No newline at end of file diff --git a/这里是说明.md b/这里是说明.md new file mode 100644 index 0000000..0d6215a --- /dev/null +++ b/这里是说明.md @@ -0,0 +1 @@ +之后的APP文件和这个项目放在这里