Răsfoiți Sursa

feat: add frontend components, custom-tab-bar and global styles

- New components: PlayfulButton, PlayfulCard, wuxing-sandbox, TaskReminder, bottom-nav
- Rewrite custom-tab-bar/index.vue for new TabBar pages [body, mind, profile]
- Updated uni.scss with new page variables and energy/promotion theme colors
- PlayfulButton/PlayfulCard: gamified UI primitives with wǔxíng animations

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
User 3 luni în urmă
părinte
comite
5a17531b08

+ 14 - 19
cfc-frontend/components/PlayfulButton.vue

@@ -41,7 +41,7 @@ export default {
   border: none;
   border-radius: 999px;
   font-weight: 700;
-  transition: transform .15s ease, box-shadow .15s ease, opacity .2s ease;
+  transition: opacity .2s ease, box-shadow .15s ease;
   line-height: 1.4;
 }
 .playful-btn--block {
@@ -51,17 +51,17 @@ export default {
 
 /* ---- Primary ---- */
 .playful-btn--primary {
-  background: var(--color-primary, #F97316);
+  background: var(--color-primary, #5B9BD5);
   color: #fff;
-  box-shadow: 0 2px 8px rgba(249, 115, 22, 0.3);
+  box-shadow: 0 2px 8px rgba(91, 155, 213, 0.3);
 }
 
 /* ---- Secondary ---- */
 .playful-btn--secondary {
   background: var(--surface, #ffffff);
-  color: var(--text, #3D2E1E);
-  border: 2rpx solid var(--border, #FED7AA);
-  box-shadow: 0 2px 6px rgba(249, 115, 22, 0.06);
+  color: var(--text, #1E293B);
+  border: 2rpx solid var(--border, #CBD5E1);
+  box-shadow: 0 2px 6px rgba(91, 155, 213, 0.08);
 }
 
 /* ---- Success ---- */
@@ -87,19 +87,17 @@ export default {
 
 /* ---- Clay (孩子端玩具风) ---- */
 .playful-btn--clay {
-  background: linear-gradient(145deg, #FFB347, var(--color-primary, #F97316));
+  background: var(--color-primary, #5B9BD5);
   color: #fff;
-  border: 3rpx solid #FED7AA;
-  box-shadow:
-    inset -2px -2px 6px rgba(255, 255, 255, 0.3),
-    4px 4px 12px rgba(249, 115, 22, 0.25);
+  border: 2rpx solid var(--border, #CBD5E1);
+  box-shadow: 0 2px 8px rgba(91, 155, 213, 0.15);
 }
 
 /* ---- Outline ---- */
 .playful-btn--outline {
   background: transparent;
-  color: var(--color-primary, #F97316);
-  border: 2rpx solid var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
+  border: 2rpx solid var(--color-primary, #5B9BD5);
 }
 
 /* ---- Sizes ---- */
@@ -110,13 +108,10 @@ export default {
 
 /* ---- States ---- */
 .playful-btn:active:not(:disabled) {
-  transform: scale(0.97);
+  opacity: 0.85;
 }
 .playful-btn--clay:active:not(:disabled) {
-  transform: scale(0.95);
-  box-shadow:
-    inset 0 0 8px rgba(0,0,0,0.1),
-    2px 2px 6px rgba(249, 115, 22, 0.15);
+  opacity: 0.85;
 }
 .playful-btn--disabled,
 .playful-btn:disabled {
@@ -124,7 +119,7 @@ export default {
   cursor: not-allowed;
 }
 .playful-btn:focus {
-  outline: 2px solid rgba(249, 115, 22, 0.4);
+  outline: 2px solid rgba(91, 155, 213, 0.4);
   outline-offset: 2px;
 }
 </style>

+ 12 - 9
cfc-frontend/components/PlayfulCard.vue

@@ -61,20 +61,23 @@ export default {
   transition: transform .15s ease, box-shadow .2s ease;
 }
 .playful-card--flat {
-  border: 1px solid var(--border, #FED7AA);
+  border: 1px solid var(--border, #CBD5E1);
   box-shadow: var(--shadow-md);
 }
 .playful-card--clay {
-  border: 4rpx solid var(--border, #FED7AA);
-  background: linear-gradient(145deg, #FFFFFF, var(--bg, #FFF7ED));
-  box-shadow: var(--shadow-clay);
+  border: 1px solid var(--border-light, #E2E8F0);
+  background: var(--surface, #FFFFFF);
+  box-shadow: var(--shadow-md);
 }
 .playful-card--bordered {
-  border: 1px solid var(--border-light, #FFEDD5);
+  border: 1px solid var(--border-light, #E2E8F0);
 }
 .playful-card--teacher {
-  border: 1px solid #DBEAFE;
-  box-shadow: 0 4rpx 14rpx rgba(59, 130, 246, 0.08);
+  border-left: 4px solid var(--color-primary, #5B9BD5);
+  border-top: 1px solid var(--border-light, #E2E8F0);
+  border-right: 1px solid var(--border-light, #E2E8F0);
+  border-bottom: 1px solid var(--border-light, #E2E8F0);
+  box-shadow: var(--shadow-sm);
   background: var(--surface, #ffffff);
 }
 .playful-card--shadow-none { box-shadow: none; }
@@ -88,10 +91,10 @@ export default {
   font-weight: 700;
   font-size: 32rpx;
   margin-bottom: 16rpx;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
 }
 .playful-card__content {
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   line-height: 1.6;
   font-size: 28rpx;
 }

+ 92 - 0
cfc-frontend/components/TaskReminder.vue

@@ -0,0 +1,92 @@
+<template>
+  <view class="reminder-card" v-if="hasReminders">
+    <view class="reminder-header">
+      <text class="reminder-title">{{ title }}</text>
+      <text class="reminder-count">{{ count }}项</text>
+    </view>
+    <view class="reminder-list">
+      <view class="reminder-item" v-for="task in tasks" :key="task.id" @click="goToTask(task)">
+        <text class="task-name">{{ task.title }}</text>
+        <text class="task-deadline">截止: {{ formatTime(task.deadline) }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    title: { type: String, default: '待提醒任务' },
+    tasks: { type: Array, default: () => [] }
+  },
+  computed: {
+    hasReminders() {
+      return this.tasks && this.tasks.length > 0
+    },
+    count() {
+      return this.tasks ? this.tasks.length : 0
+    }
+  },
+  methods: {
+    formatTime(dateStr) {
+      if (!dateStr) return ''
+      const d = new Date(dateStr)
+      const h = d.getHours().toString().padStart(2, '0')
+      const m = d.getMinutes().toString().padStart(2, '0')
+      return h + ':' + m
+    },
+    goToTask(task) {
+      uni.navigateTo({ url: '/pages/tasks/tasks?highlight=' + task.id })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.reminder-card {
+  background: linear-gradient(135deg, #fef3c7, #fde68a);
+  border-radius: 16rpx;
+  padding: 20rpx 24rpx;
+  margin-bottom: 16rpx;
+}
+.reminder-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12rpx;
+}
+.reminder-title {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #92400e;
+}
+.reminder-count {
+  font-size: 22rpx;
+  color: #92400e;
+  background: rgba(255,255,255,0.5);
+  padding: 4rpx 16rpx;
+  border-radius: 20rpx;
+}
+.reminder-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8rpx;
+}
+.reminder-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background: rgba(255,255,255,0.6);
+  border-radius: 10rpx;
+  padding: 12rpx 16rpx;
+}
+.task-name {
+  font-size: 24rpx;
+  color: #333;
+  flex: 1;
+}
+.task-deadline {
+  font-size: 20rpx;
+  color: #b45309;
+}
+</style>

+ 85 - 0
cfc-frontend/components/bottom-nav.vue

@@ -0,0 +1,85 @@
+<template>
+  <view class="bottom-nav">
+    <view
+      class="nav-item"
+      :class="{ active: current === 'index' }"
+      @click="goNav('index')"
+    >
+      <text class="nav-icon">🏠</text>
+      <text class="nav-text">首页</text>
+    </view>
+    <view
+      class="nav-item"
+      :class="{ active: current === 'action' }"
+      @click="goNav('action')"
+    >
+      <text class="nav-icon">🎯</text>
+      <text class="nav-text">行动</text>
+    </view>
+    <view
+      class="nav-item"
+      :class="{ active: current === 'mind' }"
+      @click="goNav('mind')"
+    >
+      <text class="nav-icon">🧠</text>
+      <text class="nav-text">心智</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    current: { type: String, default: 'index' }
+  },
+  methods: {
+    goNav(tab) {
+      if (tab === this.current) return
+
+      if (tab === 'index') {
+        // index 是 TabBar 页面,已登录用 switchTab,未登录用 navigateTo
+        if (uni.getStorageSync('token')) {
+          uni.switchTab({ url: '/pages/index/index' })
+        } else {
+          uni.navigateTo({ url: '/pages/index/index' })
+        }
+      } else if (tab === 'action') {
+        // shop 已从 TabBar 移除,统一用 navigateTo
+        uni.navigateTo({ url: '/pages/shop/index' })
+      } else if (tab === 'mind') {
+        // 心智文章页(非 TabBar)
+        uni.navigateTo({ url: '/pages/mind/articles' })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.bottom-nav {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 100rpx;
+  background: #fff;
+  border-top: 1rpx solid #eee;
+  display: flex;
+  align-items: center;
+  justify-content: space-around;
+  padding-bottom: env(safe-area-inset-bottom);
+  z-index: 100;
+}
+.nav-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  flex: 1;
+  height: 100%;
+}
+.nav-item.active .nav-text { color: #FF6B6B; }
+.nav-icon { font-size: 40rpx; }
+.nav-text { font-size: 20rpx; color: #999; margin-top: 4rpx; }
+.nav-item.active .nav-text { font-weight: bold; }
+</style>

+ 64 - 0
cfc-frontend/components/wuxing-sandbox-helpers/animation.js

@@ -0,0 +1,64 @@
+/**
+ * wuxing-sandbox-helpers/animation.js
+ * 五角星填充动画控制器
+ */
+
+/**
+ * FillAnimator - 管理 ease-out cubic 填充动画
+ * @param {Function} getCurrentTs - 返回当前 5 维插值数组 [0~1]
+ * @param {Function} onFrame - 每帧回调,参数为插值后的 ts 数组
+ * @param {Function} [onComplete] - 动画完成回调
+ */
+export function FillAnimator(getCurrentTs, onFrame, onComplete) {
+  this._getCurrentTs = getCurrentTs
+  this._onFrame = onFrame
+  this._onComplete = onComplete || function() {}
+  this._timer = null
+}
+
+/**
+ * 启动动画
+ * @param {Array<number>} targetTs - 目标 5 维值 (0~1)
+ * @param {number} [duration=600] - 动画时长 ms
+ */
+FillAnimator.prototype.start = function(targetTs, duration) {
+  this.cancel()
+
+  var dur = duration || 600
+  var startTime = Date.now()
+  var startTs = this._getCurrentTs()
+  var self = this
+
+  function step() {
+    var elapsed = Date.now() - startTime
+    var progress = Math.min(elapsed / dur, 1)
+    // ease-out cubic
+    var eased = 1 - Math.pow(1 - progress, 3)
+
+    var interpolated = []
+    for (var i = 0; i < 5; i++) {
+      interpolated.push(startTs[i] + (targetTs[i] - startTs[i]) * eased)
+    }
+
+    self._onFrame(interpolated)
+
+    if (progress < 1) {
+      self._timer = setTimeout(step, 16)
+    } else {
+      self._timer = null
+      self._onComplete()
+    }
+  }
+
+  step()
+}
+
+/**
+ * 取消动画,清理 timer
+ */
+FillAnimator.prototype.cancel = function() {
+  if (this._timer) {
+    clearTimeout(this._timer)
+    this._timer = null
+  }
+}

+ 80 - 0
cfc-frontend/components/wuxing-sandbox-helpers/hitdetect.js

@@ -0,0 +1,80 @@
+/**
+ * wuxing-sandbox-helpers/hitdetect.js
+ * 五角星点击检测工具(纯函数,无状态)
+ */
+
+var DIM_KEYS = ['body', 'mind', 'wisdom', 'action', 'wealth']
+
+/**
+ * 射线法(Ray Casting)判断点是否在任意多边形内
+ * @param {number} x - 点的 x 坐标
+ * @param {number} y - 点的 y 坐标
+ * @param {Array<{x:number,y:number}>} polygon - 多边形顶点数组
+ * @returns {boolean}
+ */
+export function pointInPolygon(x, y, polygon) {
+  var inside = false
+  var n = polygon.length
+  for (var i = 0, j = n - 1; i < n; j = i++) {
+    var xi = polygon[i].x
+    var yi = polygon[i].y
+    var xj = polygon[j].x
+    var yj = polygon[j].y
+    if ((yi > y) !== (yj > y) && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
+      inside = !inside
+    }
+  }
+  return inside
+}
+
+/**
+ * 计算指定星臂的多边形顶点
+ * @param {number} armIndex - 星臂索引 0~4 (body/mind/wisdom/action/wealth)
+ * @param {number} cx - 中心 x
+ * @param {number} cy - 中心 y
+ * @param {{outer:Array,inner:Array}} vertices - calcStarVertices 返回值
+ * @returns {Array<{x:number,y:number}>}
+ */
+export function calcArmPolygon(armIndex, cx, cy, vertices) {
+  var outer = vertices.outer
+  var inner = vertices.inner
+  var outerIdx = (armIndex + 4) % 5
+  var iBefore = (armIndex + 3) % 5
+  var iAfter = (armIndex + 4) % 5
+  return [
+    { x: cx, y: cy },
+    inner[iBefore],
+    outer[outerIdx],
+    inner[iAfter]
+  ]
+}
+
+/**
+ * 检测点击位置命中哪个星臂
+ * @param {number} relX - Canvas 相对 x 坐标
+ * @param {number} relY - Canvas 相对 y 坐标
+ * @param {number} cx - 中心 x
+ * @param {number} cy - 中心 y
+ * @param {number} outerR - 外半径
+ * @param {Function} calcVertices - calcStarVertices 函数引用
+ * @returns {number} 命中星臂索引 0~4,-1 表示未命中
+ */
+export function detectHitArm(relX, relY, cx, cy, outerR, calcVertices) {
+  var vertices = calcVertices(cx, cy, outerR)
+  for (var i = 0; i < 5; i++) {
+    var polygon = calcArmPolygon(i, cx, cy, vertices)
+    if (pointInPolygon(relX, relY, polygon)) {
+      return i
+    }
+  }
+  return -1
+}
+
+/**
+ * 星臂索引 → 维度 key 映射
+ * @param {number} armIndex
+ * @returns {string} 'body'|'mind'|'wisdom'|'action'|'wealth'
+ */
+export function armIndexToDomain(armIndex) {
+  return DIM_KEYS[armIndex] || ''
+}

+ 244 - 0
cfc-frontend/components/wuxing-sandbox-helpers/render.js

@@ -0,0 +1,244 @@
+/**
+ * wuxing-sandbox-helpers/render.js
+ * 五角星 Canvas 渲染(纯函数,无状态)
+ */
+
+/**
+ * 计算五角星顶点坐标
+ * 外顶点:5 个,72° 等分,起始角度 -90°(正上方)
+ * 内顶点:5 个,偏移 36°,半径 = 外半径 × 0.382
+ * @param {number} cx - 中心 x
+ * @param {number} cy - 中心 y
+ * @param {number} outerR - 外半径
+ * @returns {{outer:Array<{x:number,y:number}>,inner:Array<{x:number,y:number}>}}
+ */
+export function calcStarVertices(cx, cy, outerR) {
+  var innerR = outerR * 0.382
+  var outer = []
+  var inner = []
+  for (var i = 0; i < 5; i++) {
+    var angle = -Math.PI / 2 + i * 2 * Math.PI / 5
+    outer.push({
+      x: cx + outerR * Math.cos(angle),
+      y: cy + outerR * Math.sin(angle)
+    })
+    var innerAngle = angle + Math.PI / 5
+    inner.push({
+      x: cx + innerR * Math.cos(innerAngle),
+      y: cy + innerR * Math.sin(innerAngle)
+    })
+  }
+  return { outer: outer, inner: inner }
+}
+
+/** 五色配置(与 CSS 一致) */
+var COLORS = [
+  { r: 76, g: 175, b: 80 },   // 木·身
+  { r: 255, g: 107, b: 53 },  // 火·心
+  { r: 141, g: 110, b: 99 },  // 土·智
+  { r: 255, g: 215, b: 0 },   // 金·行
+  { r: 66, g: 165, b: 245 }   // 水·富
+]
+
+/**
+ * 绘制完整五角星图(含填充、描边、太极)
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {number} w - canvas 宽度
+ * @param {number} h - canvas 高度
+ * @param {Array<number>} ts - 5 维归一化值 (0~1),顺序 [body,mind,wisdom,action,wealth]
+ * @param {Object} [options]
+ * @param {boolean} [options.showTaiji=true] - 是否绘制太极
+ */
+export function renderStar(ctx, w, h, ts, options) {
+  if (!ctx || !w || !h) return
+
+  var opts = options || {}
+  var showTaiji = opts.showTaiji !== false
+
+  // 清空
+  ctx.clearRect(0, 0, w, h)
+
+  // 星型几何
+  var cx = w / 2
+  var cy = h / 2
+  var outerR = Math.min(w, h) * 0.42
+  var vertices = calcStarVertices(cx, cy, outerR)
+  var outer = vertices.outer
+  var inner = vertices.inner
+
+  // 绘制每个星臂
+  for (var i = 0; i < 5; i++) {
+    var outerIdx = (i + 4) % 5
+    var iBefore = (i + 3) % 5
+    var iAfter = (i + 4) % 5
+    var t = ts[i]
+    var c = COLORS[i]
+
+    // 星臂 polygon: center, inner[iBefore], outer[outerIdx], inner[iAfter]
+    var polygon = [
+      { x: cx, y: cy },
+      inner[iBefore],
+      outer[outerIdx],
+      inner[iAfter]
+    ]
+
+    // --- 背景填充(低透明度) ---
+    ctx.beginPath()
+    ctx.moveTo(polygon[0].x, polygon[0].y)
+    for (var p = 1; p < polygon.length; p++) {
+      ctx.lineTo(polygon[p].x, polygon[p].y)
+    }
+    ctx.closePath()
+    ctx.fillStyle = 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', 0.08)'
+    ctx.fill()
+
+    // --- 渐变填充(t 控制色彩饱和度,覆盖整个星臂) ---
+    if (t > 0.01) {
+      ctx.save()
+      ctx.beginPath()
+      ctx.moveTo(polygon[0].x, polygon[0].y)
+      for (var fp = 1; fp < polygon.length; fp++) {
+        ctx.lineTo(polygon[fp].x, polygon[fp].y)
+      }
+      ctx.closePath()
+      ctx.clip()
+
+      var grad = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.max(outerR, 1))
+      grad.addColorStop(0, 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + (0.85 * t) + ')')
+      grad.addColorStop(0.5, 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + (0.35 * t) + ')')
+      grad.addColorStop(1, 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + (0.02 * t) + ')')
+      ctx.fillStyle = grad
+      ctx.fillRect(0, 0, w, h)
+
+      ctx.restore()
+    }
+
+    // --- 星臂描边 ---
+    ctx.beginPath()
+    ctx.moveTo(polygon[0].x, polygon[0].y)
+    for (var sp = 1; sp < polygon.length; sp++) {
+      ctx.lineTo(polygon[sp].x, polygon[sp].y)
+    }
+    ctx.closePath()
+    ctx.strokeStyle = 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', 0.4)'
+    ctx.lineWidth = 1.5
+    ctx.stroke()
+  }
+
+  // --- 五角星外轮廓(10 个顶点依次连接) ---
+  ctx.beginPath()
+  ctx.moveTo(outer[0].x, outer[0].y)
+  ctx.lineTo(inner[0].x, inner[0].y)
+  ctx.lineTo(outer[1].x, outer[1].y)
+  ctx.lineTo(inner[1].x, inner[1].y)
+  ctx.lineTo(outer[2].x, outer[2].y)
+  ctx.lineTo(inner[2].x, inner[2].y)
+  ctx.lineTo(outer[3].x, outer[3].y)
+  ctx.lineTo(inner[3].x, inner[3].y)
+  ctx.lineTo(outer[4].x, outer[4].y)
+  ctx.lineTo(inner[4].x, inner[4].y)
+  ctx.closePath()
+  ctx.strokeStyle = 'rgba(143, 197, 232, 0.50)'
+  ctx.lineWidth = 2
+  ctx.stroke()
+
+  // --- 内部五边形(5 个内顶点) ---
+  ctx.beginPath()
+  ctx.moveTo(inner[0].x, inner[0].y)
+  ctx.lineTo(inner[1].x, inner[1].y)
+  ctx.lineTo(inner[2].x, inner[2].y)
+  ctx.lineTo(inner[3].x, inner[3].y)
+  ctx.lineTo(inner[4].x, inner[4].y)
+  ctx.closePath()
+  ctx.strokeStyle = 'rgba(143, 197, 232, 0.35)'
+  ctx.lineWidth = 1.5
+  ctx.stroke()
+
+  // --- 中心太极阴阳鱼(在 Canvas 最上层绘制) ---
+  if (showTaiji) {
+    var taijiR = outerR * 0.22
+    drawTaiji(ctx, cx, cy, taijiR)
+  }
+}
+
+/**
+ * 绘制太极阴阳鱼
+ * @param {CanvasRenderingContext2D} ctx
+ * @param {number} cx - 中心 x
+ * @param {number} cy - 中心 y
+ * @param {number} r - 半径
+ */
+export function drawTaiji(ctx, cx, cy, r) {
+  var yangColor = 'rgba(91, 155, 213, 0.35)'
+  var yinColor = 'rgba(26, 42, 74, 0.75)'
+  var strokeColor = 'rgba(143, 197, 232, 0.2)'
+  var yangEyeColor = 'rgba(26, 42, 74, 0.85)'
+  var yinEyeColor = 'rgba(143, 197, 232, 0.75)'
+
+  ctx.save()
+
+  // 1. 外圆背景及边框
+  ctx.beginPath()
+  ctx.arc(cx, cy, r, 0, Math.PI * 2)
+  ctx.closePath()
+  ctx.fillStyle = yangColor
+  ctx.fill()
+  ctx.strokeStyle = strokeColor
+  ctx.lineWidth = 1
+  ctx.stroke()
+
+  // 2. 左半边:阴(深色)
+  ctx.beginPath()
+  ctx.moveTo(cx, cy)
+  ctx.arc(cx, cy, r, Math.PI / 2, -Math.PI / 2)
+  ctx.closePath()
+  ctx.fillStyle = yinColor
+  ctx.fill()
+
+  // 3. 上半小圆左半弧:阳色
+  ctx.beginPath()
+  ctx.moveTo(cx, cy - r)
+  ctx.arc(cx, cy - r / 2, r / 2, Math.PI / 2, -Math.PI / 2)
+  ctx.closePath()
+  ctx.fillStyle = yangColor
+  ctx.fill()
+
+  // 4. 下半小圆右半弧:阴色
+  ctx.beginPath()
+  ctx.moveTo(cx, cy + r)
+  ctx.arc(cx, cy + r / 2, r / 2, -Math.PI / 2, Math.PI / 2)
+  ctx.closePath()
+  ctx.fillStyle = yinColor
+  ctx.fill()
+
+  // 5. 阳眼(深色点,上方)
+  ctx.beginPath()
+  ctx.arc(cx, cy - r / 2, r * 0.12, 0, Math.PI * 2)
+  ctx.closePath()
+  ctx.fillStyle = yangEyeColor
+  ctx.fill()
+
+  // 6. 阴眼(浅色点,下方)
+  ctx.beginPath()
+  ctx.arc(cx, cy + r / 2, r * 0.12, 0, Math.PI * 2)
+  ctx.closePath()
+  ctx.fillStyle = yinEyeColor
+  ctx.fill()
+
+  ctx.restore()
+
+  // 7. 文字标签
+  ctx.save()
+  var fontSize = Math.round(r * 0.3)
+  ctx.font = 'bold ' + fontSize + 'px sans-serif'
+  ctx.textAlign = 'center'
+  ctx.textBaseline = 'middle'
+
+  ctx.fillStyle = 'rgba(143, 197, 232, 0.6)'
+  ctx.fillText('家', cx - r * 0.3, cy + r * 0.15)
+
+  ctx.fillStyle = 'rgba(91, 155, 213, 0.8)'
+  ctx.fillText('个', cx + r * 0.3, cy - r * 0.15)
+
+  ctx.restore()
+}

+ 467 - 343
cfc-frontend/components/wuxing-sandbox.vue

@@ -1,116 +1,419 @@
 <template>
-  <view class="wuxing-sandbox">
+<view class="wuxing-sandbox">
+  <!-- header slot -->
+  <slot name="header">
     <view class="sandbox-header">
-      <text class="sandbox-title">🌟 五行能量沙盘</text>
-      <text class="sandbox-badge">{{ badgeText }}</text>
+      <text class="sandbox-title">🌟 家庭能量沙盘</text>
+      <text class="sandbox-badge">{{ displayBadgeText }}</text>
     </view>
+  </slot>
 
-    <view class="sandbox-star">
-      <!-- ===== 背景光晕 ===== -->
-      <view class="star-glow-bg"></view>
+  <view class="sandbox-star" :style="{height: starHeight}">
+    <view class="star-glow-bg"></view>
 
-      <!-- ===== 五角星轮廓(clip-path) ===== -->
-      <view class="star-shape">
-        <view class="star-shape__fill"></view>
-        <view class="star-shape__outline"></view>
-      </view>
-
-      <!-- ===== 五角星连线(内五边形) ===== -->
-      <view class="pentagram-lines">
-        <view class="p-line p-line--1"></view>
-        <view class="p-line p-line--2"></view>
-        <view class="p-line p-line--3"></view>
-        <view class="p-line p-line--4"></view>
-        <view class="p-line p-line--5"></view>
-      </view>
+    <!-- Canvas 2D 五行星图 -->
+    <canvas id="starCanvas" type="2d" style="width:100%;height:100%;position:absolute;top:0;left:0;z-index:1"></canvas>
 
-      <!-- ===== 顶点 1:心·火(上) ===== -->
-      <view class="star-point star-point--top" @click="onPointClick('mind')" hover-class="point-hover">
-        <view class="point-glow point-glow--fire"></view>
-        <view class="point-dot point-dot--fire">
-          <text class="point-icon">🔥</text>
-        </view>
-        <text class="point-label">心·火</text>
-        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
-      </view>
+    <!-- Canvas 点击感应层 -->
+    <view class="canvas-tap-area" @click="onCanvasTap"></view>
 
-      <!-- ===== 顶点 2:富·水(右上) ===== -->
-      <view class="star-point star-point--ur" @click="onPointClick('wealth')" hover-class="point-hover">
-        <view class="point-glow point-glow--water"></view>
-        <view class="point-dot point-dot--water">
-          <text class="point-icon">💧</text>
-        </view>
-        <text class="point-label">富·水</text>
-        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
+    <!-- 顶点:从 sortedDimensions 数据驱动 (心/行/富/智/身) -->
+    <view class="star-point star-point--top" @click="onPointClick(sortedDimensions[0].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[0].element)">
+        <text class="point-icon">{{ sortedDimensions[0].icon }}</text>
       </view>
-
-      <!-- ===== 顶点 3:身·土(右下) ===== -->
-      <view class="star-point star-point--lr" @click="onPointClick('body')" hover-class="point-hover">
-        <view class="point-glow point-glow--earth"></view>
-        <view class="point-dot point-dot--earth">
-          <text class="point-icon">🌏</text>
-        </view>
-        <text class="point-label">身·土</text>
-        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
+      <text class="point-label">{{ sortedDimensions[0].name }}</text>
+      <text class="point-energy">{{ sortedDimensions[0].energy }}</text>
+    </view>
+    <view class="star-point star-point--left" @click="onPointClick(sortedDimensions[1].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[1].element)">
+        <text class="point-icon">{{ sortedDimensions[1].icon }}</text>
       </view>
-
-      <!-- ===== 顶点 4:智·金(左下) ===== -->
-      <view class="star-point star-point--ll" @click="onPointClick('wisdom')" hover-class="point-hover">
-        <view class="point-glow point-glow--metal"></view>
-        <view class="point-dot point-dot--metal">
-          <text class="point-icon">⚔️</text>
-        </view>
-        <text class="point-label">智·金</text>
-        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
+      <text class="point-label">{{ sortedDimensions[1].name }}</text>
+      <text class="point-energy">{{ sortedDimensions[1].energy }}</text>
+    </view>
+    <view class="star-point star-point--right" @click="onPointClick(sortedDimensions[2].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[2].element)">
+        <text class="point-icon">{{ sortedDimensions[2].icon }}</text>
       </view>
-
-      <!-- ===== 顶点 5:行·木(左上) ===== -->
-      <view class="star-point star-point--ul" @click="onPointClick('action')" hover-class="point-hover">
-        <view class="point-glow point-glow--wood"></view>
-        <view class="point-dot point-dot--wood">
-          <text class="point-icon">🌿</text>
-        </view>
-        <text class="point-label">行·木</text>
-        <text class="point-status" v-if="mode === 'preview'">点此登录</text>
+      <text class="point-label">{{ sortedDimensions[2].name }}</text>
+      <text class="point-energy">{{ sortedDimensions[2].energy }}</text>
+    </view>
+    <view class="star-point star-point--ll" @click="onPointClick(sortedDimensions[3].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[3].element)">
+        <text class="point-icon">{{ sortedDimensions[3].icon }}</text>
       </view>
-
-      <!-- ===== 中心 ===== -->
-      <view class="star-center">
-        <view class="center-icon-wrap">
-          <text class="center-icon">☯</text>
-        </view>
-        <text class="center-text">五行平衡</text>
-        <text class="center-sub">共同成长</text>
+      <text class="point-label">{{ sortedDimensions[3].name }}</text>
+      <text class="point-energy">{{ sortedDimensions[3].energy }}</text>
+    </view>
+    <view class="star-point star-point--lr" @click="onPointClick(sortedDimensions[4].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[4].element)">
+        <text class="point-icon">{{ sortedDimensions[4].icon }}</text>
       </view>
+      <text class="point-label">{{ sortedDimensions[4].name }}</text>
+      <text class="point-energy">{{ sortedDimensions[4].energy }}</text>
     </view>
 
-    <view class="shenke-hint" v-if="mode === 'preview'">
-      <text class="hint-text">🌱 完成测评,点亮「身·土」</text>
+    <!-- 中心点击区域(太极由 Canvas 绘制) -->
+    <view class="star-center" @click="onCenterClick">
+      <text class="energy-hint">{{ viewMode === 'family' ? '家庭能量' : (currentMember ? currentMember.name : '个人') }}</text>
     </view>
+  </view>
 
-    <view class="dimension-bar" v-if="mode !== 'preview'">
-      <view class="dim-item dim-item--wood">🌿行</view>
-      <text class="dim-arrow">→</text>
-      <view class="dim-item dim-item--fire">🔥心</view>
-      <text class="dim-arrow">→</text>
-      <view class="dim-item dim-item--earth">🌏身</view>
-      <text class="dim-arrow">→</text>
-      <view class="dim-item dim-item--metal">⚔️智</view>
-      <text class="dim-arrow">→</text>
-      <view class="dim-item dim-item--water">💧富</view>
+  <!-- dimension-bar slot -->
+  <slot name="dimension-bar" v-if="showDimensionBar && mode !== 'preview'">
+    <view class="dimension-bar">
+      <view class="dim-item dim-item--wood">🌿身 {{ activeScores[0] }}%</view>
+      <view class="dim-item dim-item--fire">🔥心 {{ activeScores[1] }}%</view>
+      <view class="dim-item dim-item--earth">🌏智 {{ activeScores[2] }}%</view>
+      <view class="dim-item dim-item--metal">⚡行 {{ activeScores[3] }}%</view>
+      <view class="dim-item dim-item--water">💧富 {{ activeScores[4] }}%</view>
     </view>
-  </view>
+  </slot>
+</view>
 </template>
 
 <script>
+import { renderStar, calcStarVertices } from './wuxing-sandbox-helpers/render.js'
+import { FillAnimator } from './wuxing-sandbox-helpers/animation.js'
+import { detectHitArm, armIndexToDomain } from './wuxing-sandbox-helpers/hitdetect.js'
+
+var DIM_KEYS = ['body', 'mind', 'wisdom', 'action', 'wealth']
+
 export default {
   props: {
-    mode: { type: String, default: 'preview' },
-    badgeText: { type: String, default: '综合成长力 0%' }
+    /** 模式:'preview' 随机数据 / 'live' 真实数据 */
+    mode: {
+      type: String,
+      default: 'preview',
+      validator: function(v) { return v === 'preview' || v === 'live' }
+    },
+    /** 五维分值对象 {body,mind,wisdom,action,wealth},0-100 */
+    scores: {
+      type: Object,
+      default: function() { return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 } }
+    },
+    /** 综合分值 0-100 */
+    overallScore: {
+      type: Number,
+      default: 0
+    },
+    /** 家庭成员数组(MemberEnergyDTO 格式) */
+    members: {
+      type: Array,
+      default: function() { return [] }
+    },
+    /** 顶部徽章文字,空则自动计算 */
+    badgeText: {
+      type: String,
+      default: ''
+    },
+    /** 是否显示底部维度条 */
+    showDimensionBar: {
+      type: Boolean,
+      default: true
+    },
+    /** 五维能量数据(账本模式) */
+    dimensions: {
+      type: Array,
+      default: function() { return [] }
+    },
+    totalEnergy: {
+      type: Number,
+      default: 0
+    },
+    totalHealthIndex: {
+      type: Number,
+      default: 0
+    },
+    /** 星图区域高度 */
+    starHeight: {
+      type: String,
+      default: '480rpx'
+    }
+  },
+  data: function() {
+    return {
+      viewMode: 'family',
+      selectedMemberIndex: 0,
+      previewScores: [0, 0, 0, 0, 0],
+      canvasReady: false,
+      canvasWidth: 0,
+      canvasHeight: 0,
+      ctx: null,
+      dpr: 1,
+      _canvasLeft: 0,
+      _canvasTop: 0,
+      animBody: 0,
+      animMind: 0,
+      animWisdom: 0,
+      animAction: 0,
+      animWealth: 0,
+      _animator: null
+    }
+  },
+  computed: {
+    isPreview: function() {
+      return this.mode === 'preview'
+    },
+    currentMember: function() {
+      if (this.isPreview) return null
+      var m = this.members
+      if (!m || m.length === 0) return null
+      if (this.selectedMemberIndex >= m.length) return m[0]
+      return m[this.selectedMemberIndex]
+    },
+    /** 当前活跃的五维值数组 [body, mind, wisdom, action, wealth] */
+    activeScores: function() {
+      if (this.isPreview) return this.previewScores
+      if (this.viewMode === 'family') {
+        var s = this.scores
+        return [
+          s.body || 0,
+          s.mind || 0,
+          s.wisdom || 0,
+          s.action || 0,
+          s.wealth || 0
+        ]
+      }
+      var m = this.currentMember
+      if (!m) return [0, 0, 0, 0, 0]
+      return [
+        m.bodyScore || 0,
+        m.mindScore || 0,
+        m.wisdomScore || 0,
+        m.actionScore || 0,
+        m.wealthScore || 0
+      ]
+    },
+    activeOverall: function() {
+      if (this.isPreview) {
+        var s = this.previewScores
+        return Math.round((s[0] + s[1] + s[2] + s[3] + s[4]) / 5)
+      }
+      if (this.viewMode === 'family') return this.overallScore || 0
+      var m = this.currentMember
+      return m ? (m.overallScore || 0) : 0
+    },
+    activeTs: function() {
+      return [
+        this.activeScores[0] / 100,
+        this.activeScores[1] / 100,
+        this.activeScores[2] / 100,
+        this.activeScores[3] / 100,
+        this.activeScores[4] / 100
+      ]
+    },
+    displayBadgeText: function() {
+      if (this.badgeText) return this.badgeText
+      var label = this.viewMode === 'family' ? '家庭能量' : (this.currentMember ? this.currentMember.name : '个人能量')
+      return label + ' ' + (this.activeOverall || 0) + '%'
+    },
+    _allDimTs: function() {
+      return this.activeTs
+    },
+    hasMultipleMembers: function() {
+      return this.members && this.members.length > 1
+    },
+    /** 按五行布局排序的维度数据(心·火(顶)/行·木(左)/富·水(右)/智·金(左下)/身·土(右下)) */
+    sortedDimensions: function() {
+      var order = ['mind', 'action', 'wealth', 'wisdom', 'body']
+      var dimMap = {}
+      for (var i = 0; i < this.dimensions.length; i++) {
+        var d = this.dimensions[i]
+        if (d && d.code) dimMap[d.code] = d
+      }
+      var result = []
+      for (var j = 0; j < order.length; j++) {
+        var code = order[j]
+        result.push(dimMap[code] || { code: code, name: '', icon: '', element: '', energy: 0, healthIndex: 0 })
+      }
+      return result
+    }
+  },
+  watch: {
+    _allDimTs: function() {
+      if (this.canvasReady) {
+        this._startAnimation()
+      }
+    }
+  },
+  mounted: function() {
+    var self = this
+
+    // 预览模式:生成随机五维值
+    if (this.isPreview) {
+      var scores = []
+      for (var i = 0; i < 5; i++) {
+        scores.push(Math.floor(Math.random() * 51) + 35)
+      }
+      this.previewScores = scores
+    }
+
+    // 等待 DOM 就绪后初始化 Canvas
+    setTimeout(function() {
+      self.initCanvas()
+    }, 100)
+  },
+  beforeDestroy: function() {
+    if (this._animator) {
+      this._animator.cancel()
+      this._animator = null
+    }
   },
   methods: {
-    onPointClick(domain) {
-      this.$emit('point-click', domain)
+    /* ===========================
+    视图切换
+    =========================== */
+
+    onCenterClick: function() {
+      // 预览模式不切换
+      if (this.isPreview) {
+        this.$emit('center-click', { viewMode: this.viewMode, selectedMemberIndex: this.selectedMemberIndex })
+        return
+      }
+      var members = this.members
+      if (!members || members.length === 0) {
+        this.viewMode = 'family'
+        this.$emit('center-click', { viewMode: this.viewMode, selectedMemberIndex: this.selectedMemberIndex })
+        return
+      }
+      if (this.viewMode === 'family') {
+        this.viewMode = 'member'
+      } else {
+        if (members.length === 1) {
+          this.viewMode = 'family'
+          this.selectedMemberIndex = 0
+        } else {
+          var nextIdx = this.selectedMemberIndex + 1
+          if (nextIdx >= members.length) {
+            this.viewMode = 'family'
+            this.selectedMemberIndex = 0
+          } else {
+            this.selectedMemberIndex = nextIdx
+          }
+        }
+      }
+      this.$emit('center-click', { viewMode: this.viewMode, selectedMemberIndex: this.selectedMemberIndex })
+      this.$emit('mode-change', { viewMode: this.viewMode, selectedMemberIndex: this.selectedMemberIndex })
+    },
+
+    onPointClick: function(code) {
+      this.$emit('point-click', {
+        code: code,
+        viewMode: this.viewMode,
+        member: this.currentMember
+      })
+    },
+
+    /** 五行元素 → CSS 颜色类名 */
+    dimElementColor: function(element) {
+      var map = { '火': 'fire', '水': 'water', '土': 'earth', '金': 'metal', '木': 'wood' }
+      return map[element] || 'fire'
+    },
+
+    /* ===========================
+    Canvas 初始化 & 渲染
+    =========================== */
+
+    initCanvas: function() {
+      var self = this
+      uni.createSelectorQuery().in(this).select('#starCanvas')
+        .fields({ node: true, size: true, rect: true })
+        .exec(function(res) {
+          if (!res || !res[0] || !res[0].node) return
+
+          var canvas = res[0].node
+          var width = res[0].width
+          var height = res[0].height
+          if (!width || !height) return
+
+          var pixelRatio = uni.getSystemInfoSync().pixelRatio || 2
+
+          canvas.width = width * pixelRatio
+          canvas.height = height * pixelRatio
+
+          var ctx = canvas.getContext('2d')
+          ctx.scale(pixelRatio, pixelRatio)
+
+          self.ctx = ctx
+          self.canvasWidth = width
+          self.canvasHeight = height
+          self.dpr = pixelRatio
+          self._canvasLeft = res[0].left || 0
+          self._canvasTop = res[0].top || 0
+          self.canvasReady = true
+
+          self._startAnimation()
+        })
+    },
+
+    _doRender: function() {
+      if (!this.ctx) return
+      var ts = [this.animBody, this.animMind, this.animWisdom, this.animAction, this.animWealth]
+      renderStar(this.ctx, this.canvasWidth, this.canvasHeight, ts)
+    },
+
+    /* ===========================
+    动画控制
+    =========================== */
+
+    _startAnimation: function() {
+      var self = this
+      if (!this._animator) {
+        this._animator = new FillAnimator(
+          function() {
+            return [self.animBody, self.animMind, self.animWisdom, self.animAction, self.animWealth]
+          },
+          function(interpolated) {
+            self.animBody = interpolated[0]
+            self.animMind = interpolated[1]
+            self.animWisdom = interpolated[2]
+            self.animAction = interpolated[3]
+            self.animWealth = interpolated[4]
+            self._doRender()
+          }
+        )
+      }
+      this._animator.start(this.activeTs, 600)
+    },
+
+    /* ===========================
+    点击检测
+    =========================== */
+
+    onCanvasTap: function(e) {
+      if (!this.canvasReady) return
+
+      var pageX = e.detail.x
+      var pageY = e.detail.y
+      if (typeof pageX !== 'number') {
+        var touch = e.touches ? e.touches[0] : null
+        if (touch) {
+          pageX = touch.x || touch.clientX || 0
+          pageY = touch.y || touch.clientY || 0
+        } else {
+          var changedTouch = e.changedTouches ? e.changedTouches[0] : null
+          if (changedTouch) {
+            pageX = changedTouch.x || changedTouch.clientX || 0
+            pageY = changedTouch.y || changedTouch.clientY || 0
+          } else {
+            return
+          }
+        }
+      }
+
+      var relX = pageX - (this._canvasLeft || 0)
+      var relY = pageY - (this._canvasTop || 0)
+      if (relX < 0 || relY < 0 || relX > this.canvasWidth || relY > this.canvasHeight) return
+
+      var cx = this.canvasWidth / 2
+      var cy = this.canvasHeight / 2
+      var outerR = Math.min(this.canvasWidth, this.canvasHeight) * 0.42
+      var armIdx = detectHitArm(relX, relY, cx, cy, outerR, calcStarVertices)
+      if (armIdx >= 0) {
+        this.onPointClick(armIndexToDomain(armIdx))
+      }
     }
   }
 }
@@ -118,13 +421,13 @@ export default {
 
 <style scoped>
 /* ============================================================
-   容器 — 暗色渐变背景
-   ============================================================ */
+容器
+============================================================ */
 .wuxing-sandbox {
   margin: 20rpx 30rpx;
-  padding: 24rpx 24rpx 20rpx;
-  background: linear-gradient(135deg, #0f0f23, #1a1a3e, #16213e);
-  border-radius: 28rpx;
+  padding: 20rpx 24rpx 24rpx;
+  background: linear-gradient(135deg, #1A2A4A, #1E3A5F, #16213E);
+  border-radius: 24rpx;
   position: relative;
   overflow: hidden;
 }
@@ -135,301 +438,122 @@ export default {
   left: -50%;
   width: 200%;
   height: 200%;
-  background: radial-gradient(ellipse at 30% 20%, rgba(255, 215, 0, 0.03), transparent 60%),
-              radial-gradient(ellipse at 70% 80%, rgba(66, 165, 245, 0.03), transparent 60%);
+  background: radial-gradient(ellipse at 30% 20%, rgba(91, 155, 213, 0.04), transparent 60%),
+    radial-gradient(ellipse at 70% 80%, rgba(66, 165, 245, 0.03), transparent 60%);
   pointer-events: none;
 }
 
-/* ============================================================
-   标题栏
-   ============================================================ */
+/* ---- 标题 ---- */
 .sandbox-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 20rpx;
+  margin-bottom: 12rpx;
   position: relative;
-  z-index: 2;
-}
-.sandbox-title {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #FFD700;
+  z-index: 5;
 }
+.sandbox-title { font-size: 26rpx; font-weight: bold; color: #8FC5E8; }
 .sandbox-badge {
   font-size: 20rpx;
-  color: rgba(255, 215, 0, 0.7);
-  background: rgba(255, 215, 0, 0.1);
+  color: rgba(143,197,232,0.7);
+  background: rgba(143,197,232,0.1);
   padding: 4rpx 14rpx;
   border-radius: 20rpx;
 }
 
-/* ============================================================
-   五角星区域
-   ============================================================ */
+/* ---- 五角星区域 ---- */
 .sandbox-star {
   position: relative;
-  height: 460rpx;
+  width: 100%;
   display: flex;
   justify-content: center;
   align-items: center;
 }
 
-/* ---- 背景光晕 ---- */
+/* 背景光晕 */
 .star-glow-bg {
-  position: absolute;
-  width: 360rpx;
-  height: 360rpx;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  background: radial-gradient(circle, rgba(255, 215, 0, 0.08), transparent 70%);
-  border-radius: 50%;
-  pointer-events: none;
+  position: absolute; width: 440rpx; height: 440rpx;
+  top: 50%; left: 50%; transform: translate(-50%, -50%);
+  background: radial-gradient(circle, rgba(91,155,213,0.10), transparent 70%);
+  border-radius: 50%; pointer-events: none;
 }
 
-/* ---- 五角星形状(clip-path) ---- */
-.star-shape {
+/* Canvas 点击感应层 */
+.canvas-tap-area {
   position: absolute;
-  width: 320rpx;
-  height: 304rpx;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  pointer-events: none;
-}
-.star-shape__fill {
-  position: absolute;
-  inset: 0;
-  clip-path: polygon(
-    50% 0%, 61% 35%, 98% 35%, 68% 57%,
-    79% 91%, 50% 70%, 21% 91%, 32% 57%,
-    2% 35%, 39% 35%
-  );
-  background: linear-gradient(135deg, rgba(255, 215, 0, 0.12), rgba(255, 180, 50, 0.04));
-  animation: starPulse 4s ease-in-out infinite;
-}
-.star-shape__outline {
-  position: absolute;
-  inset: -2rpx;
-  clip-path: polygon(
-    50% 0%, 61% 35%, 98% 35%, 68% 57%,
-    79% 91%, 50% 70%, 21% 91%, 32% 57%,
-    2% 35%, 39% 35%
-  );
-  background: transparent;
-  border: 1rpx solid rgba(255, 215, 0, 0.15);
-  /* fake border via slightly larger duplicate */
-}
-/* Real outline using pseudo element trick */
-.star-shape__outline::before {
-  content: '';
-  position: absolute;
-  inset: 0;
-  clip-path: polygon(
-    50% 0%, 61% 35%, 98% 35%, 68% 57%,
-    79% 91%, 50% 70%, 21% 91%, 32% 57%,
-    2% 35%, 39% 35%
-  );
-  background: transparent;
-  box-shadow: 0 0 20rpx rgba(255, 215, 0, 0.06);
-}
-
-@keyframes starPulse {
-  0%, 100% { opacity: 1; }
-  50% { opacity: 0.6; }
-}
-
-/* ---- 五角星连线(五条边) ---- */
-.pentagram-lines {
-  position: absolute;
-  inset: 0;
-  pointer-events: none;
-}
-.p-line {
-  position: absolute;
-  height: 2rpx;
-  background: linear-gradient(90deg, rgba(255, 215, 0, 0.2), rgba(255, 215, 0, 0.05));
-  transform-origin: left center;
-  top: 0;
-  left: 0;
+  top: 0; left: 0; right: 0; bottom: 0;
+  z-index: 2;
 }
 
-/* Line 1: Fire → Earth (top → lower-right) */
-.p-line--1 { top: 46rpx; left: 250rpx; width: 290rpx; transform: rotate(68deg); }
-/* Line 2: Earth → Wood (lower-right → upper-left) */
-.p-line--2 { top: 340rpx; left: 340rpx; width: 290rpx; transform: rotate(-106deg); }
-/* Line 3: Wood → Water (upper-left → upper-right) */
-.p-line--3 { top: 156rpx; left: 72rpx; width: 364rpx; transform: rotate(0deg); }
-/* Line 4: Water → Metal (upper-right → lower-left) */
-.p-line--4 { top: 156rpx; left: 400rpx; width: 290rpx; transform: rotate(106deg); }
-/* Line 5: Metal → Fire (lower-left → top) */
-.p-line--5 { top: 340rpx; left: 100rpx; width: 290rpx; transform: rotate(-68deg); }
-
 /* ============================================================
-   顶点
-   ============================================================ */
+顶点:图标 + 中文标签
+============================================================ */
 .star-point {
-  position: absolute;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  transform: translate(-50%, -50%);
-  z-index: 3;
-  cursor: pointer;
-}
-.point-hover {
-  opacity: 0.8;
-}
-
-/* 顶点定位 */
-.star-point--top  { top: 8%;   left: 50%; }
-.star-point--ur   { top: 32%;  left: 84%; }
-.star-point--lr   { top: 76%;  left: 72%; }
-.star-point--ll   { top: 76%;  left: 28%; }
-.star-point--ul   { top: 32%;  left: 16%; }
-
-/* ---- 顶点光晕 ---- */
-.point-glow {
-  position: absolute;
-  width: 90rpx;
-  height: 90rpx;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  border-radius: 50%;
-  opacity: 0.25;
-  pointer-events: none;
-}
-.point-glow--fire  { background: radial-gradient(circle, #FF6B35, transparent 70%); }
-.point-glow--water { background: radial-gradient(circle, #42A5F5, transparent 70%); }
-.point-glow--earth { background: radial-gradient(circle, #8D6E63, transparent 70%); }
-.point-glow--metal { background: radial-gradient(circle, #FFD700, transparent 70%); }
-.point-glow--wood  { background: radial-gradient(circle, #4CAF50, transparent 70%); }
+  position: absolute; transform: translate(-50%, -50%); z-index: 3; cursor: pointer;
+  display: flex; flex-direction: column; align-items: center;
+}
+.star-point--top { top: 3%; left: 50%; }
+.star-point--left { top: 38%; left: 5%; }
+.star-point--right { top: 38%; left: 95%; }
+.star-point--lr { top: 73%; left: 75%; }
+.star-point--ll { top: 73%; left: 25%; }
 
-/* ---- 顶点圆点 ---- */
 .point-dot {
-  width: 56rpx;
-  height: 56rpx;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  position: relative;
-  z-index: 1;
-}
-.point-dot--fire  { background: linear-gradient(135deg, #FF6B35, #E65100); box-shadow: 0 0 16rpx rgba(255, 107, 53, 0.4); }
-.point-dot--water { background: linear-gradient(135deg, #42A5F5, #1565C0); box-shadow: 0 0 16rpx rgba(66, 165, 245, 0.4); }
-.point-dot--earth { background: linear-gradient(135deg, #8D6E63, #5D4037); box-shadow: 0 0 16rpx rgba(141, 110, 99, 0.4); }
-.point-dot--metal { background: linear-gradient(135deg, #FFD700, #FF8F00); box-shadow: 0 0 16rpx rgba(255, 215, 0, 0.4); }
-.point-dot--wood  { background: linear-gradient(135deg, #4CAF50, #2E7D32); box-shadow: 0 0 16rpx rgba(76, 175, 80, 0.4); }
-
-.point-icon {
-  font-size: 28rpx;
-  line-height: 1;
-}
+  width: 52rpx; height: 52rpx; border-radius: 50%;
+  display: flex; align-items: center; justify-content: center;
+  box-shadow: 0 0 24rpx rgba(255,255,255,0.25);
+}
+.point-dot--fire { background: linear-gradient(135deg, #FF6B35, #E65100); }
+.point-dot--water { background: linear-gradient(135deg, #42A5F5, #1565C0); }
+.point-dot--earth { background: linear-gradient(135deg, #8D6E63, #5D4037); }
+.point-dot--metal { background: linear-gradient(135deg, #FFD700, #FF8F00); }
+.point-dot--wood { background: linear-gradient(135deg, #4CAF50, #2E7D32); }
+
+.point-icon { font-size: 26rpx; line-height: 1; }
 
-/* ---- 顶点标签 ---- */
 .point-label {
-  font-size: 20rpx;
-  color: rgba(255, 255, 255, 0.9);
-  font-weight: bold;
-  margin-top: 8rpx;
-  white-space: nowrap;
-}
-.point-status {
-  font-size: 16rpx;
-  color: #F97316;
+  font-size: 22rpx; font-weight: bold; color: rgba(255,255,255,0.9);
+  margin-top: 6rpx; text-shadow: 0 1rpx 4rpx rgba(0,0,0,0.5);
+  letter-spacing: 2rpx;
+}
+.point-energy {
+  font-size: 18rpx; color: rgba(143,197,232,0.8);
+  background: rgba(143,197,232,0.12);
+  padding: 1rpx 10rpx; border-radius: 12rpx;
   margin-top: 2rpx;
-  white-space: nowrap;
 }
 
 /* ============================================================
-   中心圆
-   ============================================================ */
+中心点击区域
+============================================================ */
 .star-center {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  width: 110rpx;
-  height: 110rpx;
-  border-radius: 50%;
-  display: flex;
-  flex-direction: column;
-  justify-content: center;
-  align-items: center;
-  z-index: 4;
-  background: radial-gradient(circle at 40% 35%, rgba(255, 215, 0, 0.15), rgba(255, 215, 0, 0.03));
-  border: 1rpx solid rgba(255, 215, 0, 0.2);
-  box-shadow: 0 0 30rpx rgba(255, 215, 0, 0.08), inset 0 0 20rpx rgba(255, 215, 0, 0.04);
-  animation: centerPulse 3s ease-in-out infinite;
-}
-
-@keyframes centerPulse {
-  0%, 100% { box-shadow: 0 0 30rpx rgba(255, 215, 0, 0.08), inset 0 0 20rpx rgba(255, 215, 0, 0.04); }
-  50% { box-shadow: 0 0 40rpx rgba(255, 215, 0, 0.15), inset 0 0 30rpx rgba(255, 215, 0, 0.08); }
+  position: absolute; top: 50%; left: 50%;
+  transform: translate(-50%, -50%); z-index: 4;
+  width: 130rpx; height: 130rpx; border-radius: 50%;
+  cursor: pointer;
 }
 
-.center-icon-wrap {
-  width: 36rpx;
-  height: 36rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 2rpx;
-}
-.center-icon {
-  font-size: 28rpx;
-  color: #FFD700;
-}
-.center-text {
-  font-size: 20rpx;
-  color: #FFD700;
-  font-weight: bold;
-  line-height: 1.3;
-}
-.center-sub {
-  font-size: 16rpx;
-  color: rgba(255, 215, 0, 0.5);
-  line-height: 1.2;
+.energy-hint {
+  position: absolute; bottom: -30rpx; left: 50%;
+  transform: translateX(-50%);
+  font-size: 18rpx; color: rgba(143,197,232,0.4);
+  white-space: nowrap;
 }
 
 /* ============================================================
-   提示 & 维度条
-   ============================================================ */
-.shenke-hint {
-  text-align: center;
-  margin-top: 8rpx;
-  position: relative;
-  z-index: 2;
-}
-.hint-text {
-  font-size: 22rpx;
-  color: rgba(255, 215, 0, 0.7);
-}
-
+维度详情条
+============================================================ */
 .dimension-bar {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-top: 16rpx;
-  gap: 6rpx;
-  position: relative;
-  z-index: 2;
+  display: flex; align-items: center; justify-content: center;
+  margin-top: 16rpx; gap: 8rpx;
+  position: relative; z-index: 5; flex-wrap: wrap;
 }
 .dim-item {
-  font-size: 20rpx;
-  padding: 4rpx 12rpx;
-  border-radius: 10rpx;
-}
-.dim-item--wood  { color: #4CAF50;  background: rgba(76, 175, 80, 0.1); }
-.dim-item--fire  { color: #FF6B35;  background: rgba(255, 107, 53, 0.1); }
-.dim-item--earth { color: #8D6E63;  background: rgba(141, 110, 99, 0.1); }
-.dim-item--metal { color: #FFD700;  background: rgba(255, 215, 0, 0.1); }
-.dim-item--water { color: #42A5F5;  background: rgba(66, 165, 245, 0.1); }
-.dim-arrow {
-  font-size: 18rpx;
-  color: rgba(255, 255, 255, 0.2);
+  font-size: 20rpx; padding: 4rpx 12rpx; border-radius: 10rpx; white-space: nowrap;
 }
+.dim-item--wood { color: #4CAF50; background: rgba(76,175,80,0.12); }
+.dim-item--fire { color: #FF6B35; background: rgba(255,107,53,0.12); }
+.dim-item--earth { color: #8D6E63; background: rgba(141,110,99,0.12); }
+.dim-item--metal { color: #FFD700; background: rgba(255,215,0,0.12); }
+.dim-item--water { color: #42A5F5; background: rgba(66,165,245,0.12); }
 </style>

+ 4 - 3
cfc-frontend/custom-tab-bar/index.vue

@@ -45,8 +45,9 @@ export default {
       currentIndex: 0,
       normalTabs: [
         { text: '首页', pagePath: '/pages/index/index', icon: '/static/tab-home.png', selectedIcon: '/static/tab-home-active.png' },
-        { text: '任务', pagePath: '/pages/tasks/tasks', icon: '/static/tab-task.png', selectedIcon: '/static/tab-task-active.png' },
-        { text: '心愿单', pagePath: '/pages/rewards/rewards', icon: '/static/tab-reward.png', selectedIcon: '/static/tab-reward-active.png' },
+        { text: '身体', pagePath: '/pages/body/index', icon: '/static/tab-body.png', selectedIcon: '/static/tab-body-active.png' },
+        { text: '心智', pagePath: '/pages/mind/index', icon: '/static/tab-mind.png', selectedIcon: '/static/tab-mind-active.png' },
+        { text: '行动', pagePath: '/pages/shop/index', icon: '/static/tab-action.png', selectedIcon: '/static/tab-action-active.png' },
         { text: '我的', pagePath: '/pages/profile/profile', icon: '/static/tab-profile.png', selectedIcon: '/static/tab-profile-active.png' }
       ],
       teacherTabs: [
@@ -91,7 +92,7 @@ export default {
     switchTab(item, index) {
       this.currentIndex = index
       // 原生TabBar页使用switchTab,非Tab页使用redirectTo(比reLaunch轻量)
-      const nativeTabPages = ['/pages/index/index', '/pages/discover/index', '/pages/shop/index', '/pages/profile/profile']
+      const nativeTabPages = ['/pages/index/index', '/pages/body/index', '/pages/mind/index', '/pages/shop/index', '/pages/profile/profile']
       if (nativeTabPages.includes(item.pagePath)) {
         uni.switchTab({ url: item.pagePath })
       } else {

+ 114 - 216
cfc-frontend/uni.scss

@@ -1,13 +1,20 @@
 /**
  * ============================================================
- * 知心益家 (ZXYJ) Design System — UniApp SCSS Variables
+ * 浠艾福 (XAF) Design System — UniApp SCSS Variables
  * ============================================================
  *
- * 色彩体系: 暖橙 + 天蓝 (儿童友好 + 专业信任)
- * 风格: Claymorphism (孩子端) / 扁平卡片 (家长端) / 专业工具 (规划师端)
- * 角色色彩: 家长=暖橙 / 孩子=暖橙+天蓝 / 规划师=蓝色调
+ * 色彩体系: 柔和天蓝 + 珊瑚暖色 (信任专业 + 家庭温暖)
+ * 风格: 极简扁平圆角 (全角色统一) 
+ * Logo配色参考: #6BB9F0 (中蓝) / #D6EAF8 (浅蓝) / ❤白色心形
+ * 
+ * 设计原则:
+ * - 扁平圆角:全端统一,无Claymorphism
+ * - 蓝色为品牌色:传递信任/专业/平静
+ * - 珊瑚暖色为点缀:保留"家庭温暖"和"关怀"
+ * - 心形符号:品牌识别元素
+ * - 减少装饰动效,聚焦内容可读性
  *
- * v2.0 — 2026-06-05
+ * v3.0 — 2026-06-06
  * ============================================================
  */
 
@@ -15,49 +22,50 @@
 // 1. 品牌色彩 (Brand Colors)
 // ============================================================
 
-/* 主色调 - 暖橙 (Primary) */
-$uni-primary: #F97316;
-$uni-primary-light: #FB923C;
-$uni-primary-dark: #EA580C;
+/* 主色调 - 柔和天蓝 (Logo蓝色) */
+$uni-primary: #5B9BD5;          // Logo中蓝 - 主品牌色
+$uni-primary-light: #8FC5E8;    // 浅蓝
+$uni-primary-dark: #3A7CC4;     // 深蓝
+$uni-primary-pale: #D6EAF8;     // 极浅蓝 (Logo背景色)
 
-/* 强调色 - 天蓝 (Accent) */
-$uni-accent: #0EA5E9;
-$uni-accent-light: #38BDF8;
-$uni-accent-dark: #0284C7;
+/* 辅助暖色 - 珊瑚粉 (家庭温暖) */
+$uni-warm: #F08A7A;             // 心形暖色 - 保留"关爱"感
+$uni-warm-light: #F4B0A5;
+$uni-warm-dark: #E06A58;
 
 /* 语义色 (Semantic) */
-$uni-success: #22C55E;
-$uni-success-light: #4ADE80;
-$uni-warning: #F59E0B;
-$uni-warning-light: #FBBF24;
-$uni-error: #EF4444;
-$uni-error-light: #F87171;
-
-/* 中性色 (Neutral) */
-$uni-bg-color: #FFF7ED;       // 暖白背景
-$uni-bg-color-grey: #FFF2E4;  // 浅暖灰
-$uni-bg-color-dark: #F5E6D3;  // 暖灰
-$uni-surface: #FFFFFF;         // 卡片/表面
-$uni-border-color: #FED7AA;   // 浅橙边框
-$uni-border-color-light: #FFEDD5; // 更浅边框
-$uni-text-color: #3D2E1E;     // 暖深棕 (正文)
-$uni-text-color-secondary: #6B5A4A; // 次要文字
-$uni-text-color-grey: #94A3B8; // 灰蓝 (禁用/占位)
-$uni-text-color-placeholder: #A8A29E; // 占位文字
+$uni-success: #4CAF50;
+$uni-success-light: #81C784;
+$uni-warning: #FFB74D;
+$uni-warning-light: #FFD54F;
+$uni-error: #EF5350;
+$uni-error-light: #E57373;
+$uni-info: #5B9BD5;             // 复用了品牌蓝
+
+/* 中性色 (Neutral) — 冷调中性,匹配蓝色系 */
+$uni-bg-color: #F5F9FC;        // 极浅蓝灰背景
+$uni-bg-color-grey: #EDF2F7;   // 浅灰蓝
+$uni-bg-color-dark: #E2E8F0;   // 灰蓝
+$uni-surface: #FFFFFF;          // 卡片/表面
+$uni-border-color: #CBD5E1;    // 冷灰边框
+$uni-border-color-light: #E2E8F0;
+$uni-text-color: #1E293B;      // 深蓝灰 (正文)
+$uni-text-color-secondary: #64748B; // 次要文字
+$uni-text-color-grey: #94A3B8; // 灰色文字
+$uni-text-color-placeholder: #94A3B8;
 $uni-text-color-inverse: #FFFFFF;
 
 // ============================================================
 // 2. 字体系统 (Typography)
 // ============================================================
 
-/* 字体栈 - 微信小程序不支持自定义字体,使用系统回退 */
+/* 字体栈 - 小程序不支持自定义字体 */
 $uni-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',
   'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
   'Helvetica Neue', Helvetica, Arial, system-ui, sans-serif;
 
 $uni-font-family-display: $uni-font-family;
-$uni-font-family-mono: 'JetBrains Mono', 'SF Mono', 'Fira Code',
-  'Cascadia Code', monospace;
+$uni-font-family-mono: 'SF Mono', 'Fira Code', 'Cascadia Code', monospace;
 
 /* 字号 */
 $uni-font-size-xs: 20rpx;
@@ -85,74 +93,65 @@ $uni-spacing-lg: 32rpx;
 $uni-spacing-xl: 48rpx;
 $uni-spacing-2xl: 64rpx;
 
-$uni-page-padding: 32rpx;  // 页面左右边距
+$uni-page-padding: 32rpx;
 
 // ============================================================
 // 4. 圆角 (Border Radius)
 // ============================================================
 
-$uni-radius-sm: 12rpx;
-$uni-radius-md: 20rpx;
-$uni-radius-lg: 32rpx;
-$uni-radius-xl: 40rpx;
+$uni-radius-sm: 10rpx;
+$uni-radius-md: 16rpx;
+$uni-radius-lg: 24rpx;
+$uni-radius-xl: 32rpx;
 $uni-radius-full: 50%;
 $uni-radius-round: 9999rpx;
 
 // ============================================================
-// 5. 阴影 (Shadows)
+// 5. 阴影 (Shadows) — 极简风格,轻微阴影
 // ============================================================
 
-$uni-shadow-sm: 0 4rpx 12rpx rgba(249, 115, 22, 0.06);
-$uni-shadow-md: 0 8rpx 24rpx rgba(249, 115, 22, 0.10);
-$uni-shadow-lg: 0 16rpx 48rpx rgba(249, 115, 22, 0.14);
-$uni-shadow-inner: inset 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
-
-/* Claymorphism 双重阴影(孩子端风格) */
-$uni-shadow-clay: inset -4rpx -4rpx 12rpx rgba(249, 115, 22, 0.05),
-  6rpx 6rpx 18rpx rgba(249, 115, 22, 0.10);
-
-/* 规划师端阴影(蓝色调) */
-$uni-shadow-blue-sm: 0 4rpx 12rpx rgba(59, 130, 246, 0.06);
-$uni-shadow-blue-md: 0 8rpx 24rpx rgba(59, 130, 246, 0.10);
+$uni-shadow-sm: 0 2rpx 8rpx rgba(91, 155, 213, 0.06);
+$uni-shadow-md: 0 4rpx 16rpx rgba(91, 155, 213, 0.08);
+$uni-shadow-lg: 0 8rpx 32rpx rgba(91, 155, 213, 0.10);
+$uni-shadow-inner: inset 0 1rpx 4rpx rgba(0, 0, 0, 0.04);
 
 // ============================================================
-// 6. 动画 (Animations)
+// 6. 动画 (Animations) — 节制使用
 // ============================================================
 
 $uni-transition-fast: 0.15s ease;
 $uni-transition-base: 0.25s ease;
-$uni-transition-slow: 0.4s cubic-bezier(0.16, 1, 0.3, 1);
-$uni-transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
+$uni-transition-slow: 0.3s ease;
 
 // ============================================================
-// 7. CSS 自定义属性 (CSS Custom Properties)
-//    用于运行时动态覆盖和组件级使用
+// 7. CSS 自定义属性
 // ============================================================
 
 :root {
   // 品牌色
-  --color-primary: #F97316;
-  --color-primary-light: #FB923C;
-  --color-primary-dark: #EA580C;
-  --color-accent: #0EA5E9;
-  --color-accent-light: #38BDF8;
+  --color-primary: #5B9BD5;
+  --color-primary-light: #8FC5E8;
+  --color-primary-dark: #3A7CC4;
+  --color-primary-pale: #D6EAF8;
+  --color-warm: #F08A7A;
+  --color-warm-light: #F4B0A5;
 
   // 语义色
-  --color-success: #22C55E;
-  --color-warning: #F59E0B;
-  --color-error: #EF4444;
+  --color-success: #4CAF50;
+  --color-warning: #FFB74D;
+  --color-error: #EF5350;
 
   // 中性色
-  --bg: #FFF7ED;
-  --bg-grey: #FFF2E4;
+  --bg: #F5F9FC;
+  --bg-grey: #EDF2F7;
   --surface: #FFFFFF;
-  --text: #3D2E1E;
-  --text-secondary: #6B5A4A;
+  --text: #1E293B;
+  --text-secondary: #64748B;
   --muted: #94A3B8;
-  --border: #FED7AA;
-  --border-light: #FFEDD5;
+  --border: #CBD5E1;
+  --border-light: #E2E8F0;
 
-  // 间距(转换为 px 近似值,rpx 在组件内使用)
+  // 间距
   --space-xs: 4px;
   --space-sm: 8px;
   --space-md: 12px;
@@ -161,37 +160,24 @@ $uni-transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
   --space-page: 16px;
 
   // 圆角
-  --radius-sm: 6px;
-  --radius-md: 10px;
-  --radius-lg: 16px;
-  --radius-xl: 20px;
-
-  // 阴影
-  --shadow-sm: 0 2px 6px rgba(249, 115, 22, 0.06);
-  --shadow-md: 0 4px 12px rgba(249, 115, 22, 0.10);
-  --shadow-lg: 0 8px 24px rgba(249, 115, 22, 0.14);
-  --shadow-clay: inset -2px -2px 6px rgba(249, 115, 22, 0.05),
-    3px 3px 10px rgba(249, 115, 22, 0.10);
-
-  // 字体
-  --font-display: -apple-system, BlinkMacSystemFont, 'Segoe UI',
-    'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
-  --font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI',
-    'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
-  --font-mono: 'JetBrains Mono', 'SF Mono', monospace;
-
-  // 角色专属色(规划师端用蓝色系)
-  --color-teacher-primary: #3B82F6;
-  --color-teacher-bg: #EFF6FF;
+  --radius-sm: 5px;
+  --radius-md: 8px;
+  --radius-lg: 12px;
+  --radius-xl: 16px;
+
+  // 阴影 — 极简风格
+  --shadow-sm: 0 1px 4px rgba(91, 155, 213, 0.06);
+  --shadow-md: 0 2px 8px rgba(91, 155, 213, 0.08);
+  --shadow-lg: 0 4px 16px rgba(91, 155, 213, 0.10);
 }
 
 // ============================================================
-// 8. 实用工具类 (Utility Classes)
+// 8. 实用工具类
 // ============================================================
 
-// --- 文字 ---
+// --- 文字色彩 ---
 .text-primary { color: $uni-primary; }
-.text-accent { color: $uni-accent; }
+.text-warm { color: $uni-warm; }
 .text-success { color: $uni-success; }
 .text-warning { color: $uni-warning; }
 .text-error { color: $uni-error; }
@@ -209,7 +195,7 @@ $uni-transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
 
 // --- 背景 ---
 .bg-primary { background-color: $uni-primary; }
-.bg-accent { background-color: $uni-accent; }
+.bg-warm { background-color: $uni-warm; }
 .bg-surface { background-color: $uni-surface; }
 .bg-page { background-color: $uni-bg-color; }
 
@@ -259,36 +245,34 @@ $uni-transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
 }
 
 // ============================================================
-// 9. Claymorphism 风格 (孩子端视觉)
+// 9. 扁平圆角卡片 (Flat Rounded Card) — 全端统一风格
 // ============================================================
 
-@mixin clay-card($border-color: $uni-border-color) {
+@mixin flat-card($border-color: $uni-border-color-light) {
   border-radius: $uni-radius-md;
-  border: 4rpx solid $border-color;
-  background: linear-gradient(145deg, #FFFFFF, $uni-bg-color);
-  box-shadow: $uni-shadow-clay;
+  background: $uni-surface;
+  border: 1rpx solid $border-color;
+  box-shadow: $uni-shadow-sm;
 }
 
-@mixin clay-button($bg-color: $uni-primary) {
+@mixin flat-button($bg-color: $uni-primary) {
   border-radius: $uni-radius-lg;
-  border: 3px solid $uni-border-color;
-  background: linear-gradient(145deg, lighten($bg-color, 8%), $bg-color);
-  box-shadow:
-    inset -3rpx -3rpx 10rpx rgba(255, 255, 255, 0.3),
-    4rpx 4rpx 12rpx rgba($bg-color, 0.3);
-  transition: transform $uni-transition-fast;
+  background: $bg-color;
+  color: #FFFFFF;
+  border: none;
+  transition: opacity $uni-transition-fast;
 
   &:active {
-    transform: scale(0.96);
+    opacity: 0.85;
   }
 }
 
-.clay-card {
-  @include clay-card;
+.flat-card {
+  @include flat-card;
 }
 
 // ============================================================
-// 10. 关键帧动画
+// 10. 关键帧动画(精简)
 // ============================================================
 
 @keyframes fadeIn {
@@ -297,114 +281,27 @@ $uni-transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
 }
 
 @keyframes slideUp {
-  from {
-    opacity: 0;
-    transform: translateY(30rpx);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
-}
-
-@keyframes slideDown {
-  from {
-    opacity: 0;
-    transform: translateY(-20rpx);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
+  from { opacity: 0; transform: translateY(20rpx); }
+  to { opacity: 1; transform: translateY(0); }
 }
 
 @keyframes scaleIn {
-  from {
-    opacity: 0;
-    transform: scale(0.9);
-  }
-  to {
-    opacity: 1;
-    transform: scale(1);
-  }
+  from { opacity: 0; transform: scale(0.95); }
+  to { opacity: 1; transform: scale(1); }
 }
 
-@keyframes bounceIn {
-  0% {
-    opacity: 0;
-    transform: scale(0.3);
-  }
-  50% {
-    transform: scale(1.05);
-  }
-  70% {
-    transform: scale(0.95);
-  }
-  100% {
-    opacity: 1;
-    transform: scale(1);
-  }
-}
-
-@keyframes pulseGlow {
-  0%, 100% {
-    box-shadow: 0 0 0 0 rgba($uni-primary, 0.4);
-  }
-  50% {
-    box-shadow: 0 0 0 12rpx rgba($uni-primary, 0);
-  }
-}
-
-@keyframes float {
-  0%, 100% {
-    transform: translateY(0);
-  }
-  50% {
-    transform: translateY(-8rpx);
-  }
-}
-
-@keyframes shimmer {
-  0% {
-    background-position: -200% 0;
-  }
-  100% {
-    background-position: 200% 0;
-  }
-}
-
-@keyframes confettiFall {
-  0% {
-    transform: translateY(-100%) rotate(0deg);
-    opacity: 1;
-  }
-  100% {
-    transform: translateY(100vh) rotate(720deg);
-    opacity: 0;
-  }
-}
-
-@keyframes pointsFly {
-  0% {
-    opacity: 1;
-    transform: translateY(0) scale(1);
-  }
-  100% {
-    opacity: 0;
-    transform: translateY(-100rpx) scale(1.5);
-  }
+@keyframes pulseSoft {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.7; }
 }
 
 // 动画实用类
 .animate-fade-in { animation: fadeIn $uni-transition-base both; }
 .animate-slide-up { animation: slideUp $uni-transition-slow both; }
-.animate-slide-down { animation: slideDown $uni-transition-base both; }
 .animate-scale-in { animation: scaleIn $uni-transition-slow both; }
-.animate-bounce-in { animation: bounceIn $uni-transition-bounce both; }
-.animate-pulse-glow { animation: pulseGlow 2s ease-in-out infinite; }
-.animate-float { animation: float 3s ease-in-out infinite; }
+.animate-pulse-soft { animation: pulseSoft 3s ease-in-out infinite; }
 
-// 交错入场(用于列表)
+// 交错入场
 @for $i from 1 through 20 {
   .animate-stagger-#{$i} {
     animation-delay: #{$i * 60}ms;
@@ -412,14 +309,14 @@ $uni-transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
 }
 
 // ============================================================
-// 11. 骨架屏 (Skeleton)
+// 11. 骨架屏
 // ============================================================
 
 @mixin skeleton {
   background: linear-gradient(
     90deg,
     $uni-border-color-light 25%,
-    #FFF7ED 50%,
+    #F5F9FC 50%,
     $uni-border-color-light 75%
   );
   background-size: 200% 100%;
@@ -427,17 +324,18 @@ $uni-transition-bounce: 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
   border-radius: $uni-radius-sm;
 }
 
-.skeleton {
-  @include skeleton;
+@keyframes shimmer {
+  0% { background-position: -200% 0; }
+  100% { background-position: 200% 0; }
 }
 
+.skeleton { @include skeleton; }
 .skeleton-text {
   @include skeleton;
   height: 28rpx;
   margin-bottom: 12rpx;
 }
-
 .skeleton-block {
   @include skeleton;
   height: 160rpx;
-}
+}