Просмотр исходного кода

feat(home): B2 能量沙盘保留与适配(C1/C2/C3 三冲突处理)

保留清单(不得破坏项全部保留)
- Canvas 双图层(starCanvas + flowCanvas)+ 点击感应层
- live / preview 双模式、右上切换与徽章逻辑
- GateLock 沙盘锁链路(sandboxLocked / sandboxLockGate)未触碰
- §1.3 全量 props 契约(mode/scores/overallScore/members/personalData/
  badgeText/showDimensionBar/dimensions/totalEnergy/totalHealthIndex/
  starHeight/familyUnlocked/memberCount/currentView)原样保留
- 环形顶点顺序 [心,身,智,富,行] = sortedDimensions 序,未改

C1 冲突:顶点与维度条 emoji(火/叶/水滴/闪电/地球)全部替换为内联 SVG
- 24×24 viewBox、stroke-width 1.8、round cap/join,mp-weixin 用
  data:image/svg+xml 作为 image src(仓库既有模式)
- 五行语义改由形状承载:心=火焰 身=山 智=闪电 富=水滴 行=叶
- 组件忽略接口返回的 icon 字段,始终按 code 取内置图形

C2 冲突:沙盘"身"色统一到 $dim-body 家族
- 移除组件内紫蓝渐变切换器与 $brown-warm 身色,一稿内不再出现两个"身"色

C3 冲突:.sandbox-star 明确高度
- CSS 兜底 height/min-height + flex-shrink:0 + 父级 starHeight 内联覆盖

配色统一全站 $dim-*
- 新增 $dim-data-* 五个数据笔触深档令牌(uni.scss),Canvas 与图标同源
- 五行主色仅作装饰(tint 底 / 光晕 / 填充),数据笔触用 deep 档
- 对填充 $dim-sandbox-fill 实测对比度 4.56:1 ~ 6.57:1,均 >= 3:1

顺带修正既有对齐 bug
- render/hitdetect 由旧 ts 序改为环形序 [mind,body,wisdom,wealth,action],
  臂尖=outer[i],消除画布臂色与 DOM 顶点错位 3~4/5 的问题
- flowCanvas 外接半径系数 0.50 -> 0.46,与 render/hitdetect 对齐

preview 模式改轻量引导条
- 标题 + 「登录查看完整报告」徽章 + 弱化五边形轮廓,不初始化/不画完整星图
- 仍 emit point-click,保持父级登录引导链路

其他
- 维度条线性序改为 身 -> 智 -> 富 -> 行 -> 心(规格 §4.2)
- 交互目标 >= 44px(88rpx);尊重 prefers-reduced-motion(CSS 降级 + Canvas JS 降级)
- 移除全部 emoji 与符号字形;:class/:key 不含表达式与带参方法调用

index-home/index.vue 未改动(B1 正在修改,本批零侵入)
Sisyphus 2 недель назад
Родитель
Сommit
bcdf7d81cd

+ 19 - 15
cfc-frontend/components/wuxing-sandbox-helpers/flow-animation.js

@@ -2,29 +2,33 @@
  * wuxing-sandbox-helpers/flow-animation.js
  * 五行能量流动粒子动画
  * 
- * 相生循环:沿外圆路径,能量依次转化(土→金→水→木→火)
- *   身(土)→智(金)→富(水)→行(木)→心(火)→身(土)
- *   路径:outer[i] → outer[(i+1)%5] (弧线)
+ * 相生循环:沿外圆路径,能量依次转化(土->金->水->木->火)
+ *   身(土)->智(金)->富(水)->行(木)->心(火)->身(土)
+ *   路径:outer[i] -> outer[(i+1)%5] (弧线)
  *   颜色随路径从源维度过渡到目标维度
  * 
  * 相克循环:沿五角星边路径,能量抑制
- *   身(土)→富(水), 富(水)→心(火), 心(火)→智(金), 智(金)→行(木), 行(木)→身(土)
- *   路径:outer[i] → inner[(i+1)%5] → outer[(i+2)%5] (星边直线)
+ *   身(土)->富(水), 富(水)->心(火), 心(火)->智(金), 智(金)->行(木), 行(木)->身(土)
+ *   路径:outer[i] -> inner[(i+1)%5] -> outer[(i+2)%5] (星边直线)
  *   粒子保持源维度颜色
  */
 
