diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa9af26..834addc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ ## [Unreleased] +### 调整 +- 界面材质与 uhd_web 同步(截至 2026-10-08):内容卡、悬浮层、选中态、分段滑块、药丸改用同一套边光(左上亮、右下暗的细边加外侧暗线);主按钮的转动光谱环改为静止的左上光谱高光,悬停时沿边扫过一小段,减少动态效果时只变亮;次按钮与主按钮同为 lens 玻璃。 +- 输入框、多行输入、下拉选择统一由 `.field-glass` 给底、边、聚焦光晕和出错红线,去掉各处自带的边框和底色;对话框、确认框、抽屉的底更实,按钮行间距加大。 +- 提示条改成玻璃浮层,状态色只在图标上,多条时展开排列不叠放;原生 `title` 悬停提示换成同款玻璃气泡。 +- 控制台外壳:侧栏不画硬分隔线,折叠开关移到标志行;顶栏不铺磨砂,标题下用页面底色渐隐,通知、智能体、日夜切换和账户收进一枚玻璃胶囊。分组小标题保留。 +- 登录页改为整页居中一张卡,只留标题,去掉左侧宣传语。 +- 作品详情头图的「订阅」改回普通主按钮(静态高光在剧照上看得清),去掉实心按钮例外 `pill-solid`;发现页海报上的评分与状态角标改成烟色小玻璃。 +- 触屏判断改为通用方案(``、`touch:` 变体、`useTouchInput()`):车机等把触屏报告成鼠标的设备也按触屏处理,悬停效果只给真鼠标。 + ## [0.3.37] - 2026-10-07 ### 调整 diff --git a/apps/web/app/app.css b/apps/web/app/app.css index 64d80459..0e578197 100644 --- a/apps/web/app/app.css +++ b/apps/web/app/app.css @@ -5,6 +5,23 @@ /* ───────────────────────── Tailwind configuration ───────────────────────── */ @custom-variant dark (&:is(.dark *), &:is([data-force-dark] *)); +/* 悬停只给真鼠标:触屏(含被报告成鼠标的车机)点按后不留悬停态。覆盖 Tailwind 内置的 hover,group-hover / peer-hover 一并跟随 */ +@custom-variant hover { + @media (hover: hover) { + &:not(:where([data-input="touch"] *)):hover { + @slot; + } + } +} +/* 触屏:系统报告粗指针,或 (特斯拉车机等报告成鼠标的触屏,见 input-modality.ts) */ +@custom-variant touch { + @media (pointer: coarse) { + @slot; + } + &:where([data-input="touch"] *) { + @slot; + } +} @theme { --font-sans: @@ -38,7 +55,9 @@ --shadow-card-hover: 0 26px 54px -24px rgba(0, 0, 0, 1); --animate-fade-in: fade-in 520ms ease-out both; - --animate-pg-pop: pg-pop 200ms var(--ease-pop) both; + /* backwards 而不是 both:动画播完要撤掉。透明度动画一直挂着时,Chromium 把这个元素当成背景根, + 里面的玻璃 / 磨砂只能采样到它自己,菜单就不糊了(账户菜单、播放器菜单都踩过) */ + --animate-pg-pop: pg-pop 200ms var(--ease-pop) backwards; /* 播放器加载提示的滚动字幕:新一行从下方滚入,上一行向上滚出 */ --animate-ticker-in: ticker-in 360ms var(--ease-standard) both; --animate-ticker-out: ticker-out 360ms var(--ease-standard) both; @@ -79,7 +98,6 @@ --text-notfound-code: clamp(7rem, 18vw, 11rem); --text-pricing-hero: clamp(2rem, 4.4vw, 4rem); --text-pricing-title: clamp(1.6rem, 2.4vw, 2.2rem); - --text-auth-title: clamp(2rem, 3.4vw, 3.4rem); --text-landing-hero: clamp(2.1rem, 4.1vw, 3.9rem); --text-landing-platforms-title: clamp(1.8rem, 3.4vw, 2.8rem); --text-landing-immersive-title: clamp(2.25rem, 4.8vw, 5.4rem); @@ -116,6 +134,12 @@ padding: 0.75rem; } +/* 横滚标签条右端淡出:手机上分类标签滚到排序按钮跟前不再被硬切,也提示还能往右滑 */ +@utility scroll-fade-end { + -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); + mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent); +} + @utility duration-micro { --tw-duration: var(--duration-micro); transition-duration: var(--duration-micro); } @utility duration-base { --tw-duration: var(--duration-base); transition-duration: var(--duration-base); } @utility duration-layout { --tw-duration: var(--duration-layout); transition-duration: var(--duration-layout); } @@ -298,19 +322,21 @@ --mat-surface-highlight: rgba(255, 255, 255, 0.07); --mat-surface-solid: rgba(12, 13, 17, 0.92); /* pill:药丸 HUD */ - --mat-pill-bg: rgba(16, 17, 23, 0.68); + --mat-pill-bg: rgba(16, 17, 23, 0.58); --mat-pill-sheen: linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.04) 40%, rgba(255, 255, 255, 0) 70%); --mat-pill-blur: blur(16px) saturate(160%); - --mat-pill-border: rgba(255, 255, 255, 0.16); - --mat-pill-highlight: rgba(255, 255, 255, 0.18); - --mat-pill-shade: rgba(0, 0, 0, 0.25); + --mat-pill-border: rgba(255, 255, 255, 0.06); + --mat-pill-highlight: rgba(255, 255, 255, 0.34); + --mat-pill-edge: rgba(0, 0, 0, 0.4); + --mat-pill-shade: rgba(255, 255, 255, 0.1); --mat-pill-shadow: 0 2px 6px -2px rgba(0, 0, 0, 0.2), 0 12px 32px -14px rgba(0, 0, 0, 0.42); --mat-pill-solid: rgba(16, 17, 23, 0.88); /* slab:门户外层内容卡(浅色值;深色在 .dark 块) */ --mat-slab-bg: linear-gradient(155deg, rgba(255, 255, 255, 0.74) 0%, rgba(255, 255, 255, 0.58) 40%, rgba(255, 255, 255, 0.52) 100%); - --mat-slab-border: rgba(255, 255, 255, 0.7); + --mat-slab-border: rgba(255, 255, 255, 0.3); --mat-slab-blur: blur(20px) saturate(160%); - --mat-slab-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85), 0 0 0 1px rgba(20, 22, 28, 0.04), 0 1px 2px rgba(20, 22, 28, 0.05), 0 12px 28px -18px rgba(20, 22, 28, 0.16); + /* 内容卡的边与全站玻璃同一套:左上亮、右下弱的 1px 边光 + 外侧 1px 暗线;面本身不变 */ + --mat-slab-shadow: inset 1px 1px 0 -0.25px rgba(255, 255, 255, 0.95), inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.45), 0 0 0 1px rgba(20, 22, 28, 0.07), 0 1px 2px rgba(20, 22, 28, 0.05), 0 12px 28px -18px rgba(20, 22, 28, 0.16); /* 满高工作面板(文件管理的目录树 + 主面板)的实底与粘性表头:两块共用,背后的光斑不再让一块比另一块亮 */ --mat-slab-base: color-mix(in oklab, var(--background) 35%, transparent); --mat-slab-head: color-mix(in oklab, var(--background) 25%, white); @@ -326,15 +352,19 @@ --mat-chrome-blur: blur(24px) saturate(160%); --mat-chrome-border: color-mix(in oklch, var(--primary) 11%, transparent); --mat-chrome-shadow: 0 8px 24px -20px rgba(20, 22, 28, 0.25); - --mat-chrome-side-shadow: 8px 0 24px -20px rgba(20, 22, 28, 0.25); + /* 竖直侧栏的边:内侧细亮边 + 外侧细暗线 + 很淡的投影,与玻璃同一套,不画硬分隔线 */ + --mat-chrome-side-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.9), 1px 0 0 rgba(20, 22, 28, 0.06), 10px 0 28px -22px rgba(20, 22, 28, 0.2); --mat-chrome-solid: rgba(255, 255, 255, 0.95); /* float:门户小型悬浮面板。SEARCH-4(2026-09-30)起不透明实底 = popover token,不做背景模糊: 半透明弹层压在资源列表上文字看不清(用户反馈) */ --mat-float-bg: var(--popover); --mat-float-blur: none; - --mat-float-border: rgba(20, 22, 28, 0.1); - --mat-float-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 0 0 1px rgba(20, 22, 28, 0.05), 0 2px 6px -2px rgba(20, 22, 28, 0.08), 0 20px 40px -20px rgba(20, 22, 28, 0.2); + --mat-float-border: rgba(255, 255, 255, 0.3); + /* 边与全站玻璃同一套(uhd_web 2026-10-07):左上亮、右下弱的 1px 边光 + 外侧 1px 暗线。底仍是 SEARCH-4 的实底 */ + --mat-float-shadow: inset 1px 1px 0 -0.25px rgba(255, 255, 255, 0.95), inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.5), 0 0 0 1px rgba(20, 22, 28, 0.08), 0 2px 6px -2px rgba(20, 22, 28, 0.08), 0 20px 40px -20px rgba(20, 22, 28, 0.2); --mat-float-solid: rgb(251, 251, 252); + /* 对话框 / 抽屉的底:uhd_web 的菜单是通透玻璃、对话框才换实底;本项目菜单本来就是实底(SEARCH-4),两者同值,留着这一层供换皮 */ + --mat-float-modal-bg: var(--popover); /* 胶囊按钮:主操作是有填充的薄玻璃,次操作只描边。日夜对称——夜间白色薄玻璃配白字, 日间墨色薄玻璃配深字;不做实心黑 / 实心白 */ --mat-pill-primary-bg: linear-gradient(180deg, rgba(20, 22, 28, 0.02), rgba(20, 22, 28, 0.05)); @@ -344,44 +374,43 @@ /* 主按钮的棱镜环(方案 A):亮度、彩度、环与内光的强度 */ --prism-l: 0.66; --prism-c: 0.15; - --prism-ring: 0.9; - --prism-glow: 0.16; + --prism-ring: 1; + --prism-glow: 0.14; /* 选中态(分页当前页、角标、选中的选项):静态半透明 + 稍深描边,不转动、不实心 */ - --mat-pill-selected-bg: rgba(20, 22, 28, 0.07); - --mat-pill-selected-border: rgba(20, 22, 28, 0.28); + --mat-pill-selected-bg: rgba(20, 22, 28, 0.08); + --mat-pill-selected-border: rgba(20, 22, 28, 0.22); --mat-pill-secondary-bg: rgba(20, 22, 28, 0.03); --mat-pill-secondary-bg-hover: rgba(20, 22, 28, 0.07); --mat-pill-secondary-border: rgba(20, 22, 28, 0.12); - /* pill-solid:详情页头图区的唯一主操作(订阅),主色实心;只用在 MediaHero 的操作行 */ - --mat-pill-solid-bg: var(--primary); - --mat-pill-solid-bg-hover: color-mix(in oklab, var(--primary) 84%, var(--background)); - --mat-pill-solid-fg: var(--primary-foreground); - --mat-pill-solid-shadow: 0 1px 2px rgba(20, 22, 28, 0.14), 0 6px 16px -8px rgba(20, 22, 28, 0.4); + /* lens:液态玻璃小控件(次按钮、图标键;主按钮只用 edge / sheen)。meniscus 的 lens 档: + 本体几乎透明,靠左上亮右下暗的 1px 边光(内阴影)和外侧 1px 暗线成形 */ + --mat-lens-fill: rgba(255, 255, 255, 0.08); + --mat-lens-fill-hover: rgba(255, 255, 255, 0.3); + --mat-lens-rim: inset 1px 1px 0 -0.25px rgba(255, 255, 255, 0.95), inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.55), inset 0 0 0 1px rgba(255, 255, 255, 0.25); + --mat-lens-edge: 0 0 0 1px rgba(20, 22, 28, 0.1); + --mat-lens-sheen: inset 1px 1px 0 -0.5px rgba(255, 255, 255, 0.7); + --mat-lens-shadow: 0 6px 16px -10px rgba(20, 22, 28, 0.28); + --mat-lens-blur: blur(2px) saturate(160%); + /* field:输入框 / 下拉选择的触发器,与按钮同一套边光;底比按钮实一点,保证字和占位符好读 */ + /* 输入框是填充型:靠比卡片略深的底色区分,边线按 WCAG 非文本对比 3:1 给(压在白卡片上) */ + --mat-field-bg: rgba(20, 22, 28, 0.045); + --mat-field-edge: rgba(20, 22, 28, 0.46); + --mat-field-edge-focus: rgba(20, 22, 28, 0.78); + /* 登录页的表单卡片:实在的底,先把卡片和页面分开 */ + --mat-auth-card-bg: rgba(255, 255, 255, 0.84); + --mat-field-ring: rgba(20, 22, 28, 0.06); + --mat-field-highlight: rgba(255, 255, 255, 0.08); + /* 小玻璃圆片的烟色玻璃:白色图标坐在上面。日间烟墨、夜间淡白;压在图片上的恒用 media 档 */ + --mat-chip-glass: rgba(20, 22, 28, 0.56); /* media-hero:详情页头图。剧照铺在内容区顶部、渐隐到页面底色;左侧文字区压近乎不透明的底色,右侧露出剧照。 窄屏海报与文字上下排,改为自上而下压底色 */ --mat-hero-image-opacity: 1; --mat-hero-scrim-side: linear-gradient(90deg, var(--background) 0%, color-mix(in oklab, var(--background) 96%, transparent) 38%, color-mix(in oklab, var(--background) 82%, transparent) 55%, color-mix(in oklab, var(--background) 40%, transparent) 72%, transparent 92%); --mat-hero-scrim-stack: linear-gradient(180deg, color-mix(in oklab, var(--background) 30%, transparent) 0%, color-mix(in oklab, var(--background) 72%, transparent) 45%, color-mix(in oklab, var(--background) 92%, transparent) 75%, var(--background) 100%); /* 分段控件的滑块(侧栏当前项、SegmentedTabs):日间是托起的白片,夜间是薄白玻璃 */ - --mat-seg-thumb-bg: rgba(255, 255, 255, 0.8); - --mat-seg-thumb-border: rgba(20, 22, 28, 0.06); - --mat-seg-thumb-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95), 0 3px 12px -3px rgba(20, 22, 28, 0.18); - /* notice:更新提示胶囊 */ - --mat-notice-fg: var(--foreground); - --mat-notice-bg: rgba(255, 255, 255, 0.78); - --mat-notice-blur: blur(24px) saturate(160%); - --mat-notice-border: rgba(255, 255, 255, 0.85); - --mat-notice-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.95), 0 0 0 1px color-mix(in oklch, var(--primary) 12%, transparent), 0 14px 36px -14px rgba(20, 22, 28, 0.35), 0 3px 10px -4px rgba(20, 22, 28, 0.12); - --mat-notice-sub-fg: var(--muted-foreground); - --mat-notice-icon-fg: var(--primary); - --mat-notice-icon-bg: color-mix(in oklch, var(--primary) 12%, transparent); - --mat-notice-ring: 0 0 0 0 color-mix(in oklch, var(--primary) 45%, transparent); - --mat-notice-cta-fg: var(--primary-foreground); - --mat-notice-cta-bg: var(--primary); - --mat-notice-cta-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 6px 16px -8px color-mix(in oklch, var(--primary) 70%, transparent); - --mat-notice-close-fg: var(--muted-foreground); - --mat-notice-close-hover-fg: var(--foreground); - --mat-notice-close-hover-bg: rgba(0, 0, 0, 0.06); + --mat-seg-thumb-bg: rgba(255, 255, 255, 0.62); + --mat-seg-thumb-border: transparent; + --mat-seg-thumb-shadow: inset 1px 1px 0 -0.25px rgba(255, 255, 255, 1), inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.6), 0 0 0 1px rgba(20, 22, 28, 0.08), 0 3px 12px -3px rgba(20, 22, 28, 0.18); /* Scrollbars */ --scrollbar-thumb: color-mix( @@ -470,8 +499,8 @@ /* 材质参数:深色门户 */ --mat-slab-bg: linear-gradient(155deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.04) 30%, rgba(255, 255, 255, 0.03) 75%, rgba(255, 255, 255, 0.04) 100%); - --mat-slab-border: rgba(255, 255, 255, 0.1); - --mat-slab-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.12), inset 0 -1px 0 rgba(255, 255, 255, 0.03), 0 1px 2px rgba(0, 0, 0, 0.2), 0 12px 28px -18px rgba(0, 0, 0, 0.4); + --mat-slab-border: rgba(255, 255, 255, 0.04); + --mat-slab-shadow: inset 1px 1px 0 -0.25px rgba(255, 255, 255, 0.22), inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2), 0 12px 28px -18px rgba(0, 0, 0, 0.4); --mat-slab-base: color-mix(in oklab, var(--background) 78%, transparent); --mat-slab-head: color-mix(in oklab, var(--background) 94%, white); --mat-slab-reflection: linear-gradient(105deg, transparent 0%, rgba(255, 255, 255, 0.006) 36%, rgba(255, 255, 255, 0.013) 44%, rgba(255, 255, 255, 0.003) 51%, transparent 62%); @@ -483,12 +512,13 @@ --mat-chrome-bg: color-mix(in oklch, var(--card-solid) 48%, transparent); --mat-chrome-border: color-mix(in oklch, var(--primary) 13%, transparent); --mat-chrome-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06); - --mat-chrome-side-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.04); + --mat-chrome-side-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08), 1px 0 0 rgba(0, 0, 0, 0.45), 10px 0 28px -22px rgba(0, 0, 0, 0.55); --mat-chrome-solid: rgba(12, 13, 17, 0.92); --mat-float-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0) 40%), var(--popover); --mat-float-solid: rgb(22, 23, 27); + --mat-float-modal-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0) 40%), var(--popover); --mat-float-blur: none; - --mat-float-border: rgba(255, 255, 255, 0.14); + --mat-float-border: rgba(255, 255, 255, 0.06); /* 胶囊按钮:夜间主操作是白色玻璃 */ --mat-pill-primary-bg: rgba(255, 255, 255, 0.05); --mat-pill-primary-bg-hover: rgba(255, 255, 255, 0.09); @@ -496,28 +526,33 @@ --mat-pill-primary-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14), 0 1px 2px rgba(0, 0, 0, 0.2); --prism-l: 0.82; --prism-c: 0.13; - --prism-ring: 0.85; - --prism-glow: 0.22; - --mat-pill-selected-bg: rgba(255, 255, 255, 0.1); - --mat-pill-selected-border: rgba(255, 255, 255, 0.32); + --prism-ring: 1; + --prism-glow: 0.2; + --mat-pill-selected-bg: rgba(255, 255, 255, 0.12); + --mat-pill-selected-border: rgba(0, 0, 0, 0.45); --mat-pill-secondary-bg: rgba(255, 255, 255, 0.04); --mat-pill-secondary-bg-hover: rgba(255, 255, 255, 0.1); --mat-pill-secondary-border: rgba(255, 255, 255, 0.15); - --mat-pill-solid-bg: var(--primary); - --mat-pill-solid-bg-hover: color-mix(in oklab, var(--primary) 84%, var(--background)); - --mat-pill-solid-fg: var(--primary-foreground); - --mat-pill-solid-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 6px 16px -8px rgba(0, 0, 0, 0.6); + --mat-lens-fill: rgba(0, 0, 0, 0.14); + --mat-lens-fill-hover: rgba(255, 255, 255, 0.08); + --mat-lens-rim: inset 1px 1px 0 -0.25px rgba(255, 255, 255, 0.42), inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.16), inset 0 0 0 1px rgba(255, 255, 255, 0.06); + --mat-lens-edge: 0 0 0 1px rgba(0, 0, 0, 0.35); + --mat-lens-sheen: inset 1px 1px 0 -0.5px rgba(255, 255, 255, 0.28); + --mat-lens-shadow: 0 6px 16px -10px rgba(0, 0, 0, 0.55); + /* 夜间:底比卡片略亮,外线是浅色且对比 3:1(黑线压在深色卡片上看不见) */ + --mat-field-bg: rgba(255, 255, 255, 0.075); + --mat-field-edge: rgba(255, 255, 255, 0.36); + --mat-field-edge-focus: rgba(255, 255, 255, 0.78); + --mat-auth-card-bg: rgba(26, 27, 32, 0.74); + --mat-field-ring: rgba(255, 255, 255, 0.06); + --mat-chip-glass: rgba(255, 255, 255, 0.12); --mat-hero-image-opacity: 0.85; --mat-hero-scrim-side: linear-gradient(90deg, var(--background) 0%, color-mix(in oklab, var(--background) 94%, transparent) 38%, color-mix(in oklab, var(--background) 78%, transparent) 55%, color-mix(in oklab, var(--background) 36%, transparent) 72%, transparent 92%); --mat-hero-scrim-stack: linear-gradient(180deg, color-mix(in oklab, var(--background) 30%, transparent) 0%, color-mix(in oklab, var(--background) 72%, transparent) 45%, color-mix(in oklab, var(--background) 92%, transparent) 75%, var(--background) 100%); --mat-seg-thumb-bg: rgba(255, 255, 255, 0.08); - --mat-seg-thumb-border: rgba(255, 255, 255, 0.15); - --mat-seg-thumb-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18), 0 4px 18px -6px rgba(0, 0, 0, 0.4); - --mat-float-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), inset 0 -1px 0 rgba(255, 255, 255, 0.03), 0 2px 6px -2px rgba(0, 0, 0, 0.22), 0 18px 40px -20px rgba(0, 0, 0, 0.4); - --mat-notice-bg: rgba(255, 255, 255, 0.08); - --mat-notice-border: rgba(255, 255, 255, 0.14); - --mat-notice-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16), inset 0 -1px 0 rgba(0, 0, 0, 0.25), 0 16px 40px -16px rgba(0, 0, 0, 0.55); - --mat-notice-close-hover-bg: rgba(255, 255, 255, 0.08); + --mat-seg-thumb-border: transparent; + --mat-seg-thumb-shadow: inset 1px 1px 0 -0.25px rgba(255, 255, 255, 0.42), inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.14), 0 0 0 1px rgba(0, 0, 0, 0.35), 0 4px 18px -6px rgba(0, 0, 0, 0.4); + --mat-float-shadow: inset 1px 1px 0 -0.25px rgba(255, 255, 255, 0.32), inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.1), 0 0 0 1px rgba(0, 0, 0, 0.45), 0 2px 6px -2px rgba(0, 0, 0, 0.22), 0 18px 40px -20px rgba(0, 0, 0, 0.4); } /* ───────────────────────────── Base styles ───────────────────────────── */ @@ -566,14 +601,6 @@ -webkit-tap-highlight-color: transparent; } - /* 流媒体手机端有 3.5rem 的底部 Tab Bar:全局右下角浮层(更新提示)按这个 - 变量再垫高,免得压在 Tab Bar 上。播放页不渲染 Tab Bar,不设。 */ - @media (max-width: 767px) { - :root:has([data-stream-shell="page"]) { - --uhd-bottom-dock: 3.5rem; - } - } - a { @apply transition-colors; } @@ -801,69 +828,15 @@ backdrop-filter: var(--mat-pill-blur); -webkit-backdrop-filter: var(--mat-pill-blur); border: 1px solid var(--mat-pill-border); + /* 液态玻璃语言:左上亮、右下暗的边光 + 外侧 1px 暗线 */ box-shadow: - inset 0 1px 0 var(--mat-pill-highlight), - inset 0 -1px 0 var(--mat-pill-shade), + inset 1px 1px 0 -0.25px var(--mat-pill-highlight), + inset -1px -1px 0 -0.25px var(--mat-pill-shade), + 0 0 0 1px var(--mat-pill-edge), var(--mat-pill-shadow); } - - /* ─── 更新提示胶囊(app/components/update-notice.tsx)────────────── - 三处材质各归各:浅色门户 = 白磨砂玻璃 + 蓝调投影(pg-slab 浅色配方的 - 简化版);深色门户 = 白透玻璃(侧栏药丸同款);流媒体壳 = 同深色配方, - 动作钮换金色 —— 那边的强调色是金不是蓝。 */ - .update-pill { - color: var(--mat-notice-fg); - background: var(--mat-notice-bg); - backdrop-filter: var(--mat-notice-blur); - -webkit-backdrop-filter: var(--mat-notice-blur); - border: 1px solid var(--mat-notice-border); - box-shadow: var(--mat-notice-shadow); - } - - .update-pill__sub { - color: var(--mat-notice-sub-fg); - } - - .update-pill__icon { - color: var(--mat-notice-icon-fg); - background: var(--mat-notice-icon-bg); - } - - .update-pill__icon--stale { - color: var(--warning); - background: color-mix(in oklch, var(--warning) 14%, transparent); - } - - /* 一圈慢呼吸的光环,提醒但不催 */ - .update-pill__ring { - box-shadow: var(--mat-notice-ring); - animation: update-pill-ring 2.4s ease-out infinite; - } - - .update-pill__cta { - color: var(--mat-notice-cta-fg); - background: var(--mat-notice-cta-bg); - box-shadow: var(--mat-notice-cta-shadow); - } - - .update-pill__close { - color: var(--mat-notice-close-fg); - } - - .update-pill__close:hover { - color: var(--mat-notice-close-hover-fg); - background: var(--mat-notice-close-hover-bg); - } - - - @media (prefers-reduced-motion: reduce) { - .update-pill__ring { - animation: none; - } - } - /* 第四档:内容卡片,双主题(门户与 /speed 共用)。薄白/薄亮底 + 亮棱边 + 顶部内高光 + 斜反光,背景模糊由 --mat-slab-blur 给;/speed 的卡片贴着 高频重绘的实时区,在 .spd-bg 里把它关掉,零合成开销优先。 */ @@ -912,7 +885,7 @@ /* 2026-08-02 统一校准:① 暗色底从硬编码墨黑(rgba 12,13,17,流媒体 继承)改为 token 混色 —— 门户暗色是 271° 蓝紫调,chrome 必须同色相 否则读成另一种材质;② 边框掺 primary,与卡片"蓝棱线"同语言; - ③ 竖直侧栏用 .pg-chrome-side 把投影转向右侧(向下投影是横栏的光学)。 */ + ③ 竖直侧栏用 .pg-chrome-side:内侧细亮边 + 外侧细暗线 + 投影转向右侧,不画硬分隔线。 */ .pg-chrome { background: var(--mat-chrome-bg); backdrop-filter: var(--mat-chrome-blur); @@ -938,6 +911,11 @@ box-shadow: var(--mat-float-shadow); } + /* 对话框、确认框、抽屉:要专心阅读和填写,底比菜单实。压在半透明遮罩上时,通透的白底会变成脏灰 */ + .pg-float-modal { + --mat-float-bg: var(--mat-float-modal-bg); + } + /* 嵌在另一层磨砂里的菜单(页头、筛选栏、卡片内的下拉):外层磨砂是 backdrop 根, 里面的菜单糊不到页面,半透明底会透出没糊过的内容 —— 这类一律用近实底(动漫站同一做法)。 挂在 body 上的对话框、shadcn 弹出框不需要 */ @@ -1018,27 +996,6 @@ } } - /* 吸顶筛选条与顶栏之间的接缝:一条无彩细线,两端淡出,随日夜取 --wash */ - .chrome-seam-top { - position: relative; - } - - .chrome-seam-top::before { - content: ""; - position: absolute; - inset: 0 0 auto 0; - height: 1px; - background: linear-gradient( - 90deg, - transparent 0%, - color-mix(in oklch, var(--wash) 8%, transparent) 12%, - color-mix(in oklch, var(--wash) 12%, transparent) 50%, - color-mix(in oklch, var(--wash) 8%, transparent) 88%, - transparent 100% - ); - pointer-events: none; - } - /* Stream library layout */ /* 手机三列:两列时单张海报 190px,信息密度只有主流客户端的一半;三列 约 120px,一屏能看 9 张,标题字号在 MovieCard 里随之收到 15px */ @@ -1142,18 +1099,20 @@ 只管底、边、字色与投影,形状尺寸(rounded-full、内边距、字号)由调用处给。 日夜差别全在 --mat-pill-* 参数里;以后要换回实底或调透明度只改参数。 */ .pill-secondary { - background: var(--mat-pill-secondary-bg); - border: 1px solid var(--mat-pill-secondary-border); + /* 液态玻璃 lens:几乎透明的填充 + 内阴影画的方向性边光 + 外侧暗线 */ + background: var(--mat-lens-fill); + border: 1px solid transparent; + box-shadow: var(--mat-lens-rim), var(--mat-lens-edge), var(--mat-lens-shadow); color: var(--content); - -webkit-backdrop-filter: blur(20px) saturate(160%); - backdrop-filter: blur(20px) saturate(160%); + -webkit-backdrop-filter: var(--mat-lens-blur); + backdrop-filter: var(--mat-lens-blur); transition: background-color var(--duration-base) var(--ease-standard), transform 220ms var(--ease-spring); } .pill-secondary:hover { - background: var(--mat-pill-secondary-bg-hover); + background: var(--mat-lens-fill-hover); } /* 主操作(方案 A,2026-09-26):和其他按钮一样是半透明胶囊,不做实心;靠沿边框缓慢流转的 @@ -1164,12 +1123,13 @@ position: relative; isolation: isolate; overflow: visible; - background: var(--mat-pill-primary-bg); + background: var(--mat-lens-fill); border: 1px solid transparent; color: var(--mat-pill-primary-fg); - box-shadow: var(--mat-pill-primary-shadow); - -webkit-backdrop-filter: blur(20px) saturate(160%); - backdrop-filter: blur(20px) saturate(160%); + /* 液态玻璃:彩虹环就是它的边光,这里只补左上内高光和外侧暗线 */ + box-shadow: var(--mat-lens-sheen), var(--mat-lens-edge), var(--mat-lens-shadow); + -webkit-backdrop-filter: var(--mat-lens-blur); + backdrop-filter: var(--mat-lens-blur); transition: background-color var(--duration-base) var(--ease-standard), transform 220ms var(--ease-spring); @@ -1177,9 +1137,13 @@ .pill-primary:hover, .btn-glass.btn-glass-primary:hover { - background: var(--mat-pill-primary-bg-hover); + background: var(--mat-lens-fill-hover); } + /* 棱镜高光(2026-10-07 液态玻璃方案 2,替代整圈转动的光谱环):光从左上照来,在玻璃棱边上 + 分出一段光谱,右下是一段更淡的回光。静止不转,悬停时高光沿边扫过一小段。 + ::before = 棱边上的光谱弧;::after = 同一段光谱的柔光,透进玻璃本体、略洒出边外。 + 渐变直接写在伪元素上:--prism-angle 不继承,写进 :root 的变量会在根上就被算死 */ .pill-primary::before, .btn-glass.btn-glass-primary::before, .prism-ring::before { @@ -1187,18 +1151,23 @@ position: absolute; inset: -1px; border-radius: inherit; - padding: 1.5px; + padding: 1.75px; pointer-events: none; background: conic-gradient( - from var(--prism-angle), - oklch(var(--prism-l) var(--prism-c) 25), - oklch(var(--prism-l) var(--prism-c) 70), - oklch(calc(var(--prism-l) + 0.06) var(--prism-c) 105), - oklch(var(--prism-l) var(--prism-c) 155), - oklch(var(--prism-l) var(--prism-c) 205), - oklch(calc(var(--prism-l) - 0.04) var(--prism-c) 265), - oklch(calc(var(--prism-l) - 0.04) var(--prism-c) 305), - oklch(var(--prism-l) var(--prism-c) 25) + from calc(var(--prism-angle) + 225deg), + transparent 0deg, + oklch(var(--prism-l) var(--prism-c) 25) 22deg, + oklch(var(--prism-l) var(--prism-c) 70) 40deg, + oklch(calc(var(--prism-l) + 0.06) var(--prism-c) 105) 58deg, + oklch(var(--prism-l) var(--prism-c) 155) 76deg, + oklch(var(--prism-l) var(--prism-c) 205) 94deg, + oklch(calc(var(--prism-l) - 0.04) var(--prism-c) 265) 112deg, + oklch(calc(var(--prism-l) - 0.04) var(--prism-c) 305) 128deg, + transparent 152deg, + transparent 196deg, + oklch(var(--prism-l) var(--prism-c) 205 / 0.4) 226deg, + oklch(calc(var(--prism-l) - 0.04) var(--prism-c) 305 / 0.4) 254deg, + transparent 290deg ); -webkit-mask: linear-gradient(#fff 0 0) content-box, @@ -1209,8 +1178,9 @@ linear-gradient(#fff 0 0); mask-composite: exclude; opacity: var(--prism-ring); - transition: opacity var(--duration-base) var(--ease-standard); - animation: prism-spin 6s linear infinite; + transition: + --prism-angle 700ms var(--ease-pop), + opacity var(--duration-base) var(--ease-standard); } .pill-primary::after, @@ -1223,23 +1193,33 @@ border-radius: inherit; pointer-events: none; background: conic-gradient( - from var(--prism-angle), - oklch(0.8 0.14 25), - oklch(0.85 0.13 105), - oklch(0.8 0.13 205), - oklch(0.72 0.15 305), - oklch(0.8 0.14 25) + from calc(var(--prism-angle) + 225deg), + transparent 0deg, + oklch(var(--prism-l) var(--prism-c) 25) 22deg, + oklch(var(--prism-l) var(--prism-c) 70) 40deg, + oklch(calc(var(--prism-l) + 0.06) var(--prism-c) 105) 58deg, + oklch(var(--prism-l) var(--prism-c) 155) 76deg, + oklch(var(--prism-l) var(--prism-c) 205) 94deg, + oklch(calc(var(--prism-l) - 0.04) var(--prism-c) 265) 112deg, + oklch(calc(var(--prism-l) - 0.04) var(--prism-c) 305) 128deg, + transparent 152deg, + transparent 196deg, + oklch(var(--prism-l) var(--prism-c) 205 / 0.4) 226deg, + oklch(calc(var(--prism-l) - 0.04) var(--prism-c) 305 / 0.4) 254deg, + transparent 290deg ); - filter: blur(10px); + filter: blur(5px); opacity: var(--prism-glow); - transition: opacity var(--duration-base) var(--ease-standard); - animation: prism-spin 6s linear infinite; + transition: + --prism-angle 700ms var(--ease-pop), + opacity var(--duration-base) var(--ease-standard); } .pill-primary:hover::before, .btn-glass.btn-glass-primary:hover::before, .group:hover .prism-ring::before, .prism-ring:hover::before { + --prism-angle: 32deg; opacity: 1; } @@ -1247,14 +1227,16 @@ .btn-glass.btn-glass-primary:hover::after, .group:hover .prism-ring::after, .prism-ring:hover::after { - opacity: calc(var(--prism-glow) * 1.6); + --prism-angle: 32deg; + opacity: calc(var(--prism-glow) * 1.8); } .pill-primary:disabled::before, .pill-primary:disabled::after, .btn-glass.btn-glass-primary:disabled::before, .btn-glass.btn-glass-primary:disabled::after { - animation: none; + --prism-angle: 0deg; + filter: saturate(0.4); } @media (prefers-reduced-motion: reduce) { @@ -1264,7 +1246,7 @@ .btn-glass.btn-glass-primary::after, .prism-ring::before, .prism-ring::after { - animation: none; + transition: opacity var(--duration-base) var(--ease-standard); } } @@ -1324,26 +1306,12 @@ } /* 选中态:分页当前页、角标、选中的选项等"不是按钮但要标出来"的地方。静态、不实心 */ + /* 选中态 = 有填充的 lens 玻璃:比次按钮多一层填充,边由方向性边光 + 加深的外侧暗线给 */ .pill-selected { background: var(--mat-pill-selected-bg); - border: 1px solid var(--mat-pill-selected-border); - color: var(--content-strong); - } - - /* 实心主操作:只给详情页头图区的唯一主操作(订阅)用(2026-10-07 用户定的例外), - 其余页面主操作仍是 pill-primary;hover 只微调明度,不换色 */ - .pill-solid { - background: var(--mat-pill-solid-bg); border: 1px solid transparent; - color: var(--mat-pill-solid-fg); - box-shadow: var(--mat-pill-solid-shadow); - transition: - background-color var(--duration-base) var(--ease-standard), - transform 220ms var(--ease-spring); - } - - .pill-solid:hover { - background: var(--mat-pill-solid-bg-hover); + box-shadow: var(--mat-lens-rim), 0 0 0 1px var(--mat-pill-selected-border); + color: var(--content-strong); } /* 详情页头图(MediaHero):剧照层上下、右缘渐隐成透明,露出页面底色与光斑,不留硬边; @@ -1392,8 +1360,7 @@ /* 按下回弹,与 .btn-glass 同一手感 */ .pill-primary:active, - .pill-secondary:active, - .pill-solid:active { + .pill-secondary:active { transform: scale(0.97); } @@ -1432,11 +1399,12 @@ .btn-glass, .btn-glass-icon { - background: var(--mat-pill-secondary-bg); - border: 1px solid var(--mat-pill-secondary-border); + background: var(--mat-lens-fill); + border: 1px solid transparent; + box-shadow: var(--mat-lens-rim), var(--mat-lens-edge), var(--mat-lens-shadow); color: var(--content-strong); - -webkit-backdrop-filter: blur(20px) saturate(160%); - backdrop-filter: blur(20px) saturate(160%); + -webkit-backdrop-filter: var(--mat-lens-blur); + backdrop-filter: var(--mat-lens-blur); transition: background-color var(--duration-base) var(--ease-standard), transform 220ms var(--ease-spring); @@ -1444,7 +1412,109 @@ .btn-glass:hover, .btn-glass-icon:hover { - background: var(--mat-pill-secondary-bg-hover); + background: var(--mat-lens-fill-hover); + } + + /* LiquidSurface 只模糊不折射时(触屏、测速页、frost 面板):包在玻璃层顶部画的 50% 白光泽太亮, + 夜间会压住第一行字。普通档收到很淡,frost(字多的面板)去掉 */ + .liquid-surface-blur > :first-child { + background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.06) 0%, rgba(255, 255, 255, 0) 35%) !important; + } + + .liquid-surface-blur.liquid-surface-frost > :first-child { + background-image: none !important; + } + + /* 输入框 / 下拉选择触发器:淡底 + 1px 实边框,聚焦时边框加深并带一圈淡光晕; + 调用处只给尺寸与内边距,不再各写底色、边框、阴影(由 Input / SelectTrigger 挂上) */ + .field-glass { + background: var(--mat-field-bg); + /* 实打实的 1px 边框(不用阴影画线):阴影线太细,部分屏幕和浏览器上看不清输入框在哪 */ + border: 1px solid var(--mat-field-edge); + box-shadow: inset 0 1px 0 var(--mat-field-highlight); + transition: + box-shadow var(--duration-base) var(--ease-standard), + background-color var(--duration-base) var(--ease-standard); + } + + .field-glass:focus-visible, + .field-glass:focus-within, + .field-glass[data-state="open"] { + outline: none; + border-color: var(--mat-field-edge-focus); + box-shadow: 0 0 0 3px var(--mat-field-ring); + } + + .field-glass[aria-invalid="true"] { + border-color: color-mix(in oklch, var(--destructive) 65%, transparent); + } + + /* 登录类页面的表单卡片:内容卡的配方,但底换成实在的磨砂(夜间内容卡几乎透明,卡片、输入框、页面三层挤在一起分不开) */ + .auth-card { + --mat-slab-bg: var(--mat-auth-card-bg); + --mat-slab-blur: blur(24px) saturate(160%); + } + + /* 同 btn-glass 的 lens 玻璃外观,不带布局:尺寸会动画变化、不适合套 LiquidLens 折射的自定义控件 */ + .lens-glass { + background: var(--mat-lens-fill); + box-shadow: var(--mat-lens-rim), var(--mat-lens-edge), var(--mat-lens-shadow); + -webkit-backdrop-filter: var(--mat-lens-blur); + backdrop-filter: var(--mat-lens-blur); + } + + /* 小玻璃圆片 + 白色图标:glass-chip 跟随日夜(页面底上);glass-chip-media 恒为深烟色, + 用于一切压在图片上的小控件(海报 / 缩略图的角标、状态圆片、悬停出现的操作键) */ + .glass-chip, + .glass-chip-media { + display: grid; + place-items: center; + border-radius: 9999px; + color: #ffffff; + background: var(--mat-chip-glass); + -webkit-backdrop-filter: blur(6px) saturate(160%); + backdrop-filter: blur(6px) saturate(160%); + box-shadow: + inset 1px 1px 0 -0.25px rgba(255, 255, 255, 0.42), + inset -1px -1px 0 -0.25px rgba(255, 255, 255, 0.12), + 0 0 0 1px rgba(0, 0, 0, 0.22); + } + + .glass-chip-media { + background: rgba(0, 0, 0, 0.32); + } + + /* 套在 LiquidLens 里的按钮:玻璃由外层画(真折射),按钮自己不再铺底、不再模糊、不画边光 */ + .btn-glass.btn-liquid, + .btn-glass-icon.btn-liquid, + .pill-primary.btn-liquid, + .pill-secondary.btn-liquid { + background: transparent; + border-color: transparent; + box-shadow: none; + -webkit-backdrop-filter: none; + backdrop-filter: none; + } + + .btn-glass.btn-liquid:hover, + .btn-glass-icon.btn-liquid:hover, + .btn-glass.btn-glass-primary.btn-liquid, + .btn-glass.btn-glass-primary.btn-liquid:hover, + .pill-primary.btn-liquid, + .pill-primary.btn-liquid:hover, + .pill-secondary.btn-liquid:hover { + background: transparent; + box-shadow: none; + -webkit-backdrop-filter: none; + backdrop-filter: none; + } + + .btn-glass.btn-liquid:hover, + .btn-glass-icon.btn-liquid:hover, + .btn-glass.btn-glass-primary.btn-liquid:hover, + .pill-primary.btn-liquid:hover, + .pill-secondary.btn-liquid:hover { + background: rgba(255, 255, 255, 0.06); } .btn-glass:active, @@ -1692,20 +1762,3 @@ animation-duration: 420ms; animation-timing-function: var(--ease-standard); } - -@keyframes update-pill-ring { - 0% { - box-shadow: 0 0 0 0 color-mix(in oklch, var(--primary) 45%, transparent); - opacity: 1; - } - - 70% { - box-shadow: 0 0 0 10px color-mix(in oklch, var(--primary) 0%, transparent); - opacity: 0; - } - - 100% { - box-shadow: 0 0 0 0 color-mix(in oklch, var(--primary) 0%, transparent); - opacity: 0; - } -} diff --git a/apps/web/app/components/console/account-menu.tsx b/apps/web/app/components/console/account-menu.tsx index 5b8ac43c..2613e590 100644 --- a/apps/web/app/components/console/account-menu.tsx +++ b/apps/web/app/components/console/account-menu.tsx @@ -17,7 +17,7 @@ import { import { Input } from "~/components/ui/input.tsx"; import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover.tsx"; import { errorMessage, LOGIN_PATH } from "~/lib/api.ts"; -import { formatDateTime } from "~/lib/utils.ts"; +import { cn, formatDateTime } from "~/lib/utils.ts"; import { FormField, zodFieldErrors } from "./form-field.tsx"; const PasswordForm = PasswordChangeRequest.extend({ confirm: z.string() }).refine((v) => v.newPassword === v.confirm, { @@ -107,7 +107,8 @@ function ChangePasswordDialog({ open, onOpenChange }: { open: boolean; onOpenCha } /** 右上角账户弹层:当前身份、会话到期、改密、二次验证、登出 */ -export function AccountMenu({ session }: { session: SessionInfo }) { +/** bare:放在顶栏的玻璃胶囊里时触发器不自带玻璃 */ +export function AccountMenu({ session, bare = false }: { session: SessionInfo; bare?: boolean }) { const [open, setOpen] = useState(false); const [pwdOpen, setPwdOpen] = useState(false); const navigate = useNavigate(); @@ -128,7 +129,15 @@ export function AccountMenu({ session }: { session: SessionInfo }) { <> - diff --git a/apps/web/app/components/console/inbox-bell.tsx b/apps/web/app/components/console/inbox-bell.tsx index b144a66e..d6cc53d0 100644 --- a/apps/web/app/components/console/inbox-bell.tsx +++ b/apps/web/app/components/console/inbox-bell.tsx @@ -71,7 +71,8 @@ function Group({ ); } -export function InboxBell() { +/** bare:放在顶栏的玻璃胶囊里时触发器不自带玻璃 */ +export function InboxBell({ bare = false }: { bare?: boolean }) { const q = useAttention(); const [open, setOpen] = useState(false); const navigate = useNavigate(); @@ -83,7 +84,13 @@ export function InboxBell() { return ( -