diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7b274cb..42fa61b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,14 @@ { "permissions": { "allow": [ - "Bash(git add:*)" + "Bash(git add:*)", + "Read(//Users/break/.agents/skills/impeccable/**)", + "Bash(node .agents/skills/impeccable/scripts/cleanup-deprecated.mjs)", + "Bash(node *)", + "Bash(npm run *)", + "Bash(curl -s http://localhost:5173)", + "Bash(git checkout *)", + "Bash(git commit -m ' *)" ], "deny": [], "ask": [] diff --git a/.impeccable.md b/.impeccable.md new file mode 100644 index 0000000..3af2709 --- /dev/null +++ b/.impeccable.md @@ -0,0 +1,30 @@ +# Impeccable Design Context + +## Design Context + +### Users +- **Who**: Regular consumers on mobile who purchased a portable WiFi/MiFi device +- **Context**: Using the app on their phone to check data usage, manage their device settings, top up balance, and ensure connectivity while traveling +- **Job to be done**: Quick access to device status, data usage, and account management with minimal friction + +### Brand Personality +- **Voice**: Clean, confident, trustworthy +- **Tone**: Fast and efficient, modern and sleek, friendly and helpful, reliable and secure +- **3-word personality**: Modern · Trustworthy · Effortless +- **Emotional goals**: Users should feel in control of their connectivity, confident their device is working, and that managing it is simple + +### Aesthetic Direction +- **Visual tone**: Minimal and refined — clean layouts with purposeful use of space +- **References**: Consumer electronics (Apple, Tile, Anker) + Minimalist app interfaces +- **Anti-references**: Cluttered admin dashboards, AI slop aesthetics, legacy carrier apps, overly playful designs +- **Theme**: Light mode only — bright, clean, accessible +- **Colors**: Blue-based palette (iOS-like primary blue #0A84FF) with the existing color system as a base + +### Design Principles +1. **Clarity first**: Every element earns its place. No decorative clutter. +2. **Confidence through space**: Generous whitespace creates premium feel. Tight grouping shows related content. +3. **Light and bright**: Light theme only. White surfaces, not gray. Blue accent for primary actions. +4. **Progressive disclosure**: Simple by default, reveal complexity on demand (e.g., WiFi settings behind a tap). +5. **Fast interactions**: Optimistic UI. Updates feel instant. Loading states are minimal. +6. **Typography hierarchy**: Bold headings, readable body. Clear contrast between levels. +7. **Trust signals**: Online/offline status, battery level, data usage — at a glance, always visible. diff --git a/App.vue b/App.vue index a177e03..ede3452 100644 --- a/App.vue +++ b/App.vue @@ -8,85 +8,86 @@ @import "uview-plus/index.scss"; :root { - /* 主色 */ - --primary: #0A84FF; + /* 主色 - iOS Blue 家族 */ + --primary: #007AFF; --primary-light: #5AC8FA; - --primary-dark: #0066CC; + --primary-dark: #0056CC; + --primary-tint: rgba(0, 122, 255, 0.08); - /* 辅助色 */ - --success: #30D158; - --success-light: rgba(48, 209, 88, 0.12); - --warning: #FF9F0A; - --warning-light: rgba(255, 159, 10, 0.12); - --danger: #FF453A; - --danger-light: rgba(255, 69, 58, 0.12); - --info: #5E5CE6; + /* 语义色 */ + --success: #34C759; + --success-light: rgba(52, 199, 89, 0.1); + --warning: #FF9500; + --warning-light: rgba(255, 149, 0, 0.1); + --danger: #FF3B30; + --danger-light: rgba(255, 59, 48, 0.1); + --info: #5856D6; - /* 扩展色 */ - --purple: #BF5AF2; - --pink: #FF375F; - --teal: #64D2FF; - --indigo: #5E5CE6; - - /* 中性色 - 灰阶 */ - --gray-100: #F2F2F7; - --gray-200: #E5E5EA; - --gray-300: #D1D1D6; - --gray-400: #C7C7CC; - --gray-500: #AEAEB2; - --gray-600: #8E8E93; - --gray-700: #636366; - --gray-800: #3C3C43; - --gray-900: #000000; - - /* 背景色 */ - --bg-primary: #FFFFFF; - --bg-secondary: #F2F2F7; - --bg-tertiary: #FFFFFF; - --bg-grouped: #F2F2F7; + /* 中性色 - 微妙的蓝色底色 */ + --gray-50: #F8F9FC; + --gray-100: #EEF1F5; + --gray-200: #D9DDE6; + --gray-300: #B8C0D0; + --gray-400: #8E95A8; + --gray-500: #6B7280; + --gray-600: #4B5563; + --gray-700: #374151; + --gray-800: #1F2937; + --gray-900: #111827; /* 文字色 */ - --text-primary: #000000; - --text-secondary: #3C3C43; - --text-tertiary: rgba(60, 60, 67, 0.6); - --text-quaternary: rgba(60, 60, 67, 0.3); + --text-primary: var(--gray-900); + --text-secondary: var(--gray-600); + --text-tertiary: var(--gray-400); --text-inverse: #FFFFFF; - /* 阴影 */ - --shadow-small: 0 2rpx 4rpx rgba(0, 0, 0, 0.06); - --shadow-medium: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); - --shadow-large: 0 8rpx 24rpx rgba(0, 0, 0, 0.12); - --shadow-extra-large: 0 16rpx 48rpx rgba(0, 0, 0, 0.16); + /* 背景色 */ + --bg-base: #F5F7FA; + --bg-surface: #FFFFFF; + --bg-elevated: #FFFFFF; + + /* 阴影 - 更细腻 */ + --shadow-xs: 0 1rpx 2rpx rgba(0, 0, 0, 0.04); + --shadow-sm: 0 2rpx 8rpx rgba(0, 0, 0, 0.06); + --shadow-md: 0 4rpx 16rpx rgba(0, 0, 0, 0.08); + --shadow-lg: 0 8rpx 32rpx rgba(0, 0, 0, 0.10); + --shadow-xl: 0 16rpx 48rpx rgba(0, 0, 0, 0.12); /* 圆角 */ - --radius-small: 8rpx; - --radius-medium: 16rpx; - --radius-large: 24rpx; - --radius-extra-large: 32rpx; + --radius-xs: 6rpx; + --radius-sm: 10rpx; + --radius-md: 16rpx; + --radius-lg: 24rpx; + --radius-xl: 32rpx; --radius-full: 9999rpx; - /* 间距 */ - --space-xs: 8rpx; - --space-sm: 16rpx; - --space-md: 24rpx; - --space-lg: 32rpx; - --space-xl: 40rpx; - --space-2xl: 48rpx; + /* 间距 - 4pt 基准 */ + --space-1: 4rpx; + --space-2: 8rpx; + --space-3: 12rpx; + --space-4: 16rpx; + --space-5: 20rpx; + --space-6: 24rpx; + --space-8: 32rpx; + --space-10: 40rpx; + --space-12: 48rpx; + --space-16: 64rpx; /* 过渡 */ - --transition-fast: 0.2s ease-out; - --transition-normal: 0.3s ease-out; - --transition-slow: 0.5s ease-out; - - /* 边框色 */ - --border-color: var(--gray-200); - --border-light: var(--gray-100); + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + --duration-fast: 0.15s; + --duration-normal: 0.25s; + --duration-slow: 0.4s; } * { margin: 0; padding: 0; - font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "MiSans", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + box-sizing: border-box; + font-family: "MiSans", -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } ul, li { @@ -95,29 +96,31 @@ page { color: var(--text-primary); - background-color: var(--bg-secondary); + background-color: var(--bg-base); + min-height: 100vh; } .container { display: flex; flex-direction: column; - gap: 20rpx; - padding: 30rpx 20rpx; + gap: var(--space-4); + padding: var(--space-5) var(--space-4); max-width: 750rpx; margin: 0 auto; } .card { - background: var(--bg-primary); - border-radius: var(--radius-medium); - padding: var(--space-md); - border: 1rpx solid var(--border-color); - transition: all var(--transition-fast); + background: var(--bg-surface); + border-radius: var(--radius-lg); + padding: var(--space-5); + box-shadow: var(--shadow-sm); + transition: box-shadow var(--duration-normal) var(--ease-out), + transform var(--duration-normal) var(--ease-out); } .card-elevated { - box-shadow: var(--shadow-large); - transform: translateY(-4rpx); + box-shadow: var(--shadow-lg); + transform: translateY(-2px); } .card-interactive { @@ -126,103 +129,120 @@ -webkit-tap-highlight-color: transparent; } - .title { + /* 排版系统 */ + .text-display { + font-size: 48rpx; font-weight: 700; - font-size: 34rpx; - line-height: 1.2; + line-height: 1.1; + letter-spacing: -0.03em; color: var(--text-primary); - letter-spacing: -0.02em; } - .subtitle { + .text-title { + font-size: 36rpx; font-weight: 600; - font-size: 28rpx; - line-height: 1.3; - color: var(--text-secondary); + line-height: 1.2; + letter-spacing: -0.02em; + color: var(--text-primary); } - .body { + .text-heading { + font-size: 30rpx; + font-weight: 600; + line-height: 1.25; + letter-spacing: -0.01em; + color: var(--text-primary); + } + + .text-body { + font-size: 28rpx; font-weight: 400; - font-size: 28rpx; - line-height: 1.4; + line-height: 1.5; color: var(--text-secondary); } - .caption { - font-weight: 500; + .text-caption { font-size: 24rpx; - line-height: 1.3; + font-weight: 500; + line-height: 1.4; color: var(--text-tertiary); } - .mt-xs { margin-top: var(--space-xs); } - .mt-sm { margin-top: var(--space-sm); } - .mt-md { margin-top: var(--space-md); } - .mt-lg { margin-top: var(--space-lg); } - .mt-xl { margin-top: var(--space-xl); } - .mt-30 { margin-top: 30rpx; } - .mt-20 { margin-top: 20rpx; } - - .mb-xs { margin-bottom: var(--space-xs); } - .mb-sm { margin-bottom: var(--space-sm); } - .mb-md { margin-bottom: var(--space-md); } - .mb-lg { margin-bottom: var(--space-lg); } - .mb-xl { margin-bottom: var(--space-xl); } - - .p-xs { padding: var(--space-xs); } - .p-sm { padding: var(--space-sm); } - .p-md { padding: var(--space-md); } - .p-lg { padding: var(--space-lg); } - .p-xl { padding: var(--space-xl); } - - .flex-row { display: flex; align-items: center; } - .flex-row-g8 { display: flex; align-items: center; gap: var(--space-xs); } - .flex-row-g16 { display: flex; align-items: center; gap: var(--space-sm); } - .flex-row-g20 { display: flex; align-items: center; gap: 20rpx; } - .flex-row-g24 { display: flex; align-items: center; gap: var(--space-md); } - .flex-row-sb { display: flex; align-items: center; justify-content: space-between; } - .flex-row-center { display: flex; align-items: center; justify-content: center; } - - .flex-col { display: flex; flex-direction: column; } - .flex-col-g8 { display: flex; flex-direction: column; gap: var(--space-xs); } - .flex-col-g16 { display: flex; flex-direction: column; gap: var(--space-sm); } - .flex-col-g20 { display: flex; flex-direction: column; gap: 20rpx; } - .flex-col-g24 { display: flex; flex-direction: column; gap: var(--space-md); } - .flex-col-center-g20 { display: flex; flex-direction: column; gap: 20rpx; align-items: center; justify-content: center; } - .flex-col-center { display: flex; flex-direction: column; align-items: center; justify-content: center; } - - .w-100 { width: 100%; } - .h-100 { height: 100%; } - - .interactive { - transition: all var(--transition-normal); - cursor: pointer; - -webkit-tap-highlight-color: transparent; - } - - .button-apple { - background: var(--primary); - color: var(--text-inverse); - border: none; - border-radius: var(--radius-medium); - padding: var(--space-sm) var(--space-md); - font-weight: 600; - font-size: 28rpx; - transition: all var(--transition-normal); - cursor: pointer; - -webkit-tap-highlight-color: transparent; - } - - .tag-apple { - display: inline-flex; - align-items: center; - padding: var(--space-xs) var(--space-sm); - background: var(--gray-100); - color: var(--text-secondary); - border-radius: var(--radius-small); + .text-label { font-size: 22rpx; font-weight: 600; + line-height: 1.3; letter-spacing: 0.02em; + color: var(--text-tertiary); + text-transform: uppercase; + } + + /* 间距 */ + .mt-1 { margin-top: var(--space-1); } + .mt-2 { margin-top: var(--space-2); } + .mt-3 { margin-top: var(--space-3); } + .mt-4 { margin-top: var(--space-4); } + .mt-5 { margin-top: var(--space-5); } + .mt-6 { margin-top: var(--space-6); } + .mt-8 { margin-top: var(--space-8); } + .mt-10 { margin-top: var(--space-10); } + + .mb-1 { margin-bottom: var(--space-1); } + .mb-2 { margin-bottom: var(--space-2); } + .mb-3 { margin-bottom: var(--space-3); } + .mb-4 { margin-bottom: var(--space-4); } + .mb-5 { margin-bottom: var(--space-5); } + .mb-6 { margin-bottom: var(--space-6); } + .mb-8 { margin-bottom: var(--space-8); } + + .p-4 { padding: var(--space-4); } + .p-5 { padding: var(--space-5); } + .p-6 { padding: var(--space-6); } + + /* Flex */ + .flex-row { display: flex; align-items: center; } + .flex-row-g2 { display: flex; align-items: center; gap: var(--space-2); } + .flex-row-g3 { display: flex; align-items: center; gap: var(--space-3); } + .flex-row-g4 { display: flex; align-items: center; gap: var(--space-4); } + .flex-row-g5 { display: flex; align-items: center; gap: var(--space-5); } + .flex-row-g6 { display: flex; align-items: center; gap: var(--space-6); } + .flex-row-sb { display: flex; align-items: center; justify-content: space-between; } + .flex-row-center { display: flex; align-items: center; justify-content: center; } + .flex-row-end { display: flex; align-items: center; justify-content: flex-end; } + + .flex-col { display: flex; flex-direction: column; } + .flex-col-g2 { display: flex; flex-direction: column; gap: var(--space-2); } + .flex-col-g3 { display: flex; flex-direction: column; gap: var(--space-3); } + .flex-col-g4 { display: flex; flex-direction: column; gap: var(--space-4); } + .flex-col-g5 { display: flex; flex-direction: column; gap: var(--space-5); } + .flex-col-g6 { display: flex; flex-direction: column; gap: var(--space-6); } + .flex-col-center { display: flex; flex-direction: column; align-items: center; justify-content: center; } + + .w-full { width: 100%; } + .h-full { height: 100%; } + + /* 交互状态 */ + .interactive { + transition: all var(--duration-normal) var(--ease-out); + cursor: pointer; + -webkit-tap-highlight-color: transparent; + } + + /* 标签 */ + .tag { + display: inline-flex; + align-items: center; + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-full); + font-size: 22rpx; + font-weight: 600; + line-height: 1.4; + letter-spacing: 0.01em; + + &.tag-default { + background: var(--gray-100); + color: var(--gray-600); + } &.tag-success { background: var(--success-light); @@ -230,7 +250,7 @@ } &.tag-primary { - background: rgba(10, 132, 255, 0.12); + background: var(--primary-tint); color: var(--primary); } @@ -245,36 +265,207 @@ } } - .progress-apple { + /* 进度条 */ + .progress { width: 100%; - height: 8rpx; - background: var(--gray-200); - border-radius: var(--radius-small); + height: 10rpx; + background: var(--gray-100); + border-radius: var(--radius-full); overflow: hidden; - position: relative; .progress-fill { height: 100%; - background: linear-gradient(90deg, var(--primary), var(--primary-light)); - border-radius: var(--radius-small); - transition: width var(--transition-normal); + background: var(--primary); + border-radius: var(--radius-full); + transition: width var(--duration-slow) var(--ease-out); } } + /* 按钮 */ + .btn { + display: inline-flex; + align-items: center; + justify-content: center; + border: none; + border-radius: var(--radius-md); + font-weight: 600; + line-height: 1; + cursor: pointer; + transition: all var(--duration-fast) var(--ease-out); + user-select: none; + -webkit-tap-highlight-color: transparent; + padding: var(--space-4) var(--space-5); + font-size: 28rpx; + background: var(--gray-100); + color: var(--text-primary); + + &.btn-primary { + background: var(--primary); + color: var(--text-inverse); + } + + &.btn-success { + background: var(--success); + color: var(--text-inverse); + } + + &.btn-warning { + background: var(--warning); + color: var(--text-inverse); + } + + &.btn-danger { + background: var(--danger); + color: var(--text-inverse); + } + + &.btn-ghost { + background: transparent; + color: var(--primary); + } + + &.btn-mini { + min-height: 52rpx; + padding: 0 var(--space-4); + font-size: 24rpx; + border-radius: var(--radius-sm); + } + + &.btn-large { + min-height: 88rpx; + padding: 0 var(--space-8); + font-size: 32rpx; + border-radius: var(--radius-lg); + } + + &.btn-disabled { + opacity: 0.4; + cursor: not-allowed; + pointer-events: none; + } + } + + .btn-group { + display: flex; + gap: var(--space-3); + + .btn { + flex: 1; + } + + &.btn-group-vertical { + flex-direction: column; + } + } + + /* 兼容旧类名 */ + .mt-xs { margin-top: var(--space-2); } + .mt-sm { margin-top: var(--space-4); } + .mt-md { margin-top: var(--space-6); } + .mt-lg { margin-top: var(--space-8); } + .mt-xl { margin-top: var(--space-10); } + .mt-30 { margin-top: 30rpx; } + .mt-20 { margin-top: 20rpx; } + + .mb-xs { margin-bottom: var(--space-2); } + .mb-sm { margin-bottom: var(--space-4); } + .mb-md { margin-bottom: var(--space-6); } + .mb-lg { margin-bottom: var(--space-8); } + .mb-xl { margin-bottom: var(--space-10); } + + .p-xs { padding: var(--space-2); } + .p-sm { padding: var(--space-4); } + .p-md { padding: var(--space-6); } + .p-lg { padding: var(--space-8); } + .p-xl { padding: var(--space-10); } + + .flex-row-g8 { display: flex; align-items: center; gap: var(--space-2); } + .flex-row-g16 { display: flex; align-items: center; gap: var(--space-4); } + .flex-row-g20 { display: flex; align-items: center; gap: 20rpx; } + .flex-row-g24 { display: flex; align-items: center; gap: var(--space-6); } + + .flex-col-g8 { display: flex; flex-direction: column; gap: var(--space-2); } + .flex-col-g16 { display: flex; flex-direction: column; gap: var(--space-4); } + .flex-col-g20 { display: flex; flex-direction: column; gap: 20rpx; } + .flex-col-g24 { display: flex; flex-direction: column; gap: var(--space-6); } + + /* 背景色 */ + .bg-primary { background-color: var(--bg-surface); } + .bg-secondary { background-color: var(--bg-base); } + .bg-success { background-color: var(--success); } + .bg-warning { background-color: var(--warning); } + .bg-danger { background-color: var(--danger); } + + /* 文字色 */ + .text-primary { color: var(--text-primary); } + .text-secondary { color: var(--text-secondary); } + .text-tertiary { color: var(--text-tertiary); } + .text-inverse { color: var(--text-inverse); } + .text-success { color: var(--success); } + .text-warning { color: var(--warning); } + .text-danger { color: var(--danger); } + + /* 旧标签兼容 */ + .tag-apple { + display: inline-flex; + align-items: center; + padding: var(--space-1) var(--space-3); + background: var(--gray-100); + color: var(--gray-600); + border-radius: var(--radius-full); + font-size: 22rpx; + font-weight: 600; + letter-spacing: 0.01em; + + &.tag-success { + background: var(--success-light); + color: var(--success); + } + + &.tag-primary { + background: var(--primary-tint); + color: var(--primary); + } + + &.tag-warning { + background: var(--warning-light); + color: var(--warning); + } + + &.tag-danger { + background: var(--danger-light); + color: var(--danger); + } + } + + /* 旧按钮兼容 */ + .button-apple { + background: var(--primary); + color: var(--text-inverse); + border: none; + border-radius: var(--radius-md); + padding: var(--space-4) var(--space-5); + font-weight: 600; + font-size: 28rpx; + transition: all var(--duration-normal) var(--ease-out); + cursor: pointer; + -webkit-tap-highlight-color: transparent; + } + .btn-apple { display: inline-flex; align-items: center; justify-content: center; border: none; - border-radius: var(--radius-small); + border-radius: var(--radius-sm); font-weight: 500; font-size: 26rpx; line-height: 1; cursor: pointer; - transition: all 0.2s ease; + transition: all var(--duration-fast) var(--ease-out); user-select: none; -webkit-tap-highlight-color: transparent; - padding: 20rpx; + padding: var(--space-5); background: var(--gray-100); color: var(--text-primary); @@ -299,56 +490,44 @@ } &.btn-secondary { - background: var(--gray-300); - color: var(--text-secondary); + background: var(--gray-200); + color: var(--gray-700); } &.btn-mini { - min-height: 40rpx; - padding: 0 16rpx; + min-height: 52rpx; + padding: 0 var(--space-4); font-size: 24rpx; - border-radius: 6rpx; + border-radius: var(--radius-xs); } &.btn-large { - min-height: 64rpx; - padding: 0 32rpx; + min-height: 88rpx; + padding: 0 var(--space-8); font-size: 32rpx; - border-radius: var(--radius-medium); + border-radius: var(--radius-md); } &.btn-disabled { opacity: 0.4; cursor: not-allowed; + pointer-events: none; } } - .btn-group { - display: flex; - gap: 12rpx; + /* 旧进度条兼容 */ + .progress-apple { + width: 100%; + height: 10rpx; + background: var(--gray-100); + border-radius: var(--radius-full); + overflow: hidden; - .btn-apple { - flex: 1; - } - - &.btn-group-vertical { - flex-direction: column; + .progress-fill { + height: 100%; + background: var(--primary); + border-radius: var(--radius-full); + transition: width var(--duration-slow) var(--ease-out); } } - - /* 通用背景色 */ - .bg-primary { background-color: var(--bg-primary); } - .bg-secondary { background-color: var(--bg-secondary); } - .bg-success { background-color: var(--success); } - .bg-warning { background-color: var(--warning); } - .bg-danger { background-color: var(--danger); } - - /* 通用文字色 */ - .text-primary { color: var(--text-primary); } - .text-secondary { color: var(--text-secondary); } - .text-tertiary { color: var(--text-tertiary); } - .text-inverse { color: var(--text-inverse); } - .text-success { color: var(--success); } - .text-warning { color: var(--warning); } - .text-danger { color: var(--danger); } diff --git a/components/DeviceStatusCard.vue b/components/DeviceStatusCard.vue index 6ee7396..90f106d 100644 --- a/components/DeviceStatusCard.vue +++ b/components/DeviceStatusCard.vue @@ -1,41 +1,55 @@