+/**
+ * 粒子颜色:与 render.js 同一套 deep 档(uni.scss $dim-data-*)
+ * 暖底 #FCE7CE 上对比度 ≥3:1,粒子不再用旧高饱和五行色
+ */
 var WUXING_COLORS = [
-  { r: 141, g: 110, b: 99 },  // 0 土·身 (body)
-  { r: 255, g: 107, b: 53 },  // 1 火·心 (mind)
-  { r: 255, g: 215, b: 0 },   // 2 金·智 (wisdom)
-  { r: 76, g: 175, b: 80 },   // 3 木·行 (action)
-  { r: 66, g: 165, b: 245 }   // 4 水·富 (wealth)
+  { r: 156, g: 74, b: 26 },   // 0 土·身 (body)   $dim-data-body   #9C4A1A
+  { r: 157, g: 23, b: 77 },   // 1 火·心 (mind)   $dim-data-mind   #9D174D
+  { r: 67, g: 56, b: 202 },   // 2 金·智 (wisdom) $dim-data-wisdom #4338CA
+  { r: 4, g: 120, b: 87 },    // 3 木·行 (action) $dim-data-action #047857
+  { r: 146, g: 64, b: 14 }    // 4 水·富 (wealth) $dim-data-wealth #92400E
 ]
 
 var DIM_KEYS = ['body', 'mind', 'wisdom', 'action', 'wealth']
 
 /**
- * 维度代码 → 颜色
+ * 维度代码 -> 颜色
  */
 function colorForDimCode(code) {
   var idx = DIM_KEYS.indexOf(code)
@@ -70,7 +74,7 @@ function Particle(path, srcColor, dstColor, speed, baseSize) {
   this.path = path             // [{x,y},...] 路径控制点
   this.srcColor = srcColor     // {r,g,b}
   this.dstColor = dstColor     // {r,g,b}(相克 与 srcColor 相同)
-  this.progress = 0            // 0→1
+  this.progress = 0            // 0->1
   this.speed = speed
   this.baseSize = baseSize || 3
   this.x = path[0].x
@@ -141,7 +145,7 @@ export function FlowAnimation(ctx, w, h, opts) {
 
 /**
  * 构建相生路径(外圆弧线)
- * 顶点顺序 0→1→2→3→4→0
+ * 顶点顺序 0->1->2->3->4->0
  */
 FlowAnimation.prototype._buildShengPaths = function () {
   this._shengPaths = []
@@ -158,8 +162,8 @@ FlowAnimation.prototype._buildShengPaths = function () {
 
 /**
  * 构建相克路径(五角星边)
- * 相克索引对:0→2, 2→4, 4→1, 1→3, 3→0
- * 路径:outer[s] → inner[(s+1)%5] → outer[e]
+ * 相克索引对:0->2, 2->4, 4->1, 1->3, 3->0
+ * 路径:outer[s] -> inner[(s+1)%5] -> outer[e]
  */
 FlowAnimation.prototype._buildKePaths = function () {
   this._kePaths = []

+ 10 - 8
cfc-frontend/components/wuxing-sandbox-helpers/hitdetect.js

@@ -1,9 +1,13 @@
 /**
  * wuxing-sandbox-helpers/hitdetect.js
  * 五角星点击检测工具(纯函数,无状态)
+ *
+ * 命中索引与渲染保持同一套环形顺序(= sortedDimensions 序):
+ * 心(火) -> 身(土) -> 智(金) -> 富(水) -> 行(木)
  */
 
-var DIM_KEYS = ['body', 'mind', 'wisdom', 'action', 'wealth']
+/** 环形顶点顺序(= sortedDimensions 序):0 心 1 身 2 智 3 富 4 行 */
+var DIM_KEYS = ['mind', 'body', 'wisdom', 'wealth', 'action']
 
 /**
  * 射线法(Ray Casting)判断点是否在任意多边形内
@@ -38,14 +42,12 @@ export function pointInPolygon(x, y, polygon) {
 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
+  // 环形第 i 个顶点 = outer[i],星臂 polygon 以 outer[i] 为外尖
   return [
     { x: cx, y: cy },
-    inner[iBefore],
-    outer[outerIdx],
-    inner[iAfter]
+    inner[(armIndex + 4) % 5],
+    outer[armIndex],
+    inner[armIndex]
   ]
 }
 
@@ -71,7 +73,7 @@ export function detectHitArm(relX, relY, cx, cy, outerR, calcVertices) {
 }
 
 /**
- * 星臂索引 → 维度 key 映射
+ * 星臂索引 -> 维度 key 映射
  * @param {number} armIndex
  * @returns {string} 'body'|'mind'|'wisdom'|'action'|'wealth'
  */

+ 94 - 66
cfc-frontend/components/wuxing-sandbox-helpers/render.js

@@ -1,8 +1,43 @@
 /**
  * wuxing-sandbox-helpers/render.js
  * 五角星 Canvas 渲染(纯函数,无状态)
+ *
+ * 配色口径(2026-10-27 主理人裁定 · 首页重设计 B2)
+ * - 环形顶点顺序 = sortedDimensions 序:心(火) -> 身(土) -> 智(金) -> 富(水) -> 行(木),不可改
+ * - ts 入参即按上面的环形顺序排列:[mind, body, wisdom, wealth, action]
+ * - 数据笔触(星臂描边)统一用 deep 档(uni.scss $dim-data-*),对填充 #FCE7CE ≥3:1
+ * - 五行主色仅作装饰(星臂 tint 填充 / 光晕),不再承载数据识别
  */
 
+/** 环形顶点顺序(= sortedDimensions 序):0 心 1 身 2 智 3 富 4 行 */
+export var DIM_RING = ['mind', 'body', 'wisdom', 'wealth', 'action']
+
+/** 数据笔触深档(与 uni.scss $dim-data-* 一致) */
+var STROKE = {
+  mind:   { r: 157, g: 23,  b: 77  },  // $dim-data-mind   #9D174D
+  body:   { r: 156, g: 74,  b: 26  },  // $dim-data-body   #9C4A1A
+  wisdom: { r: 67,  g: 56,  b: 202 },  // $dim-data-wisdom #4338CA
+  wealth: { r: 146, g: 64,  b: 14  },  // $dim-data-wealth #92400E
+  action: { r: 4,   g: 120, b: 87  }   // $dim-data-action #047857
+}
+
+/** 装饰主色(tint 填充 / 光晕;uni.scss $dim-* 主档,仅装饰不承载数据) */
+var DECOR = {
+  mind:   { r: 255, g: 107, b: 157 },  // $dim-mind   #FF6B9D
+  body:   { r: 194, g: 112, b: 61  },  // $dim-body   #C2703D
+  wisdom: { r: 99,  g: 102, b: 241 },  // $dim-wisdom #6366F1
+  wealth: { r: 245, g: 158, b: 11  },  // $dim-wealth #F59E0B
+  action: { r: 16,  g: 185, b: 129 }   // $dim-action #10B981
+}
+
+/** 暖中性(星廓 / 太极),与填充 #FCE7CE 同色系 */
+var SAND = { r: 176, g: 137, b: 104 }  // 暖沙描边
+var INK  = { r: 74,  g: 55,  b: 40  }  // 暖褐墨(太极深面 / 深色数字)
+
+function rgba(c, a) {
+  return 'rgba(' + c.r + ', ' + c.g + ', ' + c.b + ', ' + a + ')'
+}
+
 /**
  * 计算五角星顶点坐标
  * 外顶点:5 个,72° 等分,起始角度 -90°(正上方)
@@ -31,21 +66,12 @@ export function calcStarVertices(cx, cy, outerR) {
   return { outer: outer, inner: inner }
 }
 
-/** 五色配置(与 CSS 一致,ts 顺序 = [body, mind, wisdom, action, wealth]) */
-var COLORS = [
-  { r: 141, g: 110, b: 99 },  // 土·身 (body)
-  { r: 255, g: 107, b: 53 },  // 火·心 (mind)
-  { r: 255, g: 215, b: 0 },   // 金·智 (wisdom)
-  { r: 76, g: 175, b: 80 },   // 木·行 (action)
-  { r: 66, g: 165, b: 245 }   // 水·富 (wealth)
-]
-
 /**
  * 绘制完整五角星图(含填充、描边、太极)
  * @param {CanvasRenderingContext2D} ctx
  * @param {number} w - canvas 宽度
  * @param {number} h - canvas 高度
- * @param {Array<number>} ts - 5 维归一化值 (0~1),顺序 [body,mind,wisdom,action,wealth]
+ * @param {Array<number>} ts - 5 维归一化值 (0~1),环形顺序 [mind,body,wisdom,wealth,action]
  * @param {Object} [options]
  * @param {boolean} [options.showTaiji=true] - 是否绘制太极
  * @param {number} [options.familyEnergy] - 家庭能量值(显示在阳面)
@@ -70,33 +96,34 @@ export function renderStar(ctx, w, h, ts, options) {
   var outer = vertices.outer
   var inner = vertices.inner
 
-  // 绘制每个星臂
+  // 绘制每个星臂(第 i 个臂对应环形第 i 个顶点 outer[i])
   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]
+    var code = DIM_RING[i]
+    var t = ts && ts[i] != null ? ts[i] : 0
+    if (t < 0) t = 0
+    if (t > 1) t = 1
+    var stroke = STROKE[code] || STROKE.mind
+    var decor = DECOR[code] || DECOR.mind
 
-    // 星臂 polygon: center, inner[iBefore], outer[outerIdx], inner[iAfter]
+    // 星臂 polygon: center, inner[i-1], outer[i], inner[i]
     var polygon = [
       { x: cx, y: cy },
-      inner[iBefore],
-      outer[outerIdx],
-      inner[iAfter]
+      inner[(i + 4) % 5],
+      outer[i],
+      inner[i]
     ]
 
-    // --- 背景填充(低透明度) ---
+    // --- 装饰 tint 底(主色低透明,仅装饰) ---
     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.fillStyle = rgba(decor, 0.07)
     ctx.fill()
 
-    // --- 渐变填充(t 控制色彩饱和度,覆盖整个星臂) ---
+    // --- 装饰渐变(主色弱填充,数据强度只体现在笔触深浅) ---
     if (t > 0.01) {
       ctx.save()
       ctx.beginPath()
@@ -108,28 +135,29 @@ export function renderStar(ctx, w, h, ts, options) {
       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) + ')')
+      grad.addColorStop(0, rgba(decor, 0.30 * t))
+      grad.addColorStop(0.55, rgba(decor, 0.16 * t))
+      grad.addColorStop(1, rgba(decor, 0.02 * t))
       ctx.fillStyle = grad
       ctx.fillRect(0, 0, w, h)
 
       ctx.restore()
     }
 
-    // --- 星臂描边 ---
+    // --- 数据笔触:deep 档描边,强度随分数变化(对 #FCE7CE ≥3:1) ---
+    // alpha 下限 0.7(叠加暖底后混合对比度仍 ≥3:1),上限 1.0(满档)
     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.strokeStyle = rgba(stroke, 0.7 + 0.3 * t)
+    ctx.lineWidth = 1.5 + 1.5 * t
     ctx.stroke()
   }
 
-  // --- 五角星外轮廓(10 个顶点依次连接) ---
+  // --- 五角星外轮廓(10 个顶点依次连接,暖沙中性) ---
   ctx.beginPath()
   ctx.moveTo(outer[0].x, outer[0].y)
   ctx.lineTo(inner[0].x, inner[0].y)
@@ -142,7 +170,7 @@ export function renderStar(ctx, w, h, ts, options) {
   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.strokeStyle = rgba(SAND, 0.45)
   ctx.lineWidth = 2
   ctx.stroke()
 
@@ -154,7 +182,7 @@ export function renderStar(ctx, w, h, ts, options) {
   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.strokeStyle = rgba(SAND, 0.28)
   ctx.lineWidth = 1.5
   ctx.stroke()
 
@@ -166,9 +194,9 @@ export function renderStar(ctx, w, h, ts, options) {
 }
 
 /**
- * 绘制太极阴阳鱼
- * 阳(亮面·白)= 家庭能量,阴(暗面·黑)= 个人能量
- * 数值显示在两个鱼眼上
+ * 绘制太极阴阳鱼(暖白底版)
+ * 下半深面(暖褐墨)= 阳(家庭能量),上半浅面 = 阴(个人能量)
+ * 数值显示在两个鱼眼上:深面用白字,浅面用深墨字
  * @param {CanvasRenderingContext2D} ctx
  * @param {number} cx - 中心 x
  * @param {number} cy - 中心 y
@@ -179,68 +207,68 @@ export function renderStar(ctx, w, h, ts, options) {
 export function drawTaiji(ctx, cx, cy, r, familyEnergy, personalEnergy) {
   ctx.save()
 
-  // 1. 外圆背景(透明,仅保留极淡边框)
+  // 1. 底圆(暖白)
   ctx.beginPath()
   ctx.arc(cx, cy, r, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.strokeStyle = 'rgba(0,0,0,0.08)'
-  ctx.lineWidth = 0.5
+  ctx.fillStyle = 'rgba(255,255,255,0.94)'
+  ctx.fill()
+  ctx.strokeStyle = rgba(SAND, 0.45)
+  ctx.lineWidth = 1
   ctx.stroke()
 
-  // 2. 左半边(阴,半透明)
+  // 2. 下半深面(暖褐墨)
   ctx.beginPath()
   ctx.moveTo(cx, cy)
-  ctx.arc(cx, cy, r, Math.PI / 2, -Math.PI / 2)
+  ctx.arc(cx, cy, r, 0, Math.PI)
   ctx.closePath()
-  ctx.fillStyle = 'rgba(120,120,120,0.2)'
+  ctx.fillStyle = rgba(INK, 0.92)
   ctx.fill()
 
-  // 3. 上半小圆左半弧(半透明浅色,在暗半圆中)
+  // 3. 上半小鱼(深面在浅区里的"头")
   ctx.beginPath()
-  ctx.moveTo(cx, cy - r)
-  ctx.arc(cx, cy - r / 2, r / 2, Math.PI / 2, -Math.PI / 2)
+  ctx.arc(cx, cy - r / 2, r / 2, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = 'rgba(255,255,255,0.35)'
+  ctx.fillStyle = rgba(INK, 0.92)
   ctx.fill()
 
-  // 4. 下半小圆右半弧(半透明深色,在浅半圆中)
+  // 4. 下半小鱼(浅面在深区里的"头")
   ctx.beginPath()
-  ctx.moveTo(cx, cy + r)
-  ctx.arc(cx, cy + r / 2, r / 2, -Math.PI / 2, Math.PI / 2)
+  ctx.arc(cx, cy + r / 2, r / 2, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = 'rgba(120,120,120,0.2)'
+  ctx.fillStyle = 'rgba(255,255,255,0.94)'
   ctx.fill()
 
   // 鱼眼半径(够大,容纳数值文字)
-  var eyeR = r * 0.16
+  var eyeR = r * 0.19
 
-  // 5. 阳鱼眼(浅区域中的暗点)
+  // 5. 上鱼眼(深面上的亮点)— 个人能量
   ctx.beginPath()
-  ctx.arc(cx, cy + r / 2, eyeR, 0, Math.PI * 2)
+  ctx.arc(cx, cy - r / 2, eyeR, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = 'rgba(100,100,100,0.4)'
+  ctx.fillStyle = 'rgba(255,255,255,0.92)'
   ctx.fill()
 
-  // 6. 阴鱼眼(暗区域中的亮点)
+  // 6. 下鱼眼(浅面上的暗点)— 家庭能量
   ctx.beginPath()
-  ctx.arc(cx, cy - r / 2, eyeR, 0, Math.PI * 2)
+  ctx.arc(cx, cy + r / 2, eyeR, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = 'rgba(255,255,255,0.5)'
+  ctx.fillStyle = rgba(INK, 0.92)
   ctx.fill()
 
-  // 7. 阳眼文字 — 家庭能量
-  var familyVal = (familyEnergy != null && !isNaN(familyEnergy)) ? Math.round(familyEnergy) : '--'
-  var eyeFontSize = Math.round(eyeR * 1.2)
+  // 7. 上眼文字 — 个人能量(浅底深墨字)
+  var personalVal = (personalEnergy != null && !isNaN(personalEnergy)) ? Math.round(personalEnergy) : '--'
+  var eyeFontSize = Math.round(eyeR * 1.05)
   ctx.font = 'bold ' + eyeFontSize + 'px sans-serif'
   ctx.textAlign = 'center'
   ctx.textBaseline = 'middle'
-  ctx.fillStyle = 'rgba(255,255,255,0.85)'
-  ctx.fillText(familyVal + '', cx, cy + r / 2)
-
-  // 8. 阴眼文字 — 个人能量
-  var personalVal = (personalEnergy != null && !isNaN(personalEnergy)) ? Math.round(personalEnergy) : '--'
-  ctx.fillStyle = 'rgba(80,80,80,0.7)'
+  ctx.fillStyle = rgba(INK, 0.95)
   ctx.fillText(personalVal + '', cx, cy - r / 2)
 
+  // 8. 下眼文字 — 家庭能量(深底白字)
+  var familyVal = (familyEnergy != null && !isNaN(familyEnergy)) ? Math.round(familyEnergy) : '--'
+  ctx.fillStyle = 'rgba(255,255,255,0.96)'
+  ctx.fillText(familyVal + '', cx, cy + r / 2)
+
   ctx.restore()
-}
+}

+ 350 - 282
cfc-frontend/components/wuxing-sandbox.vue

@@ -5,17 +5,17 @@
     <view class="sandbox-header">
       <image class="sandbox-logo" src="/static/logo.png" mode="aspectFit"></image>
       <text class="sandbox-title">浠艾福能量沙盘</text>
-      <!-- 右上角:live 模式显示个人/全家切换,preview 模式显示徽章文案 -->
-      <view class="sandbox-switch" v-if="mode !== 'preview'" @click="onToggle">
-        <text class="sandbox-switch-icon">{{ activeMemberIcon }}</text>
+      <!-- 右上角:live 模式显示个人/全家切换(preview 为轻量引导条,徽章移入引导条) -->
+      <view class="sandbox-switch" v-if="!isPreview" @click="onToggle">
+        <image class="sandbox-switch-icon" :src="activeMemberIcon" mode="aspectFit"></image>
         <text class="sandbox-switch-label">{{ activeMemberLabel }}</text>
-        <text class="sandbox-switch-arrow">⇄</text>
+        <image class="sandbox-switch-arrow" :src="uiIcons.swap" mode="aspectFit"></image>
       </view>
-      <text class="sandbox-badge" v-else>{{ displayBadgeText }}</text>
     </view>
   </slot>
 
-  <view class="sandbox-star" :style="{height: starHeight}">
+  <!-- live:完整星图(双 Canvas + 点击感应层) -->
+  <view class="sandbox-star" v-if="!isPreview" :style="{height: starHeight}">
     <view class="star-glow-bg"></view>
 
     <!-- Canvas 2D 五行星图 -->
@@ -27,71 +27,63 @@
     <!-- Canvas 点击感应层 -->
     <view class="canvas-tap-area" @click="onCanvasTap"></view>
 
-    <!-- 顶点:从 sortedDimensions 顺时针排 [心, 身, 智, 富, 行] -->
-    <view class="star-point star-point--top star-point--row" @click="onPointClick(sortedDimensions[0].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[0].code)">
-        <text class="point-icon">{{ sortedDimensions[0].icon }}</text>
+    <!-- 顶点:sortedDimensions 顺时针 [心, 身, 智, 富, 行](图标形状承载五行语义) -->
+    <view
+      v-for="pt in sortedDimensions"
+      :key="pt.code"
+      class="star-point"
+      :class="pt.pointClass"
+      @click="onPointClick(pt.code)"
+    >
+      <view class="point-dot" :class="pt.dotClass">
+        <image class="point-icon" :src="pt.iconUri" mode="aspectFit"></image>
       </view>
-      <text class="point-label">{{ sortedDimensions[0].name }}</text>
-      <text class="point-energy">{{ sortedDimensions[0].energy }}</text>
-    </view>
-    <view class="star-point star-point--right" @click="onPointClick(sortedDimensions[1].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[1].code)">
-        <text class="point-icon">{{ sortedDimensions[1].icon }}</text>
-      </view>
-      <text class="point-label">{{ sortedDimensions[1].name }}</text>
-      <text class="point-energy">{{ sortedDimensions[1].energy }}</text>
-    </view>
-    <view class="star-point star-point--lr star-point--row" @click="onPointClick(sortedDimensions[2].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[2].code)">
-        <text class="point-icon">{{ sortedDimensions[2].icon }}</text>
-      </view>
-      <text class="point-label">{{ sortedDimensions[2].name }}</text>
-      <text class="point-energy">{{ sortedDimensions[2].energy }}</text>
-    </view>
-    <view class="star-point star-point--ll star-point--rev" @click="onPointClick(sortedDimensions[3].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[3].code)">
-        <text class="point-icon">{{ sortedDimensions[3].icon }}</text>
-      </view>
-      <text class="point-label">{{ sortedDimensions[3].name }}</text>
-      <text class="point-energy">{{ sortedDimensions[3].energy }}</text>
-    </view>
-    <view class="star-point star-point--left" @click="onPointClick(sortedDimensions[4].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[4].code)">
-        <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>
+      <text class="point-label">{{ pt.name }}</text>
+      <text class="point-energy" :class="pt.energyClass">{{ pt.energy }}</text>
     </view>
 
     <!-- 中心点击区域(太极由 Canvas 绘制) -->
     <view class="star-center" @click="onCenterClick"></view>
   </view>
 
+  <!-- preview:轻量引导条(标题 + 徽章 + 弱化五边形轮廓,不画完整星图) -->
+  <view class="sandbox-preview" v-else @click="onPreviewTap">
+    <image class="preview-pentagon" :src="uiIcons.pentagon" mode="aspectFit"></image>
+    <view class="preview-copy">
+      <text class="preview-title">五维能量沙盘</text>
+      <text class="preview-sub">亲子共同成长的能量地图</text>
+    </view>
+    <text class="preview-badge">{{ displayBadgeText }}</text>
+  </view>
+
   <!-- dimension-bar slot -->
-  <slot name="dimension-bar" v-if="showDimensionBar && mode !== 'preview'">
+  <slot name="dimension-bar" v-if="showDimensionBar && !isPreview">
     <view class="dimension-bar">
-      <view class="dim-item dim-item--earth">🌏身 {{ activeScores[0] }}%</view>
-      <view class="dim-item dim-item--fire">🔥心 {{ activeScores[1] }}%</view>
-      <view class="dim-item dim-item--metal">⚡智 {{ activeScores[2] }}%</view>
-      <view class="dim-item dim-item--wood">🌿行 {{ activeScores[3] }}%</view>
-      <view class="dim-item dim-item--water">💧富 {{ activeScores[4] }}%</view>
+      <view
+        v-for="item in linearDimensions"
+        :key="item.code"
+        class="dim-item"
+        :class="item.itemClass"
+      >
+        <image class="dim-item-icon" :src="item.iconUri" mode="aspectFit"></image>
+        <text class="dim-item-text">{{ item.name }} {{ item.energy }}%</text>
+      </view>
     </view>
   </slot>
 
-  <!-- 右上角成员选择面板 -->
+  <!-- 成员选择面板 -->
   <view v-if="memberPickerVisible" class="member-picker-mask" @click="memberPickerVisible = false">
     <view class="member-picker-panel" @click.stop>
       <view class="member-picker-title">切换视角</view>
       <view
-        v-for="option in memberOptions"
+        v-for="option in memberOptionsActive"
         :key="option.key"
         class="member-picker-item"
-        :class="{ 'member-picker-item--active': isOptionActive(option) }"
+        :class="{ 'member-picker-item--active': option.active }"
         @click="selectOption(option)"
       >
         <text class="member-picker-label">{{ option.label }}</text>
-        <text class="member-picker-check" v-if="isOptionActive(option)">✓</text>
+        <image class="member-picker-check" v-if="option.active" :src="uiIcons.check" mode="aspectFit"></image>
       </view>
     </view>
   </view>
@@ -112,20 +104,116 @@ import {
   resolveSandboxScores
 } from '@/utils/wuxing-sandbox.js'
 
-var DIM_KEYS = ['body', 'mind', 'wisdom', 'action', 'wealth']
-
-/** 维度 → 五行映射(API 缺字段时的兜底) */
+/**
+ * 五行图标(内联 SVG,24×24 viewBox / stroke-width 1.8 / 无填充)
+ *
+ * mp-weixin WXML 不支持模板内联 <svg>,故用 data:image/svg+xml 作为 <image> src
+ * (仓库既有模式:pages/shop/order-list/order-list.vue)。SVG 文档是独立渲染上下文,
+ * currentColor 无法继承宿主 CSS,故 stroke 颜色在此处按 $dim-data-* / $brand-primary-deep
+ * 词表取色(与 uni.scss 令牌一一对应,注释映射;Canvas/JS 侧无法使用 SCSS 变量)。
+ *
+ * 五行语义由形状承载:心=火焰 身=山 智=闪电 富=水滴 行=叶。
+ */
+function svgUri(paths, color) {
+  var inner = ''
+  for (var i = 0; i < paths.length; i++) {
+    inner += '<path d="' + paths[i] + '"/>'
+  }
+  var svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="' +
+    color + '" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' + inner + '</svg>'
+  return 'data:image/svg+xml,' + encodeURIComponent(svg)
+}
+
+var ICON_PATHS = {
+  flame: [
+    'M12 2.8c1.9 2.7 3.5 4.9 3.5 7.7a3.5 3.5 0 0 1-7 0c0-1.4.6-2.6 1.7-3.7.2 1.2.9 2 1.9 2.4-.6-2.3 0-4.6-.1-6.4z',
+    'M12 7.6c1 1.4 1.7 2.4 1.7 3.7a1.7 1.7 0 0 1-3.4 0c0-1.3.7-2.3 1.7-3.7z'
+  ],
+  mountain: [
+    'M2.8 19.2 8.8 8.6l3.4 5.6 2.2-3.6 6.8 8.6z'
+  ],
+  lightning: [
+    'M13.4 2.6 5.8 13.4h4.9l-1 8 7.6-10.8h-4.9z'
+  ],
+  droplet: [
+    'M12 2.8c3.2 3.8 5.2 6.5 5.2 9.2a5.2 5.2 0 0 1-10.4 0c0-2.7 2-5.4 5.2-9.2z'
+  ],
+  leaf: [
+    'M5.6 18.6C6 11.4 11.6 5.2 19.6 4.6c.4 8.2-5.6 14.2-14 14z',
+    'M7 17c2.6-4.3 6-7.4 10-9.6'
+  ],
+  person: [
+    'M12 11.6a3.4 3.4 0 1 0 0-6.8 3.4 3.4 0 0 0 0 6.8z',
+    'M5.8 20.2c.5-3.5 3-5.2 6.2-5.2s5.7 1.7 6.2 5.2'
+  ],
+  family: [
+    'M8.6 12.2a3 3 0 1 0 0-6 3 3 0 0 0 0 6z',
+    'M3.4 19.6c.4-3.1 2.6-4.6 5.2-4.6s4.8 1.5 5.2 4.6',
+    'M17.4 8.6a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4z',
+    'M17.8 15.2c1.4.3 2.8 1.6 2.8 3.6'
+  ],
+  swap: [
+    'M4.4 9.4h14.2',
+    'M15.6 6.6l3 2.8-3 2.8',
+    'M19.6 14.6H5.4',
+    'M8.4 11.8l-3 2.8 3 2.8'
+  ],
+  check: [
+    'M5 12.8 9.6 17.4 19 7.6'
+  ],
+  pentagon: [
+    'M12 3.4 20.8 9.2l-3.4 10.2H6.6L3.2 9.2z'
+  ]
+}
+
+// 数据笔触深档(与 uni.scss $dim-data-* 一致)—— 图标颜色 = 数据笔触色,保证对填充 #FCE7CE ≥3:1
+var DIM_ICON_URIS = {
+  mind:   svgUri(ICON_PATHS.flame,    '#9D174D'), // $dim-data-mind   #9D174D
+  body:   svgUri(ICON_PATHS.mountain, '#9C4A1A'), // $dim-data-body   #9C4A1A
+  wisdom: svgUri(ICON_PATHS.lightning,'#4338CA'), // $dim-data-wisdom #4338CA
+  wealth: svgUri(ICON_PATHS.droplet,  '#92400E'), // $dim-data-wealth #92400E
+  action: svgUri(ICON_PATHS.leaf,     '#047857')  // $dim-data-action #047857
+}
+
+// UI 图标(品牌深橙,对白底 / 品牌浅底均 ≥3:1;$brand-primary-deep #C2410C)
+var UI_ICON_URIS = {
+  person:   svgUri(ICON_PATHS.person,   '#C2410C'),
+  family:   svgUri(ICON_PATHS.family,   '#C2410C'),
+  swap:     svgUri(ICON_PATHS.swap,     '#C2410C'),
+  check:    svgUri(ICON_PATHS.check,    '#C2410C'),
+  pentagon: svgUri(ICON_PATHS.pentagon, '#C2410C')
+}
+
+/** 环形顶点定位类(与 sortedDimensions 序一一对应:上/右/右下/左下/左) */
+var RING_POS = [
+  'star-point--top star-point--row',
+  'star-point--right',
+  'star-point--lr star-point--row',
+  'star-point--ll star-point--rev',
+  'star-point--left'
+]
+
+/** 线性维度条顺序(规格 §4.2):身 -> 智 -> 富 -> 行 -> 心;idx = activeScores 下标 */
+var LINEAR_ORDER = [
+  { code: 'body',   idx: 0 },
+  { code: 'wisdom', idx: 2 },
+  { code: 'wealth', idx: 4 },
+  { code: 'action', idx: 3 },
+  { code: 'mind',   idx: 1 }
+]
+
+/** 维度 -> 五行映射(API 缺字段时的兜底;图标一律用内置 SVG,忽略接口返回的 emoji) */
 var DIM_META = {
-  mind:   { name: '心', icon: '🔥', element: '火', color: 'fire' },
-  action: { name: '行', icon: '🌿', element: '木', color: 'wood' },
-  wealth: { name: '富', icon: '💧', element: '水', color: 'water' },
-  wisdom: { name: '智', icon: '⚡', element: '金', color: 'metal' },
-  body:   { name: '身', icon: '🌏', element: '土', color: 'earth' }
+  mind:   { name: '心', element: '火' },
+  body:   { name: '身', element: '土' },
+  wisdom: { name: '智', element: '金' },
+  wealth: { name: '富', element: '水' },
+  action: { name: '行', element: '木' }
 }
 
 export default {
   props: {
-    /** 模式:'preview' 随机数据 / 'live' 真实数据 */
+    /** 模式:'preview' 引导条 / 'live' 真实数据 */
     mode: {
       type: String,
       default: 'preview',
@@ -216,7 +304,8 @@ export default {
       animWealth: 0,
       _animator: null,
       _flowAnim: null,
-      _flowCanvasReady: false
+      _flowCanvasReady: false,
+      uiIcons: UI_ICON_URIS
     }
   },
   computed: {
@@ -229,6 +318,20 @@ export default {
     memberOptions: function() {
       return buildMemberOptions(this.members, this.personalData)
     },
+    /** 成员选择选项(预计算 active 标志,避免模板 :class 中调用方法) */
+    memberOptionsActive: function() {
+      return this.memberOptions.map(function(option) {
+        var active = option.viewMode === this.viewMode &&
+          (option.viewMode !== 'member' || String(option.memberId) === String(this.selectedMemberId))
+        return {
+          key: option.key,
+          label: option.label,
+          viewMode: option.viewMode,
+          memberId: option.memberId,
+          active: active
+        }
+      }, this)
+    },
     totalMemberCount: function() {
       if (this.memberCount > 0) return this.memberCount
       return this.members ? this.members.length : 0
@@ -254,7 +357,7 @@ export default {
       return '本人'
     },
     activeMemberIcon: function() {
-      return this.viewMode === 'family' ? '👨‍👩‍👧‍👦' : '👤'
+      return this.viewMode === 'family' ? this.uiIcons.family : this.uiIcons.person
     },
     /** 当前活跃的五维值数组 [body, mind, wisdom, action, wealth] */
     activeScores: function() {
@@ -262,23 +365,20 @@ export default {
       return resolveSandboxScores(this.viewMode, this.scores, this.personalData, this.currentMember)
     },
     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.isPreview) return 0
       return resolveSandboxOverall(this.viewMode, this.scores, this.personalData, this.currentMember)
     },
+    /**
+     * 动画目标值 ts,按环形顶点序 [mind, body, wisdom, wealth, action] 排列
+     * (与 render.js DIM_RING / sortedDimensions 顺序一致,不可改)
+     */
     activeTs: function() {
-      return [
-        this.activeScores[0] / 100,
-        this.activeScores[1] / 100,
-        this.activeScores[2] / 100,
-        this.activeScores[3] / 100,
-        this.activeScores[4] / 100
-      ]
+      var s = this.activeScores
+      return [s[1] / 100, s[0] / 100, s[2] / 100, s[4] / 100, s[3] / 100]
     },
     displayBadgeText: function() {
       if (this.badgeText) return this.badgeText
+      if (this.isPreview) return '登录查看完整报告'
       var label = this.viewMode === 'family' ? '家庭能量' : (this.activeMemberLabel + '能量')
       return label + ' ' + (this.activeOverall || 0) + '%'
     },
@@ -288,18 +388,9 @@ export default {
     hasMultipleMembers: function() {
       return this.totalMemberCount > 1
     },
-    activeClassName: function() {
-      return this.currentView === 'personal' ? 'toggle-item--active' : ''
-    },
-    activeFamilyClassName: function() {
-      return this.currentView === 'family' ? 'toggle-item--active' : ''
-    },
     /** 家庭能量值(用于太极阳面) */
     familyEnergy: function() {
-      if (this.isPreview) {
-        var s = this.previewScores
-        return Math.round((s[0] + s[1] + s[2] + s[3] + s[4]) / 5)
-      }
+      if (this.isPreview) return 0
       return this.overallScore || 0
     },
     /** 当前选中视角的能量值(用于太极阴面) */
@@ -307,9 +398,9 @@ export default {
       if (this.isPreview) return 0
       return this.activeOverall || 0
     },
-    /** 按五行顺序绕圈排列
-     *  固定序:心(火)→身(土)→智(金)→富(水)→行(木),心始终在顶部
-     *  其余按五行相生序顺时针依次排:左边→右边→左下→右下
+    /**
+     * 环形顶点序(= sortedDimensions,不可改):心(火)->身(土)->智(金)->富(水)->行(木)
+     * 心始终在顶部,其余按五行相生序顺时针:左->右->左下->右下
      */
     sortedDimensions: function() {
       var order = ['mind', 'body', 'wisdom', 'wealth', 'action']
@@ -334,14 +425,33 @@ export default {
         all.push({
           code: code,
           name: d.name || meta.name || '',
-          icon: d.icon || meta.icon || '',
           element: d.element || meta.element || '',
           energy: score,
-          healthIndex: score
+          healthIndex: score,
+          pointClass: RING_POS[j],
+          dotClass: 'point-dot--' + code,
+          energyClass: 'point-energy--' + code,
+          iconUri: DIM_ICON_URIS[code] || ''
         })
       }
       return all
-      // all = [心→顶, 身→右, 智→右下, 富→左下, 行→左]
+      // all = [心->顶, 身->右, 智->右下, 富->左下, 行->左]
+    },
+    /** 底部线性维度条(规格 §4.2):身 -> 智 -> 富 -> 行 -> 心 */
+    linearDimensions: function() {
+      var out = []
+      for (var i = 0; i < LINEAR_ORDER.length; i++) {
+        var def = LINEAR_ORDER[i]
+        var meta = DIM_META[def.code] || {}
+        out.push({
+          code: def.code,
+          name: meta.name || '',
+          energy: this.activeScores[def.idx] || 0,
+          itemClass: 'dim-item--' + def.code,
+          iconUri: DIM_ICON_URIS[def.code] || ''
+        })
+      }
+      return out
     }
   },
   watch: {
@@ -389,21 +499,8 @@ export default {
   mounted: function() {
     var self = this
 
-    // 预览模式:生成随机五维值(缓存以避免每次 mount 重算)
-    if (this.isPreview) {
-      var cacheKey = '_wuxing_preview_scores'
-      var cached = uni.getStorageSync(cacheKey)
-      if (cached && Array.isArray(cached) && cached.length === 5) {
-        this.previewScores = cached
-      } else {
-        var scores = []
-        for (var i = 0; i < 5; i++) {
-          scores.push(Math.floor(Math.random() * 51) + 35)
-        }
-        this.previewScores = scores
-        uni.setStorageSync(cacheKey, scores)
-      }
-    }
+    // preview:轻量引导条,不初始化 Canvas、不画完整星图
+    if (this.isPreview) return
 
     // 等待 DOM 就绪后初始化 Canvas
     setTimeout(function() {
@@ -459,15 +556,6 @@ export default {
       })
     },
 
-    isOptionActive: function(option) {
-      if (!option) return false
-      if (option.viewMode !== this.viewMode) return false
-      if (option.viewMode === 'member') {
-        return String(option.memberId) === String(this.selectedMemberId)
-      }
-      return true
-    },
-
     selectOption: function(option) {
       if (!option) return
       if (option.viewMode === 'family' && this.totalMemberCount < 2) {
@@ -501,18 +589,17 @@ export default {
       })
     },
 
+    /** preview 引导条点击:仍触发 point-click,由父级驱动登录 */
+    onPreviewTap: function() {
+      this.$emit('point-click', { code: '', viewMode: this.viewMode, member: null })
+    },
+
     /** 右上角:打开成员选择面板(本人 / 各成员 / 全家) */
     onToggle: function() {
       if (this.isPreview) return
       this.memberPickerVisible = true
     },
 
-    /** 维度 code → CSS 颜色类名(不依赖 API 的 element 字段) */
-    dimElementColor: function(code) {
-      var meta = DIM_META[code]
-      return meta ? meta.color : 'fire'
-    },
-
     /* ===========================
     Canvas 初始化 & 渲染
     =========================== */
@@ -552,7 +639,8 @@ export default {
 
     _doRender: function() {
       if (!this.ctx) return
-      var ts = [this.animBody, this.animMind, this.animWisdom, this.animAction, this.animWealth]
+      // ts 环形序 [mind, body, wisdom, wealth, action](与 render.js DIM_RING / sortedDimensions 一致)
+      var ts = [this.animMind, this.animBody, this.animWisdom, this.animWealth, this.animAction]
       renderStar(this.ctx, this.canvasWidth, this.canvasHeight, ts, {
         familyEnergy: this.familyEnergy,
         personalEnergy: this.personalEnergy
@@ -563,19 +651,41 @@ export default {
     动画控制
     =========================== */
 
+    _prefersReducedMotion: function() {
+      try {
+        if (typeof window !== 'undefined' && window.matchMedia) {
+          return window.matchMedia('(prefers-reduced-motion: reduce)').matches
+        }
+      } catch (e) {}
+      return false
+    },
+
     _startAnimation: function() {
       var self = this
+
+      // 降低动效偏好:直接落值,不做填充动画(Canvas 无 CSS 降级,只能 JS 侧判断)
+      if (this._prefersReducedMotion()) {
+        var target = this.activeTs
+        this.animMind = target[0]
+        this.animBody = target[1]
+        this.animWisdom = target[2]
+        this.animWealth = target[3]
+        this.animAction = target[4]
+        this._doRender()
+        return
+      }
+
       if (!this._animator) {
         this._animator = new FillAnimator(
           function() {
-            return [self.animBody, self.animMind, self.animWisdom, self.animAction, self.animWealth]
+            return [self.animMind, self.animBody, self.animWisdom, self.animWealth, self.animAction]
           },
           function(interpolated) {
-            self.animBody = interpolated[0]
-            self.animMind = interpolated[1]
+            self.animMind = interpolated[0]
+            self.animBody = interpolated[1]
             self.animWisdom = interpolated[2]
-            self.animAction = interpolated[3]
-            self.animWealth = interpolated[4]
+            self.animWealth = interpolated[3]
+            self.animAction = interpolated[4]
             self._doRender()
           }
         )
@@ -608,7 +718,8 @@ export default {
           var ctx = canvas.getContext('2d')
           ctx.scale(pixelRatio, pixelRatio)
 
-          var outerR = Math.min(width, height) * 0.50
+          // 与 render.js / hitdetect.js 保持一致的外接半径系数
+          var outerR = Math.min(width, height) * 0.46
           var vertices = calcStarVertices(width / 2, height / 2, outerR)
 
           self._flowAnim = new FlowAnimation(ctx, width, height, {
@@ -616,6 +727,10 @@ export default {
             inner: vertices.inner,
             dimOrder: dimOrder
           })
+
+          // 降低动效偏好:不启动粒子动画
+          if (self._prefersReducedMotion()) return
+
           self._flowAnim.start()
           self._flowCanvasReady = true
         })
@@ -664,27 +779,18 @@ export default {
 
 <style scoped lang="scss">
 /* ============================================================
-容器
+容器:浅色暖调卡片(与 B1 .dim-band 视觉语言一致)
 ============================================================ */
 .wuxing-sandbox {
   margin: 20rpx $uni-space-lg;
   padding: 20rpx 24rpx 24rpx;
-  background: linear-gradient(135deg, $facade-ink-navy, $accent-navy, $app-ink-night-2);
-  border-radius: 24rpx;
+  background: $app-surface;
+  border: 1rpx solid rgba($brand-primary, 0.12);
+  border-radius: $radius-lg;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
   position: relative;
   overflow: hidden;
 }
-.wuxing-sandbox::before {
-  content: '';
-  position: absolute;
-  top: -50%;
-  left: -50%;
-  width: 200%;
-  height: 200%;
-  background: radial-gradient(ellipse at 30% 20%, rgba(249,115,22,0.04), transparent 60%),
-    radial-gradient(ellipse at 70% 80%, rgba(251,146,60,0.03), transparent 60%);
-  pointer-events: none;
-}
 
 /* ---- 标题 ---- */
 .sandbox-header {
@@ -695,37 +801,28 @@ export default {
   position: relative;
   z-index: 5;
 }
-.sandbox-title { font-size: calc(26rpx * var(--font-scale, 1)); font-weight: bold; color: #8FC5E8; margin-left: 8rpx; }
+.sandbox-title { font-size: calc(26rpx * var(--font-scale, 1)); font-weight: bold; color: $app-text; margin-left: 8rpx; }
 .sandbox-logo { width: 32rpx; height: 32rpx; flex-shrink: 0; }
-.sandbox-badge {
-  font-size: calc(20rpx * var(--font-scale, 1));
-  color: rgba(143,197,232,0.7);
-  background: rgba(143,197,232,0.1);
-  padding: 4rpx 14rpx;
-  border-radius: 24rpx;
-  margin-left: auto;
-}
+
+/* ---- 右上角切换(触控 ≥44px = 88rpx) ---- */
 .sandbox-switch {
   display: flex;
   align-items: center;
   gap: 6rpx;
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $app-surface;
-  background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep);
-  padding: 10rpx 20rpx;
-  border-radius: 24rpx;
+  min-height: 88rpx;
+  padding: 0 20rpx;
   margin-left: auto;
   flex-shrink: 0;
-  box-shadow: 0 4rpx 12rpx rgba(102,126,234,0.45);
-  border: 1rpx solid rgba(255,255,255,0.25);
-}
-.sandbox-switch:active {
-  opacity: 0.85;
-  box-shadow: none;
-}
-.sandbox-switch-icon { font-size: calc(24rpx * var(--font-scale, 1)); line-height: 1; }
+  border-radius: 44rpx;
+  color: $brand-primary-deep;
+  background: $brand-primary-soft;
+  border: 1rpx solid rgba($brand-primary, 0.18);
+  transition: opacity $duration-fast;
+}
+.sandbox-switch:active { opacity: 0.8; }
+.sandbox-switch-icon { width: 32rpx; height: 32rpx; flex-shrink: 0; }
 .sandbox-switch-label { font-size: calc(22rpx * var(--font-scale, 1)); font-weight: 700; max-width: 140rpx; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
-.sandbox-switch-arrow { font-size: calc(20rpx * var(--font-scale, 1)); opacity: 0.8; }
+.sandbox-switch-arrow { width: 26rpx; height: 26rpx; flex-shrink: 0; opacity: 0.7; }
 
 /* ---- 成员选择面板 ---- */
 .member-picker-mask {
@@ -745,7 +842,7 @@ export default {
   overflow-y: auto;
   box-sizing: border-box;
   padding: 28rpx 28rpx calc(24rpx + env(safe-area-inset-bottom));
-  background: #ffffff;
+  background: $app-surface;
   border-radius: 24rpx 24rpx 0 0;
   box-shadow: 0 -8rpx 32rpx rgba(0, 0, 0, 0.12);
 }
@@ -753,7 +850,7 @@ export default {
   padding-bottom: 16rpx;
   font-size: calc(26rpx * var(--font-scale, 1));
   font-weight: 700;
-  color: #333333;
+  color: $app-text;
   text-align: center;
 }
 .member-picker-item {
@@ -764,39 +861,35 @@ export default {
   padding: 0 24rpx;
   margin-top: 8rpx;
   border-radius: 16rpx;
-  background: #f7f8fc;
-}
-.member-picker-item--active {
-  background: rgba(102, 126, 234, 0.12);
-}
-.member-picker-label {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: #333333;
-}
-.member-picker-item--active .member-picker-label {
-  color: #667eea;
-  font-weight: 700;
-}
-.member-picker-check {
-  font-size: calc(30rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: #667eea;
+  background: $app-bg;
+  transition: background $duration-fast;
 }
+.member-picker-item--active { background: $brand-primary-soft; }
+.member-picker-label { font-size: calc(28rpx * var(--font-scale, 1)); color: $app-text; }
+.member-picker-item--active .member-picker-label { color: $brand-primary-deep; font-weight: 700; }
+.member-picker-check { width: 34rpx; height: 34rpx; flex-shrink: 0; }
 
-/* ---- 五角星区域 ---- */
+/* ============================================================
+五角星区域(C3:明确高度,避免 flex 下塌陷)
+============================================================ */
 .sandbox-star {
   position: relative;
   width: 100%;
+  height: 540rpx;      /* 兜底(父级 starHeight 内联覆盖) */
+  min-height: 400rpx;
+  flex-shrink: 0;
   display: flex;
   justify-content: center;
   align-items: center;
+  background: $dim-sandbox-fill;
+  border-radius: $radius-md;
 }
 
-/* 背景光晕 */
+/* 背景光晕(主色仅装饰) */
 .star-glow-bg {
   position: absolute; width: 500rpx; height: 500rpx;
   top: 50%; left: 50%; transform: translate(-50%, -50%);
-  background: radial-gradient(circle, rgba(249,115,22,0.10), transparent 70%);
+  background: radial-gradient(circle, rgba($brand-primary, 0.08), transparent 70%);
   border-radius: 50%; pointer-events: none;
 }
 
@@ -808,72 +901,76 @@ export default {
 }
 
 /* ============================================================
-顶点:图标 + 中文标签
+顶点:图标 + 中文标签(图标形状承载五行语义,深档色承载数据)
 ============================================================ */
 .star-point {
-  position: absolute; transform: translate(-50%, -50%); z-index: 3; cursor: pointer;
+  position: absolute; transform: translate(-50%, -50%); z-index: 3;
   display: flex; flex-direction: column; align-items: center;
+  min-width: 88rpx; min-height: 88rpx;   /* 触控 ≥44px */
+  padding: 8rpx 12rpx;
+  box-sizing: border-box;
 }
-/* 横排 */
-.star-point--row {
-  flex-direction: row;
-  gap: 6rpx;
-}
-.star-point--rev {
-  flex-direction: row-reverse;
-  gap: 6rpx;
-}
-.star-point--top { top: 1%; left: 50%; }
-.star-point--left { top: 38%; left: 5%; }
-.star-point--right { top: 38%; left: 95%; }
-.star-point--lr { top: 89%; left: 85%; }
-.star-point--ll { top: 89%; left: 15%; }
+.star-point--row { flex-direction: row; gap: 6rpx; }
+.star-point--rev { flex-direction: row-reverse; gap: 6rpx; }
+/* 顺时针:上=心 右=身 右下=智 左下=富 左=行 */
+.star-point--top { top: 6%; left: 50%; }
+.star-point--left { top: 40%; left: 5%; }
+.star-point--right { top: 40%; left: 95%; }
+.star-point--lr { top: 88%; left: 84%; }
+.star-point--ll { top: 88%; left: 16%; }
 
 .point-dot {
-  width: 52rpx; height: 52rpx; border-radius: 50%;
+  width: 60rpx; height: 60rpx; 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, $mind-accent, $mat-orange-strong); }
-.point-dot--water { background: linear-gradient(135deg, $mat-blue-400, $mat-blue-strong); }
-.point-dot--earth { background: linear-gradient(135deg, $brown-warm, #5D4037); }
-.point-dot--metal { background: linear-gradient(135deg, $accent-gold, #FF8F00); }
-.point-dot--wood { background: linear-gradient(135deg, $mat-green, $mat-green-strong); }
-
-.point-icon { font-size: calc(26rpx * var(--font-scale, 1)); line-height: 1; }
-
-.point-label {
-  font-size: calc(22rpx * var(--font-scale, 1)); 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;
+  flex-shrink: 0;
+  border: 1rpx solid transparent;
 }
+.point-dot--mind { background: $dim-mind-tint; border-color: rgba($dim-mind, 0.35); box-shadow: 0 0 12rpx rgba($dim-mind, 0.22); }
+.point-dot--body { background: $dim-body-tint; border-color: rgba($dim-body, 0.35); box-shadow: 0 0 12rpx rgba($dim-body, 0.22); }
+.point-dot--wisdom { background: $dim-wisdom-tint; border-color: rgba($dim-wisdom, 0.35); box-shadow: 0 0 12rpx rgba($dim-wisdom, 0.22); }
+.point-dot--wealth { background: $dim-wealth-tint; border-color: rgba($dim-wealth, 0.35); box-shadow: 0 0 12rpx rgba($dim-wealth, 0.22); }
+.point-dot--action { background: $dim-action-tint; border-color: rgba($dim-action, 0.35); box-shadow: 0 0 12rpx rgba($dim-action, 0.22); }
+
+.point-icon { width: 40rpx; height: 40rpx; }  /* 20px 下可辨识 */
+.point-label { font-size: calc(22rpx * var(--font-scale, 1)); font-weight: bold; color: $app-text; margin-top: 6rpx; letter-spacing: 2rpx; }
 .star-point--row .point-label,
-.star-point--rev .point-label {
-  margin-top: 0;
-}
-.point-energy {
-  font-size: calc(18rpx * var(--font-scale, 1)); color: rgba(143,197,232,0.8);
-  background: rgba(143,197,232,0.12);
-  padding: 1rpx 10rpx; border-radius: 16rpx;
-  margin-top: 2rpx;
-}
+.star-point--rev .point-label { margin-top: 0; }
+.point-energy { font-size: calc(18rpx * var(--font-scale, 1)); font-weight: 600; padding: 1rpx 10rpx; border-radius: 16rpx; margin-top: 2rpx; }
 .star-point--row .point-energy,
-.star-point--rev .point-energy {
-  margin-top: 0;
-}
+.star-point--rev .point-energy { margin-top: 0; }
+.point-energy--mind { color: $dim-data-mind; background: $dim-mind-tint; }
+.point-energy--body { color: $dim-data-body; background: $dim-body-tint; }
+.point-energy--wisdom { color: $dim-data-wisdom; background: $dim-wisdom-tint; }
+.point-energy--wealth { color: $dim-data-wealth; background: $dim-wealth-tint; }
+.point-energy--action { color: $dim-data-action; background: $dim-action-tint; }
+
+.star-center { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); z-index: 4; width: 180rpx; height: 180rpx; border-radius: 50%; }
 
 /* ============================================================
-中心点击区域
+preview 轻量引导条(标题 + 徽章 + 弱化五边形轮廓)
 ============================================================ */
-.star-center {
-  position: absolute; top: 50%; left: 50%;
-  transform: translate(-50%, -50%); z-index: 4;
-  width: 180rpx; height: 180rpx; border-radius: 50%;
-  cursor: pointer;
+.sandbox-preview {
+  display: flex;
+  align-items: center;
+  min-height: 88rpx;
+  padding: 16rpx 20rpx;
+  border-radius: $radius-md;
+  background: $brand-primary-soft;
+  border: 1rpx solid rgba($brand-primary, 0.15);
+}
+.preview-pentagon { width: 56rpx; height: 56rpx; flex-shrink: 0; opacity: 0.45; }
+.preview-copy { display: flex; flex-direction: column; margin-left: 16rpx; flex: 1; min-width: 0; }
+.preview-title { font-size: calc(26rpx * var(--font-scale, 1)); font-weight: bold; color: $app-text; }
+.preview-sub { font-size: calc(20rpx * var(--font-scale, 1)); color: $app-text-secondary; margin-top: 4rpx; }
+.preview-badge {
+  font-size: calc(20rpx * var(--font-scale, 1)); font-weight: 700;
+  color: $app-surface; background: $brand-primary-deep;
+  padding: 6rpx 14rpx; border-radius: 24rpx;
+  margin-left: 12rpx; flex-shrink: 0;
 }
 
 /* ============================================================
-维度详情条
+维度条(线性顺序:身 -> 智 -> 富 -> 行 -> 心)
 ============================================================ */
 .dimension-bar {
   display: flex; align-items: center; justify-content: center;
@@ -881,52 +978,23 @@ export default {
   position: relative; z-index: 5; flex-wrap: wrap;
 }
 .dim-item {
-  font-size: calc(20rpx * var(--font-scale, 1)); padding: 4rpx 12rpx; border-radius: 8rpx; white-space: nowrap;
-}
-.dim-item--wood { color: $mat-green; background: rgba(76,175,80,0.12); }
-.dim-item--fire { color: $mind-accent; background: rgba(255,107,53,0.12); }
-.dim-item--earth { color: $brown-warm; background: rgba(141,110,99,0.12); }
-.dim-item--metal { color: $accent-gold; background: rgba(255,215,0,0.12); }
-.dim-item--water { color: $mat-blue-400; background: rgba(66,165,245,0.12); }
-
-/* ============================================================
-个人/全家切换开关
-============================================================ */
-.sandbox-toggle {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  margin-bottom: 12rpx;
-  position: relative;
-  z-index: 5;
-  gap: 0;
-}
-.toggle-item {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 8rpx 28rpx;
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: rgba(143,197,232,0.6);
-  background: rgba(143,197,232,0.08);
-  border: 1rpx solid rgba(143,197,232,0.15);
-  border-radius: 24rpx 0 0 24rpx;
-  transition: color 0.2s, background-color 0.2s, border-color 0.2s;
-}
-.toggle-item--family {
-  border-radius: 0 24rpx 24rpx 0;
-  border-left: none;
-}
-.toggle-item--active {
-  color: $app-surface;
-  background: linear-gradient(135deg, $brand-primary, $brand-primary-dark);
-  border-color: $brand-primary;
-}
-.toggle-icon {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  margin-right: 8rpx;
-}
-.toggle-text {
-  font-weight: bold;
+  display: flex; align-items: center;
+  font-size: calc(20rpx * var(--font-scale, 1));
+  padding: 6rpx 12rpx; border-radius: 8rpx; white-space: nowrap;
+}
+.dim-item--body { color: $dim-data-body; background: $dim-body-tint; }
+.dim-item--wisdom { color: $dim-data-wisdom; background: $dim-wisdom-tint; }
+.dim-item--wealth { color: $dim-data-wealth; background: $dim-wealth-tint; }
+.dim-item--action { color: $dim-data-action; background: $dim-action-tint; }
+.dim-item--mind { color: $dim-data-mind; background: $dim-mind-tint; }
+.dim-item-icon { width: 24rpx; height: 24rpx; margin-right: 4rpx; flex-shrink: 0; }
+
+/* 降低动效偏好:CSS 侧关闭过渡 */
+@media (prefers-reduced-motion: reduce) {
+  .sandbox-switch,
+  .member-picker-item,
+  .dim-item {
+    transition-duration: 0.01ms !important;
+  }
 }
-</style>
+</style>

+ 12 - 0
cfc-frontend/uni.scss

@@ -483,6 +483,18 @@ $dim-wisdom-tint: #F0F0FF;
 $dim-action-tint: #F0FFF4;
 $dim-wealth-tint: #FFFBEB;
 
+// 能量沙盘数据区填充(B2:数据笔触 $dim-data-* 对 #FCE7CE 实测 ≥3:1)
+$dim-sandbox-fill: #FCE7CE;
+
+// 能量沙盘数据笔触深档(B2 · 主理人裁定值)
+// 说明:$dim-wisdom-deep 在本文件后段被二次声明为 #4F46E5(影响 6 个既有文件),
+//      为不改共享令牌、同时保证沙盘笔触为裁定值 #4338CA,此处单列沙盘专用深档。
+$dim-data-mind:   #9D174D;  // 心·玫红深档  对 #FCE7CE 6.56:1
+$dim-data-body:   #9C4A1A;  // 身·赭石深档  对 #FCE7CE 5.12:1
+$dim-data-wisdom: #4338CA;  // 智·靛蓝深档  对 #FCE7CE 6.57:1
+$dim-data-wealth: #92400E;  // 富·黄土深档  对 #FCE7CE 5.90:1
+$dim-data-action: #047857;  // 行·森林绿深档 对 #FCE7CE 4.56:1
+
 // 边框与文字的次级阶
 $app-border-strong: #D1D5DB;
 $app-text-subtle:   #94A3B8;