/* ============================================================================ * v4 微拟物控件语言(skeuomorphic controls)—— 设计系统的灵魂 * * 物理模型:光从正上方来。每个可交互控件都是一枚「真实的键」—— * ① 凸面 竖向微渐变(顶亮→底沉),面是鼓起来的 * ② 顶部反光 inset 高光线(键面上缘接住光) * ③ 贴地投影 两层落影(近影锐、远影柔),键浮在纸面上 * ④ 反光光晕 外圈 0 0 0 Npx 高光环(键周围一圈反光),hover 时变亮变宽 * ⑤ 按压内陷 :active 渐变翻转 + inset 阴影——按下去真的「凹」 * * 铁律: * · 按钮一律【圆角矩形】(sm/md=8px · lg=12px),禁胶囊(999)——胶囊只留给非交互徽章 * · 光影只从本文件的质感类取(zzm-btn / zzm-card / zzm-input),禁止散写 boxShadow—— * 微拟物的一体感来自全站共用同一套光照模型,各写各的光源方向必然穿帮 * · backdrop-filter 仍然全域禁用(性能);渐变/阴影仅经本文件的类生效 * * 变体:默认纸键 / --primary 墨键 / --accent 朱键 / --ghost 幽灵(hover 才成键) / --ondark 暗面键 * 尺寸:--sm h28 / 默认 h36 / --lg h44;--icon 方形;Shadcn/Radix 确认窗亦在此复用纸面/朱砂/键感 * [PROTOCOL]: 变更时更新此头部,然后检查 CLAUDE.md * ========================================================================== */ /* ── 基键(纸面键)── */ .zzm-v4 .zzm-btn { -webkit-appearance: none; appearance: none; display: inline-flex; align-items: center; justify-content: center; gap: 8px; height: 36px; padding: 0 16px; font-family: inherit; font-size: 13px; font-weight: 600; letter-spacing: .2px; white-space: nowrap; cursor: pointer; user-select: none; border-radius: 8px; /* 圆角矩形——按钮禁胶囊 */ border: 1px solid rgba(0, 0, 0, .14); color: #3F3F3B; background: linear-gradient(180deg, #FFFFFF 0%, #F2F2EF 100%); box-shadow: 0 0 0 3px rgba(255, 255, 255, .55), /* ④ 反光光晕环 */ 0 1px 2px rgba(0, 0, 0, .09), /* ③ 近影 */ 0 2px 6px rgba(0, 0, 0, .06), /* ③ 远影 */ inset 0 1px 0 rgba(255, 255, 255, .92), /* ② 顶部反光 */ inset 0 -1px 0 rgba(0, 0, 0, .05); /* ① 底缘收沉 */ transition: box-shadow .16s ease, transform .16s ease, background .16s ease, border-color .16s ease, filter .16s ease, opacity .16s ease; } .zzm-v4 .zzm-btn:hover { transform: translateY(-1px); border-color: rgba(0, 0, 0, .18); box-shadow: 0 0 0 4px rgba(255, 255, 255, .8), 0 2px 4px rgba(0, 0, 0, .10), 0 4px 12px rgba(0, 0, 0, .09), inset 0 1px 0 rgba(255, 255, 255, .95), inset 0 -1px 0 rgba(0, 0, 0, .05); } .zzm-v4 .zzm-btn:active { transform: translateY(0); background: linear-gradient(180deg, #ECECE9 0%, #F4F4F1 100%); /* ⑤ 凹面翻转 */ box-shadow: 0 0 0 3px rgba(255, 255, 255, .45), 0 1px 0 rgba(255, 255, 255, .65), inset 0 2px 4px rgba(0, 0, 0, .13), inset 0 1px 2px rgba(0, 0, 0, .09); } .zzm-v4 .zzm-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px rgba(166, 64, 47, .20), /* 键盘焦点=朱色光晕 */ 0 1px 2px rgba(0, 0, 0, .09), 0 2px 6px rgba(0, 0, 0, .06), inset 0 1px 0 rgba(255, 255, 255, .92); } /* ── 禁用(非等待):灰面配灰字——任何变体禁用后都读得清。 不再用 opacity 淡化整键:变体白字 × 半透明 × 灰面 = 白字隐形事故(后台登录「验证中」曾不可读) ── */ .zzm-v4 .zzm-btn:disabled { cursor: not-allowed; transform: none; } .zzm-v4 .zzm-btn:disabled:not(.zzm-btn--busy) { color: #A2A29C; background: #F2F2EF; border-color: rgba(0, 0, 0, .08); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .7), 0 1px 2px rgba(0, 0, 0, .04); } .zzm-v4 .zzm-btn--ondark:disabled:not(.zzm-btn--busy) { color: rgba(255, 255, 255, .45); background: rgba(255, 255, 255, .08); border-color: rgba(255, 255, 255, .16); box-shadow: none; } /* ── Saving / 等待键(--busy)=设计系统原始按钮本体,只叠加三样,不改基座(v21.2.0 老大定版): ① 一道反光扫过——与档位胶囊同一条 zzmPillShine 关键帧(::after 覆盖整键面、快扫一次后长歇= 「时不时闪一下」,非呼吸光晕);② 不可点击——pointer-events:none(Btn 组件 disabled 亦已禁); ③ 饱和度略低——filter:saturate(.72)(比普通键偏低,朱键最明显、墨键近灰变化细微)。 变体色相/渐变/边框/五层光影一概不动(墨键仍墨、朱键仍朱);::before 旋转环保持不变。 overflow:hidden 只裁剪反光覆盖层,不裁外圈光影(box-shadow/focus 环不受 overflow 裁剪)。 ── */ .zzm-v4 .zzm-btn--busy { position: relative; overflow: hidden; pointer-events: none; opacity: .92; filter: saturate(.72); transform: none; } .zzm-v4 .zzm-btn--busy::before { content: ""; position: relative; z-index: 1; /* 旋转环压在反光层之上 */ width: 12px; height: 12px; flex: none; border-radius: 50%; border: 2px solid currentColor; border-top-color: transparent; opacity: .7; animation: zzmBtnSpin .7s linear infinite; } /* 反光层:斜向半透明白,从 -180% 扫到 180%(zzmPillShine),一道光从键面掠过 */ .zzm-v4 .zzm-btn--busy::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; transform: translateX(-180%); background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, .45) 50%, transparent 60%); animation: zzmPillShine 4.4s ease-in-out infinite; } /* ── 墨键(主操作·深墨)── */ .zzm-v4 .zzm-btn--primary { color: #FFFFFF; border-color: rgba(0, 0, 0, .85); background: linear-gradient(180deg, #3A3A36 0%, #1D1D1B 58%, #141412 100%); box-shadow: 0 0 0 3px rgba(22, 22, 22, .06), 0 1px 2px rgba(0, 0, 0, .18), 0 3px 10px rgba(22, 22, 22, .24), inset 0 1px 0 rgba(255, 255, 255, .20), inset 0 -1px 0 rgba(0, 0, 0, .38); } .zzm-v4 .zzm-btn--primary:hover { filter: brightness(1.12); border-color: rgba(0, 0, 0, .85); box-shadow: 0 0 0 4px rgba(22, 22, 22, .09), 0 2px 4px rgba(0, 0, 0, .20), 0 5px 14px rgba(22, 22, 22, .28), inset 0 1px 0 rgba(255, 255, 255, .24), inset 0 -1px 0 rgba(0, 0, 0, .38); } .zzm-v4 .zzm-btn--primary:active { filter: none; background: linear-gradient(180deg, #101010 0%, #22221F 100%); box-shadow: 0 0 0 3px rgba(22, 22, 22, .06), inset 0 2px 5px rgba(0, 0, 0, .55), inset 0 1px 2px rgba(0, 0, 0, .4); } /* ── 朱键(品牌强调·会员/播放/危险动作)── */ .zzm-v4 .zzm-btn--accent { color: #FFFFFF; border-color: rgba(122, 42, 29, .9); background: linear-gradient(180deg, #C25743 0%, #A6402F 56%, #99392A 100%); box-shadow: 0 0 0 3px rgba(166, 64, 47, .10), /* 朱色反光光晕 */ 0 1px 2px rgba(0, 0, 0, .14), 0 3px 10px rgba(166, 64, 47, .28), inset 0 1px 0 rgba(255, 255, 255, .32), inset 0 -1px 0 rgba(0, 0, 0, .20); } .zzm-v4 .zzm-btn--accent:hover { filter: brightness(1.06); border-color: rgba(122, 42, 29, .9); box-shadow: 0 0 0 4px rgba(166, 64, 47, .16), 0 2px 4px rgba(0, 0, 0, .16), 0 5px 14px rgba(166, 64, 47, .34), inset 0 1px 0 rgba(255, 255, 255, .36), inset 0 -1px 0 rgba(0, 0, 0, .20); } .zzm-v4 .zzm-btn--accent:active { filter: none; background: linear-gradient(180deg, #8E3425 0%, #A03D2C 100%); box-shadow: 0 0 0 3px rgba(166, 64, 47, .10), inset 0 2px 5px rgba(0, 0, 0, .34), inset 0 1px 2px rgba(0, 0, 0, .24); } /* ── 幽灵键(工具条/次级导航):静默是文字,hover 才「长成键」── */ .zzm-v4 .zzm-btn--ghost { border-color: transparent; background: transparent; box-shadow: none; color: #4A4A46; } .zzm-v4 .zzm-btn--ghost:hover { transform: translateY(-1px); border-color: rgba(0, 0, 0, .14); background: linear-gradient(180deg, #FFFFFF 0%, #F2F2EF 100%); box-shadow: 0 0 0 3px rgba(255, 255, 255, .55), 0 1px 2px rgba(0, 0, 0, .09), 0 2px 6px rgba(0, 0, 0, .06), inset 0 1px 0 rgba(255, 255, 255, .92); } .zzm-v4 .zzm-btn--ghost:active { transform: translateY(0); border-color: rgba(0, 0, 0, .14); background: linear-gradient(180deg, #ECECE9 0%, #F4F4F1 100%); box-shadow: inset 0 2px 4px rgba(0, 0, 0, .12); } /* ── 暗面键(深色面上的键:付款页/播放器锁罩/海报态)── */ .zzm-v4 .zzm-btn--ondark { color: #FFFFFF; border-color: rgba(255, 255, 255, .28); background: linear-gradient(180deg, rgba(255, 255, 255, .16) 0%, rgba(255, 255, 255, .06) 100%); box-shadow: 0 0 0 3px rgba(255, 255, 255, .05), 0 2px 8px rgba(0, 0, 0, .4), inset 0 1px 0 rgba(255, 255, 255, .28), inset 0 -1px 0 rgba(0, 0, 0, .3); } .zzm-v4 .zzm-btn--ondark:hover { border-color: rgba(255, 255, 255, .4); background: linear-gradient(180deg, rgba(255, 255, 255, .22) 0%, rgba(255, 255, 255, .10) 100%); box-shadow: 0 0 0 4px rgba(255, 255, 255, .08), 0 3px 10px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .34), inset 0 -1px 0 rgba(0, 0, 0, .3); } .zzm-v4 .zzm-btn--ondark:active { background: linear-gradient(180deg, rgba(0, 0, 0, .28) 0%, rgba(255, 255, 255, .04) 100%); box-shadow: inset 0 2px 5px rgba(0, 0, 0, .5); } /* ── 视频启动键:唯一允许为圆形的交互键 ─────────────────────────────── 圆形不是装饰例外,而是「点按即开始播放」这个媒体语义的通用符号;导学封面与 所有课时海报必须共用这一原件,避免同一动作在不同页面长成不同按钮。 其余文字按钮仍遵循 8/12 圆角矩形,禁止借此扩散成胶囊。 ── */ .zzm-v4 .zzm-video-play { display: grid; place-items: center; width: 72px; height: 72px; padding: 0; border: 1px solid rgba(0, 0, 0, .16); border-radius: 50%; cursor: pointer; color: #1D1D1B; background: linear-gradient(180deg, #FFFFFF 0%, #F2F2EF 100%); box-shadow: 0 0 0 4px rgba(255, 255, 255, .24), 0 2px 4px rgba(0, 0, 0, .16), 0 6px 16px rgba(0, 0, 0, .22), inset 0 1px 0 rgba(255, 255, 255, .96), inset 0 -1px 0 rgba(0, 0, 0, .08); transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease; } .zzm-v4 .zzm-video-play--center { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); } .zzm-v4 .zzm-video-play:hover { border-color: rgba(0, 0, 0, .3); box-shadow: 0 0 0 5px rgba(255, 255, 255, .3), 0 3px 6px rgba(0, 0, 0, .18), 0 9px 22px rgba(0, 0, 0, .28), inset 0 1px 0 rgba(255, 255, 255, .98), inset 0 -1px 0 rgba(0, 0, 0, .08); } .zzm-v4 .zzm-video-play--center:hover { transform: translate(-50%, -50%) scale(1.06); } .zzm-v4 .zzm-video-play:active { transform: scale(.98); background: linear-gradient(180deg, #E9E9E6 0%, #F4F4F1 100%); box-shadow: 0 0 0 4px rgba(255, 255, 255, .24), inset 0 2px 5px rgba(0, 0, 0, .16), inset 0 1px 2px rgba(0, 0, 0, .10); } .zzm-v4 .zzm-video-play--center:active { transform: translate(-50%, -50%) scale(.98); } .zzm-v4 .zzm-video-play:focus-visible { outline: none; box-shadow: 0 0 0 4px rgba(166, 64, 47, .28), 0 2px 4px rgba(0, 0, 0, .16), 0 6px 16px rgba(0, 0, 0, .22), inset 0 1px 0 rgba(255, 255, 255, .96); } .zzm-v4 .zzm-video-play__icon { width: 24px; height: 24px; margin-left: 4px; fill: currentColor; } /* ── 尺寸(高度落 4pt 网格:28/36/44)── */ .zzm-v4 .zzm-btn--sm { height: 28px; padding: 0 12px; font-size: 12px; gap: 4px; } .zzm-v4 .zzm-btn--lg { height: 44px; padding: 0 24px; font-size: 14px; border-radius: 12px; gap: 8px; } .zzm-v4 .zzm-btn--icon { width: 36px; padding: 0; } .zzm-v4 .zzm-btn--icon.zzm-btn--sm { width: 28px; } .zzm-v4 .zzm-btn--icon.zzm-btn--lg { width: 44px; } .zzm-v4 .zzm-btn--block { width: 100%; } /* ── 主题化确认窗(Shadcn/Radix Dialog)────────────────────────────────── 浏览器原生 confirm 没有产品的层级、语言和键感;所有后台确认统一经过 DialogProvider(appearance="v4")。Portal 仍挂在 .zzm-v4 内,故此处可复用 同一纸面、朱砂、凸键和内凹输入槽,而不是另造一套浮窗皮肤。 遮罩只降亮度,不模糊:背景仍可辨认当前操作上下文。 ── */ .zzm-v4 .zzm-dialog-overlay { background: rgba(22, 22, 22, .46); } .zzm-v4 .zzm-dialog-panel { width: min(calc(100% - 32px), 440px); max-width: 440px; gap: 16px; padding: 24px; border: 1px solid rgba(0, 0, 0, .14); border-radius: 16px; background: #FFFFFF; box-shadow: 0 1px 2px rgba(0, 0, 0, .10), 0 18px 48px rgba(0, 0, 0, .22), inset 0 1px 0 rgba(255, 255, 255, .92); } .zzm-v4 .zzm-dialog-title { color: #1D1D1B; font-size: 17px; font-weight: 700; line-height: 24px; } .zzm-v4 .zzm-dialog-description { color: #6E6E68; font-size: 13px; line-height: 20px; } .zzm-v4 input.zzm-dialog-input { width: 100%; } .zzm-v4 .zzm-dialog-actions { margin-top: 4px; gap: 8px; } /* ── 可点卡链(zzm-cardlink):给「裸缩略图+文字」的课节/历史/收藏卡穿上白卡承托, 悬停=高亮描边 + 上浮 + 柔影——与综合首页课程卡(zzm-hovercard)同一手感,全站卡片一个词汇。 内容内嵌圆角取 12(卡 16 内嵌 12,圆角梯嵌套律) ── */ .zzm-v4 .zzm-cardlink { background: #FFFFFF; border: 1px solid rgba(0, 0, 0, .09); border-radius: 16px; padding: 12px; cursor: pointer; box-shadow: 0 1px 2px rgba(0, 0, 0, .04); transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease; } .zzm-v4 .zzm-cardlink:hover { transform: translateY(-3px); border-color: rgba(0, 0, 0, .3); box-shadow: 0 10px 30px rgba(0, 0, 0, .09), 0 1px 2px rgba(0, 0, 0, .04); } /* ── 已完成课节卡(--done):容器级完成态——整卡纸面朱染 + 朱描边,一眼可辨, 取代右上角 18px ✓ 小圆徽(用户不必费力找小标识)。色值=朱色低透明派生: 底 .04(比目录当前行 .06 低一档,大面积 tint 更淡)/ 边 .35(zzm-vdrag hover 同系数)。 必须走类而非 inline borderColor——inline 会永久压过 :hover 的描边高亮(specificity) ── */ .zzm-v4 .zzm-cardlink--done { background: rgba(166, 64, 47, .04); border-color: rgba(166, 64, 47, .35); } .zzm-v4 .zzm-cardlink--done:hover { border-color: rgba(166, 64, 47, .55); } /* ── 微拟物卡片:白面浮在纸上(1px 细线仍在,光影让层级立起来)── */ .zzm-v4 .zzm-card { background: #FFFFFF; border: 1px solid rgba(0, 0, 0, .09); border-radius: 16px; box-shadow: 0 1px 2px rgba(0, 0, 0, .04), 0 6px 18px rgba(0, 0, 0, .05), inset 0 1px 0 rgba(255, 255, 255, .85); } /* ── 输入内凹(既有 .zzm-input 全站自动升级:键是凸的,输入槽是凹的)── */ .zzm-v4 input.zzm-input, .zzm-v4 textarea.zzm-input { background: #FDFDFC; box-shadow: inset 0 1.5px 3px rgba(0, 0, 0, .06), inset 0 1px 1px rgba(0, 0, 0, .04); } .zzm-v4 input.zzm-input:focus, .zzm-v4 textarea.zzm-input:focus { box-shadow: inset 0 1.5px 3px rgba(0, 0, 0, .06), 0 0 0 3px rgba(22, 22, 22, .07); /* 聚焦光晕 */ } .zzm-v4 input.zzm-input-accent:focus { box-shadow: inset 0 1.5px 3px rgba(0, 0, 0, .06), 0 0 0 3px rgba(166, 64, 47, .14); } /* ── 档位胶囊闪光(.zzm-pill-shine):一道反光时不时从名牌上扫过(与 saving 键 --busy 同一条 zzmPillShine 关键帧)。胶囊几何/配色由 JSX inline 出(TierPill 读 lib/courseTiers);本类只负责 裁剪 + 反光 ::after。错峰靠调用方经 CSS 变量 --zzm-shine-delay 逐卡设不同延时,避免齐刷。 ── */ .zzm-v4 .zzm-pill-shine { position: relative; overflow: hidden; } .zzm-v4 .zzm-pill-shine::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; transform: translateX(-180%); background: linear-gradient(105deg, transparent 34%, rgba(255, 255, 255, .85) 50%, transparent 66%); animation: zzmPillShine 4.6s ease-in-out infinite; animation-delay: var(--zzm-shine-delay, 0s); }