Files
ClassHelper/website/assets/styles.css
T
LoveChengke 17f55864bf feat(website): 下载页改成「客户端 / 服务端 / 插件」切换,卡片照 ClassIsland 官网那排重做
进来默认停在**客户端**(原先那颗主按钮指着服务端),服务端与插件各是一个可切换的分组。

- 新增「装什么」分段控件(客户端 / 服务端 / 插件):客户端一组一张卡,服务端一组两张
  (Windows + Linux),插件一组一张。它与版本选择器共用同一套指示器实现(beUI Tabs 的滑移;
  位置用 offsetLeft / offsetWidth 量,所以版本多了横滚之后也不会错位);
- 卡片改成 ClassIsland 官网那一页的样子:平台图标 + 名称 + 系统要求 + 主按钮 + 变体下拉。
  第一个文件是主按钮,其余进 `<details>` 下拉(点别处或 Esc 收起);插件那张只有一个文件,
  就不摆一个点不开的箭头;
- 主按钮下面留一行 mono 文件名与体积 —— 自托管场景里"点下去拿到哪个文件"是要看得见的;
- 首屏那颗主按钮跟着「版本 + 分组」走:默认是这一版客户端的安装版,切分组就跟着换;
- Linux 那个企鹅重画了一版:窄头宽肚 + 白脸 + 橙嘴橙脚(卡片底色恒为纸白,可以用实色)。
  原先那版头身糊成一团,44px 下认不出来;
- 卡片整排**左对齐**(跟着这一节的标题走):参考图那一页整体居中,但本站在每一带都是
  左对齐的节奏,孤零零一张卡居中会跟标题对不上。窄屏下卡片单列、分裂按钮撑满卡片宽。

`<meta name="ch-version">` 那一行的版本号没有跟着工作区里的 1.2.0 一起提交:仓库里另外
13 个落点还是 1.1.2,单独把它改成 1.2.0 会让 `pnpm version:check` 在 HEAD 上直接失败。
等整批版本号一起提交时它会自然跟上(它是 `scripts/version.mjs` 的落点之一)。
2026-10-06 19:35:59 +08:00

2475 lines
54 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ============================================================================
ClassHelper · 官网样式
----------------------------------------------------------------------------
两套表面,对应教室里的两样东西:
黑板(.band-board) 教室前面那块板 —— 屏幕上的事都在这里发生
纸张(.band-paper) 老师手里的东西 —— 名单、课表、账目都在这里读
颜色只有两个强调色,各有各的活儿:
蓝(--blue / --blue-deep) 产品自己的强调色。表示"这是一条真的数据"
红(--red) 只用在"上课被打断"这一件事上
========================================================================= */
:root {
/* 黑板 */
--board: #16231e;
--board-deep: #0c1311;
--board-raise: #1d2e27;
--board-line: rgba(232, 238, 233, 0.13);
--board-line-soft: rgba(232, 238, 233, 0.07);
--chalk: #e8eee9;
--chalk-2: #97aca1;
--chalk-3: #6d8279;
/* 屏幕(黑板里那块亮起来的屏) */
--screen-line: rgba(232, 238, 233, 0.12);
/* 纸张 */
--paper: #f1f4f7;
--paper-raise: #ffffff;
--paper-rule: #d9e0e7;
--paper-rule-soft: #e9eef3;
--ink: #17231f;
--ink-2: #56655f;
--ink-3: #8a9893;
/* 强调色:
蓝 = 产品自己的强调色,表示"这是一条真的数据"(链接、进度、实时状态)
红 = 只用在"上课被打断"这一件事上,整页只出现一次 */
--blue: #2f8fe6;
--blue-deep: #0f6cbd;
--chalk-red: #f0a5a1;
/* 灵动岛用的产品原色(只在这一处出现,是复刻,不参与整站配色) */
--island-bg: #000000;
--island-border: rgba(255, 255, 255, 0.118);
--island-accent: #0a84ff;
--island-urgent: #ff453a;
--island-call: #ff9f0a;
/* 曲线 token —— 与 assets/motion.js 的 EASE_* 同值(beUI lib/ease.ts) */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
/* 字体:标题用楷体(课本、板书、作文本的那个声音),正文用产品自己那套 UI 字体 */
--display:
'LXGW WenKai Screen', 'LXGW WenKai', '霞鹜文楷', KaiTi, '楷体', STKaiti, 'Kaiti SC', 'AR PL UKai CN',
serif;
--ui:
'Segoe UI Variable Text', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'PingFang SC',
'Microsoft YaHei UI', 'Microsoft YaHei', 'Noto Sans SC', system-ui, sans-serif;
--mono: ui-monospace, 'Cascadia Mono', 'SF Mono', Consolas, 'Microsoft YaHei UI', monospace;
/* 首屏标题:比站内其它标题大一档(ClassIsland 官网也是这个量级)。
两个数都是量出来的:
上限 4rem —— 第一行 11 个汉字 × 1em ≈ 704px,得留在 .hero-intro 的列宽(46em)里;
下限 1.8rem —— 375px 屏上可用宽度只有 327px,再大会把「它知道现在是第几节课,」
折成「它知道现在是第几」+ 三个字的孤行。 */
--fs-display: clamp(1.8rem, 5.7vw, 4rem);
--fs-h2: clamp(1.7rem, 3.4vw, 2.5rem);
--fs-h3: clamp(1.05rem, 1.5vw, 1.2rem);
--fs-lead: clamp(1rem, 1.5vw, 1.12rem);
--fs-sm: 0.875rem;
--fs-xs: 0.8125rem;
--wrap: 1120px;
--gutter: 24px;
--radius: 4px;
}
/* ------------------------------------------------------------------ 基础 */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
scroll-padding-top: 72px;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
background: var(--board);
color: var(--chalk);
font-family: var(--ui);
font-size: 1rem;
line-height: 1.9;
font-feature-settings: 'tnum' 1;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
img {
max-width: 100%;
height: auto;
display: block;
}
a {
color: inherit;
}
code {
font-family: var(--mono);
font-size: 0.875em;
font-variant-numeric: tabular-nums;
}
p,
ul,
ol,
dl,
figure,
table {
margin: 0;
}
:focus-visible {
outline: 2px solid var(--blue);
outline-offset: 3px;
border-radius: 2px;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
.skip {
position: absolute;
left: -9999px;
top: 0;
z-index: 40;
background: var(--paper-raise);
color: var(--ink);
padding: 10px 16px;
}
.skip:focus {
left: 12px;
top: 12px;
}
.wrap {
width: 100%;
max-width: var(--wrap);
margin-inline: auto;
padding-inline: var(--gutter);
}
/* ------------------------------------------------------------------ 顶栏 */
.topbar {
position: sticky;
top: 0;
z-index: 30;
background: color-mix(in srgb, var(--board-deep) 88%, transparent);
backdrop-filter: blur(14px);
border-bottom: 1px solid var(--board-line-soft);
}
.topbar-inner {
display: flex;
align-items: center;
gap: 20px;
min-height: 60px;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
flex-shrink: 0;
}
.brand-mark {
border-radius: 8px;
}
.brand-name {
font-family: var(--display);
font-size: 1.28rem;
letter-spacing: 0.02em;
}
.brand-ver {
font-size: var(--fs-xs);
color: var(--chalk-3);
font-variant-numeric: tabular-nums;
padding-left: 10px;
border-left: 1px solid var(--board-line);
line-height: 1;
}
.topnav {
display: flex;
gap: 22px;
margin-left: auto;
font-size: var(--fs-sm);
position: relative;
}
/* 悬停胶囊:beUI SharedLayoutBg —— 一个 layoutId 在条目之间滑移,
显隐走 blur(6px) 的淡入淡出,位置由 SPRING_LAYOUT 驱动(见 main.js) */
.nav-pill {
position: absolute;
top: 50%;
left: 0;
width: 0;
height: 34px;
margin-top: -17px;
border-radius: 10px;
background: var(--board-raise);
opacity: 0;
filter: blur(6px);
transition:
opacity 0.2s var(--ease-out),
filter 0.2s var(--ease-out);
pointer-events: none;
}
.nav-pill.is-visible {
opacity: 1;
filter: blur(0);
}
.topnav a {
position: relative;
color: var(--chalk-2);
text-decoration: none;
padding: 6px 0;
transition: color 0.16s var(--ease-out);
}
.topnav a:hover,
.topnav a:focus-visible {
color: var(--chalk);
}
/* 阅读进度 —— beUI ScrollProgressBar:2px、origin-left、scaleX 跟着滚动走,
值由 SPRING_PROGRESS 缓跟(故意比 UI 弹簧松,让它追着滚动而不是贴着) */
.scroll-progress {
position: absolute;
left: 0;
right: 0;
bottom: -1px;
height: 2px;
background: var(--blue);
transform: scaleX(0);
transform-origin: left center;
will-change: transform;
}
/* ------------------------------------------------------------------ 按钮 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: var(--fs-sm);
font-weight: 500;
line-height: 1;
padding: 13px 22px;
border-radius: var(--radius);
text-decoration: none;
border: 1px solid transparent;
transition:
background-color 0.16s var(--ease-out),
border-color 0.16s var(--ease-out),
color 0.16s var(--ease-out);
white-space: nowrap;
}
.btn-solid {
background: var(--blue-deep);
color: #fff;
}
.btn-solid:hover {
background: #1280dd;
}
.btn-line {
border-color: var(--board-line);
color: var(--chalk);
}
.btn-line:hover {
border-color: var(--chalk-3);
background: var(--board-raise);
}
/* .btn-line 的颜色是给黑板调的(粉笔白),落在浅色纸面上会看不见 —— 纸面另给一套 */
.band-paper .btn-line {
border-color: var(--paper-rule);
color: var(--ink);
}
.band-paper .btn-line:hover {
border-color: var(--blue);
background: var(--paper-raise);
}
/* 纸面上的按钮行跟在正文后面,自己带一段上边距(黑板那边靠 .lead 的下边距撑开) */
.band-paper .actions {
margin-top: 26px;
}
.btn-quiet {
margin-left: auto;
border-color: var(--board-line);
color: var(--chalk);
padding: 9px 16px;
}
.btn-quiet:hover {
background: var(--board-raise);
}
/* ------------------------------------------------------------------ 色带 */
.band {
padding-block: clamp(64px, 9vw, 116px);
border-bottom: 1px solid var(--board-line-soft);
}
.band-board {
background: var(--board);
color: var(--chalk);
}
.band-paper {
background: var(--paper);
color: var(--ink);
border-bottom-color: var(--paper-rule);
}
/* ── 分幕标题 ────────────────────────────────────────────────────────────────
照 ClassIsland 官网的分节写法:居中的一句大标题,中间一小段用渐变填色。
它是「这一幕讲什么」的路标,所以比下面的小节标题大一档、也更空。 */
.act {
background: var(--board);
padding-block: clamp(64px, 8vw, 104px);
border-bottom: 1px solid var(--board-line-soft);
}
.act-title {
font-family: var(--display);
font-weight: 400;
font-size: clamp(1.75rem, 3.6vw, 2.6rem);
line-height: 1.35;
letter-spacing: 0.02em;
text-align: center;
color: var(--chalk);
}
/* 渐变高亮词组。深色底上从粉笔白走到品牌蓝,读起来还是「同一块黑板上的字」 */
.act-hl {
background-image: linear-gradient(135deg, var(--chalk) 15%, var(--blue) 85%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
}
/* ── 收尾 CTA ───────────────────────────────────────────────────────────── */
.cta {
background: var(--board);
padding-block: clamp(72px, 9vw, 120px);
border-bottom: 0;
}
.cta-title {
font-family: var(--display);
font-weight: 400;
font-size: clamp(1.75rem, 3.6vw, 2.6rem);
line-height: 1.35;
text-align: center;
color: var(--chalk);
}
.cta-lead {
margin: 18px auto 0;
max-width: 34em;
text-align: center;
font-size: var(--fs-lead);
line-height: 1.95;
color: var(--chalk-2);
}
.cta-actions {
margin-top: 30px;
display: flex;
justify-content: center;
gap: 12px;
flex-wrap: wrap;
}
.band-paper code {
background: rgba(23, 35, 31, 0.06);
padding: 1px 5px;
border-radius: 3px;
}
/* 纸张上的分隔线换色 */
.band-paper .lead strong {
color: var(--ink);
}
/* ------------------------------------------------------------------ 排版 */
.display {
font-family: var(--display);
font-weight: 400;
font-size: var(--fs-display);
line-height: 1.22;
letter-spacing: 0.01em;
max-width: 20em;
margin-bottom: 26px;
}
/* 标题逐字上浮 —— beUI TextReveal。
脚本没跑起来时标题就是普通标题;跑起来了才按字拆开并藏起来等揭示。
`reveal-pending` 由一个 4 秒的兜底定时器摘掉,脚本坏在半路也不会把标题永久藏住。 */
.reveal-pending .display[data-reveal] {
visibility: hidden;
}
/* 每行一个块级容器:换行交给浏览器,窄屏上标题才会自己折行。
字本身是 inline-block(beUI 的做法),所以不会在字中间断开。 */
.reveal-line {
display: block;
}
.reveal-unit {
display: inline-block;
white-space: pre;
will-change: transform, opacity, filter;
}
.h2 {
font-family: var(--display);
font-weight: 400;
font-size: var(--fs-h2);
line-height: 1.4;
margin-bottom: 20px;
max-width: 22em;
}
.h3 {
font-family: var(--display);
font-weight: 400;
font-size: var(--fs-h3);
line-height: 1.5;
margin-bottom: 6px;
padding-bottom: 12px;
border-bottom: 1px solid var(--paper-rule);
}
.lead {
font-size: var(--fs-lead);
line-height: 1.95;
color: var(--chalk-2);
max-width: 34em;
}
.narrow {
max-width: 34em;
}
.band-paper .lead {
color: var(--ink-2);
}
.lead + .lead {
margin-top: 18px;
}
strong {
font-weight: 600;
color: var(--chalk);
}
.band-paper strong {
color: var(--ink);
}
/* ════════════════════════════════ 教室那块屏幕 ════════════════════════════ */
.hero {
padding-top: clamp(44px, 6vw, 76px);
}
.screen {
margin-top: clamp(40px, 5vw, 60px);
border: 1px solid var(--board-line);
border-radius: 10px;
/* 亮着的屏:比黑板亮一档的冷灰蓝,灵动岛那块纯黑才映得出来 */
background: linear-gradient(180deg, #22303f 0%, #161e28 100%);
overflow: hidden;
box-shadow: 0 24px 60px -30px rgba(0, 0, 0, 0.9);
}
.screen-bar {
display: flex;
align-items: center;
gap: 10px;
padding: 0 16px;
height: 38px;
border-bottom: 1px solid var(--screen-line);
font-size: var(--fs-xs);
color: var(--chalk-3);
}
.screen-dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #4ac07a;
box-shadow: 0 0 0 3px rgba(74, 192, 122, 0.16);
flex-shrink: 0;
}
.screen-title {
color: var(--chalk-2);
}
.screen-live {
margin-left: auto;
font-variant-numeric: tabular-nums;
}
.screen-stage {
position: relative;
padding: 0 var(--gutter) 30px;
--headroom: 300px;
}
.screen-body {
display: flex;
align-items: center;
gap: clamp(18px, 4vw, 52px);
flex-wrap: wrap;
padding-top: var(--headroom);
}
.clock-hm {
font-size: clamp(2.6rem, 6vw, 4rem);
font-weight: 600;
line-height: 1;
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
display: flex;
align-items: baseline;
gap: 6px;
}
.clock-s {
font-size: 0.32em;
font-weight: 500;
color: var(--chalk-3);
}
.clock-week {
margin-top: 10px;
font-size: var(--fs-sm);
color: var(--chalk-3);
font-variant-numeric: tabular-nums;
}
.clock-now {
flex: 1 1 300px;
min-width: 260px;
max-width: 460px;
border-left: 2px solid var(--blue-deep);
padding-left: 18px;
}
.clock-current {
font-size: 1.06rem;
}
.clock-current b {
font-weight: 600;
}
.clock-sub {
margin-top: 4px;
font-size: var(--fs-xs);
color: var(--chalk-3);
font-variant-numeric: tabular-nums;
}
.clock-bar {
margin-top: 12px;
height: 4px;
border-radius: 2px;
background: rgba(232, 238, 233, 0.12);
overflow: hidden;
}
.clock-bar i {
display: block;
height: 100%;
background: var(--blue);
border-radius: 2px;
}
.clock-next {
margin-left: auto;
text-align: right;
padding-left: 18px;
border-left: 1px solid var(--screen-line);
}
.next-head {
font-size: var(--fs-xs);
color: var(--chalk-3);
}
.next-name {
font-size: 1.06rem;
margin-top: 4px;
}
.next-name span {
font-size: var(--fs-xs);
color: var(--chalk-3);
font-variant-numeric: tabular-nums;
margin-left: 6px;
}
.next-sub {
margin-top: 4px;
font-size: var(--fs-xs);
color: var(--chalk-3);
}
.hero-cap {
margin-top: 16px;
font-size: var(--fs-xs);
line-height: 1.85;
color: var(--chalk-3);
max-width: 40em;
}
/* 首屏文字收成左列(ClassIsland 的 .introduction-col 也是这么窄的一条)。
46em ≈ 736px:这一列要能装下放大的「它知道现在是第几节课,」(11 个汉字 × 4rem ),
同时右边还要留得下三句事实。两边的数是一起量的,改一个就得回头看另一个。 */
.hero-intro {
max-width: 46em;
}
/* 整行渐变填色 —— ClassIsland 官网的 .gradient-base 那个写法。
这一行**不拆字**(main.js 的 setupHeadlineReveal 会跳过它):拆成 per-char 的
inline-block 之后每个字各自建一层合成,祖先的 `background-clip: text` 就穿不过去,
渐变会整行失效。所以它按「整行」入场:透明度 + 轻微上浮,比逐字稍晚一点起。 */
.display .reveal-grad {
background-image: linear-gradient(135deg, var(--chalk) 6%, var(--blue) 78%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
opacity: 0;
transition: opacity 0.85s var(--ease-out) 0.24s;
}
html:not(.reveal-pending) .display .reveal-grad {
opacity: 1;
}
@media (prefers-reduced-motion: no-preference) {
.display .reveal-grad {
transform: translateY(14%);
transition:
opacity 0.85s var(--ease-out) 0.24s,
transform 0.85s var(--ease-out) 0.24s;
}
html:not(.reveal-pending) .display .reveal-grad {
transform: none;
}
}
/* 宽屏上把三句事实挪到文字右边,填住那条空 —— ClassIsland 的首屏也是左文右块。
屏幕照旧整幅铺在下面:灵动岛是「屏幕顶部居中」的,不能只给它半幅。 */
@media (min-width: 1080px) {
.hero .wrap {
display: grid;
grid-template-columns: minmax(0, 46em) 1fr;
grid-template-areas:
'intro facts'
'screen screen'
'cap cap';
align-items: start;
gap: 34px clamp(36px, 5vw, 72px);
}
.hero-intro {
grid-area: intro;
}
.facts {
grid-area: facts;
margin-top: 10px;
grid-template-columns: 1fr;
gap: 18px;
padding-top: 24px;
}
.screen {
grid-area: screen;
}
.hero-cap {
grid-area: cap;
}
}
.hero-sub {
font-family: var(--display);
font-size: clamp(1.4rem, 2.9vw, 2.1rem);
line-height: 1.45;
color: var(--chalk);
margin-bottom: 16px;
}
.hero-intro .lead {
max-width: 32em;
margin-bottom: 28px;
}
/* 首屏按钮比站内其它按钮大一档 —— 它是这一页的主行动点 */
.hero-intro .actions .btn {
padding: 15px 26px;
font-size: 0.95rem;
}
/* 平台 / 支持情况那一行小标签(对应 ClassIsland 首屏底部那句「支持在 Windows…」)。
做成 mono 的芯片而不是一句话:这行是要被扫一眼的,不是要被读的。 */
.hero-badges {
list-style: none;
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 22px;
}
.hero-badges li {
font-family: var(--mono);
font-size: var(--fs-xs);
line-height: 1.6;
padding: 4px 11px;
border: 1px solid var(--board-line);
border-radius: 999px;
color: var(--chalk-2);
}
/* 第一个是版本号:当前这一版是什么,比平台支持更需要一眼看到 */
.hero-badges li:first-child {
border-color: var(--blue);
color: var(--chalk);
}
/* ── 首屏入场:照着 ClassIsland 的 .intro-seq 写(逐项 0.72s EASE_OUT + 错峰延迟)。
用 backwards 而不是 both:延迟期间先落在起始帧,动画结束后就回到底样式,
不在元素上留一个永久的 transform(那会多出一层包含块)。 */
@media (prefers-reduced-motion: no-preference) {
.intro-seq {
animation: intro-rise 0.72s var(--ease-out) backwards;
animation-delay: var(--intro-delay, 0s);
}
.screen {
animation: intro-fly 0.75s var(--ease-out) 60ms backwards;
}
}
@keyframes intro-rise {
from {
opacity: 0;
transform: translateX(-42px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes intro-fly {
from {
opacity: 0;
transform: translateX(96px) scale(0.86);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
.actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
flex-shrink: 0;
}
.facts {
list-style: none;
padding: 28px 0 0;
margin-top: 44px;
border-top: 1px solid var(--board-line);
display: grid;
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
gap: 20px 34px;
}
.facts li {
font-size: var(--fs-sm);
line-height: 1.85;
color: var(--chalk-2);
padding-left: 18px;
position: relative;
}
.facts li::before {
content: '';
position: absolute;
left: 0;
top: 0.72em;
width: 7px;
height: 1px;
background: var(--chalk-3);
}
/* ════════════════════════════════ 灵动岛 ═════════════════════════════════
下面这些数值整块来自产品本身(DEFAULT_ISLAND_APPEARANCE /
ISLAND_SIZE_DELTA / ISLAND_TYPE_SCALE):底色 #000、白字 alpha 分级
.92/.72/.58、强调色 #0a84ff、紧急 #ff453a、叫人 #ff9f0a。
====================================================================== */
.island-slot {
position: absolute;
top: 8px;
left: 50%;
transform: translateX(-50%);
z-index: 2;
pointer-events: none;
}
.island {
--island-font: 13px;
--state-color: var(--island-accent);
position: relative;
width: 268px;
height: 44px;
transform-origin: top center;
transition: opacity 0.3s var(--ease-out);
color: rgba(255, 255, 255, 0.92);
font-size: var(--island-font);
line-height: 1.5;
}
.island[data-kind='urgent'] {
--state-color: var(--island-urgent);
}
.island[data-kind='call'] {
--state-color: var(--island-call);
}
.island-shape {
position: absolute;
inset: 0;
overflow: visible;
display: block;
}
.island-base {
fill: var(--island-bg);
stroke: var(--island-border);
stroke-width: 1;
/* 投影常驻:给元素增删 CSS filter 会让 Chromium 重建渲染表面,开合瞬间底座闪一帧。
beUI 的岛也是一直挂阴影,不做展开态才有的那套。 */
filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.28));
}
/* 紧急 / 叫人:描边跟着状态色走,卡片顶部有一层同色光晕 */
.island[data-kind='urgent'] .island-base {
stroke: color-mix(in srgb, var(--island-urgent) 62%, rgba(255, 255, 255, 0.2));
}
.island[data-kind='call'] .island-base {
stroke: color-mix(in srgb, var(--island-call) 62%, rgba(255, 255, 255, 0.2));
}
.island-tint {
fill: none;
stroke: none;
}
.island[data-kind='urgent'] .island-tint {
fill: url(#island-tint-urgent);
}
.island[data-kind='call'] .island-tint {
fill: url(#island-tint-call);
}
.island-layers {
position: absolute;
inset: 0;
/* 壳在缩小时裁掉溢出 —— beUI 的 DynamicIsland 也是 overflow-hidden */
overflow: hidden;
border-radius: inherit;
}
/* 胶囊层与内容槽都铺满整个壳,靠 data-shape 切换谁在场 */
.island-pill,
.island-slot-content {
position: absolute;
inset: 0;
display: flex;
}
.island-pill {
align-items: center;
gap: calc(var(--island-font) * 0.62);
padding: 0 calc(var(--island-font) * 0.78);
transform-origin: top center;
/* 退场与 beUI 的 Slot 同参数:0.08s、ease-out、先走且不带模糊 */
transition:
opacity 0.08s var(--ease-out),
transform 0.08s var(--ease-out);
}
.island[data-shape='card'] .island-pill {
opacity: 0;
transform: translateY(-6px) scale(0.9);
pointer-events: none;
}
.pill-icon {
width: calc(var(--island-font) * 1.24);
height: calc(var(--island-font) * 1.24);
flex-shrink: 0;
color: var(--state-color);
}
.pill-icon svg {
width: 100%;
height: 100%;
display: block;
}
/* 语义化图标动效:铃铛从铰链(上边中点)上摆一下,0.28s */
.pill-icon[data-swing='true'] svg,
.card-icon-swing svg {
transform-origin: top center;
animation: bell-swing 0.28s var(--ease-out);
}
@keyframes bell-swing {
0% {
transform: rotate(0deg);
}
28% {
transform: rotate(12deg);
}
55% {
transform: rotate(-8deg);
}
78% {
transform: rotate(4deg);
}
100% {
transform: rotate(0deg);
}
}
.pill-title,
.pill-sub {
/* action-swap 的 blur 变体:0.2s、ease-in-out、scale 0.94 + 模糊 8px */
transition:
opacity 0.2s ease-in-out,
transform 0.2s ease-in-out,
filter 0.2s ease-in-out;
}
.pill-title {
font-size: calc(var(--island-font) * 0.92);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.pill-sub {
margin-left: auto;
font-size: calc(var(--island-font) * 0.74);
color: rgba(255, 255, 255, 0.58);
white-space: nowrap;
}
/* 先摆到起点、再在下一帧去掉这个类 —— 浏览器才会把过渡跑起来 */
.pill-title.is-swapping,
.pill-sub.is-swapping {
animation: swap-in 0.2s ease-in-out;
}
@keyframes swap-in {
from {
opacity: 0;
transform: scale(0.94);
filter: blur(8px);
}
to {
opacity: 1;
transform: scale(1);
filter: blur(0);
}
}
.island-slot-content {
flex-direction: column;
padding: calc(var(--island-font) * 1.4) calc(var(--island-font) * 1.6);
gap: calc(var(--island-font) * 0.7);
/* 内容从胶囊那条线向下展开,被收回时也往那里去 */
transform-origin: top center;
will-change: transform, opacity, filter;
}
.card-head {
display: flex;
align-items: center;
gap: calc(var(--island-font) * 0.55);
}
.card-icon {
width: calc(var(--island-font) * 1.62);
height: calc(var(--island-font) * 1.62);
border-radius: calc(var(--island-font) * 0.5);
display: grid;
place-items: center;
flex-shrink: 0;
background: color-mix(in srgb, var(--state-color) 20%, transparent);
color: var(--state-color);
}
.card-icon svg {
width: calc(var(--island-font) * 1.05);
height: calc(var(--island-font) * 1.05);
}
.card-badge {
height: calc(var(--island-font) * 1.25);
display: inline-flex;
align-items: center;
padding: 0 calc(var(--island-font) * 0.5);
border-radius: calc(var(--island-font) * 0.36);
font-size: calc(var(--island-font) * 0.68);
font-weight: 500;
background: color-mix(in srgb, var(--state-color) 22%, transparent);
color: var(--state-color);
white-space: nowrap;
}
.card-meta {
font-size: calc(var(--island-font) * 0.68);
color: rgba(255, 255, 255, 0.58);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.card-collapse {
margin-left: auto;
width: calc(var(--island-font) * 1.5);
height: calc(var(--island-font) * 1.5);
border-radius: 50%;
display: grid;
place-items: center;
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.72);
flex-shrink: 0;
}
.card-collapse svg {
width: calc(var(--island-font) * 0.76);
height: calc(var(--island-font) * 0.76);
}
.card-title {
font-size: calc(var(--island-font) * 1.08);
font-weight: 600;
line-height: 1.5;
}
.card-body {
font-size: calc(var(--island-font) * 0.95);
color: rgba(255, 255, 255, 0.72);
line-height: 1.62;
flex: 1;
min-height: 0;
overflow: hidden;
}
.card-foot {
display: flex;
align-items: center;
gap: calc(var(--island-font) * 0.5);
padding-top: calc(var(--island-font) * 0.9);
border-top: 1px solid rgba(255, 255, 255, 0.09);
}
.card-more {
font-size: calc(var(--island-font) * 0.76);
color: rgba(255, 255, 255, 0.58);
margin-right: auto;
}
.card-btn {
height: calc(var(--island-font) * 2.1);
display: inline-flex;
align-items: center;
padding: 0 calc(var(--island-font) * 0.86);
border-radius: calc(var(--island-font) * 0.5);
font-size: calc(var(--island-font) * 0.76);
background: rgba(255, 255, 255, 0.1);
color: rgba(255, 255, 255, 0.92);
white-space: nowrap;
}
.card-btn-primary {
background: color-mix(in srgb, var(--state-color) 78%, transparent);
color: #fff;
}
/* ════════════════════════════════ 链路 ═════════════════════════════════ */
.pipeline {
list-style: none;
padding: 0;
margin-top: 48px;
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 0;
}
.pipeline li {
position: relative;
padding: 26px 18px 0 0;
border-top: 2px solid var(--board-line);
}
.pipeline li + li {
padding-left: 20px;
}
/* 头上的圆点,表示这是一条真的有先后顺序的流程 */
.pipeline li::before {
content: '';
position: absolute;
top: -5px;
left: 0;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--board);
border: 2px solid var(--board-line);
}
.pipeline li:first-child::before {
border-color: var(--blue);
background: var(--blue);
}
.pipe-step {
font-family: var(--display);
font-size: 1.12rem;
margin-bottom: 8px;
}
.pipe-body {
font-size: var(--fs-sm);
line-height: 1.85;
color: var(--chalk-2);
}
.pipe-body code {
background: rgba(232, 238, 233, 0.08);
padding: 1px 5px;
border-radius: 3px;
}
/* ════════════════════════════════ 时间轴 ═══════════════════════════════ */
.policy {
margin-top: 44px;
border: 1px solid var(--board-line);
border-radius: 8px;
padding: clamp(20px, 3vw, 34px);
background: var(--board-deep);
}
.policy-clock {
display: flex;
align-items: baseline;
gap: 18px;
flex-wrap: wrap;
margin-bottom: 26px;
}
.policy-time {
font-size: clamp(2rem, 4.4vw, 3rem);
font-weight: 600;
line-height: 1;
letter-spacing: -0.02em;
font-variant-numeric: tabular-nums;
}
.policy-state {
font-size: var(--fs-sm);
color: var(--chalk);
font-weight: 500;
}
.day {
position: relative;
padding-bottom: 26px;
}
.day-track {
position: relative;
height: 40px;
border-radius: 4px;
background: rgba(232, 238, 233, 0.05);
overflow: hidden;
}
/* 已经过去的那一段(beUI RangeSlider 的进度填充:内缩、圆角、低对比) */
.day-fill {
position: absolute;
top: 2px;
bottom: 2px;
left: 2px;
width: 0;
border-radius: 3px;
background: rgba(232, 238, 233, 0.07);
pointer-events: none;
}
.day-block {
position: absolute;
top: 0;
bottom: 0;
background: rgba(232, 238, 233, 0.04);
border-left: 1px solid var(--board-deep);
border-right: 1px solid var(--board-deep);
}
/* 上课的节次填实、课间留白 —— 一眼看出这段在不在课上 */
.day-block.is-class {
background: rgba(232, 238, 233, 0.2);
}
.day-block-label {
position: absolute;
inset: 0;
display: grid;
place-items: center;
font-size: var(--fs-xs);
color: var(--chalk-3);
overflow: hidden;
white-space: nowrap;
}
.day-block.is-class .day-block-label {
color: rgba(232, 238, 233, 0.78);
}
/* 把手 —— beUI RangeSlider 的 thumb:一根竖圆条,拖动时纵向拉伸 1.35 倍。
位置由 SPRING_GLIDE 驱动(跟手、临界阻尼、不会在两端回弹),拉伸由 SPRING_BOUNCY 驱动。 */
.day-marker {
position: absolute;
top: 50%;
margin-top: -13px;
margin-left: -2px;
width: 4px;
height: 26px;
border-radius: 999px;
background: var(--chalk);
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.45);
transform: scaleY(var(--thumb-stretch, 1));
transform-origin: center;
pointer-events: none;
will-change: transform, left;
}
/* 每个节次的边界一个刻度点 */
.day-tick {
position: absolute;
bottom: 4px;
width: 3px;
height: 3px;
margin-left: -1.5px;
border-radius: 50%;
background: rgba(232, 238, 233, 0.34);
pointer-events: none;
}
.day-input {
position: absolute;
left: 0;
right: 0;
top: 0;
width: 100%;
height: 40px;
margin: 0;
opacity: 0;
cursor: ew-resize;
-webkit-appearance: none;
appearance: none;
background: transparent;
}
.day-input::-webkit-slider-thumb {
-webkit-appearance: none;
width: 28px;
height: 40px;
cursor: ew-resize;
}
.day-input::-moz-range-thumb {
width: 28px;
height: 40px;
border: 0;
cursor: ew-resize;
}
/* 拖动条本身是透明的(视觉全交给轨道),所以键盘焦点要画在轨道上,否则看不见 */
.day:focus-within .day-track {
outline: 2px solid var(--blue);
outline-offset: 3px;
}
.day-scale {
position: absolute;
left: 0;
right: 0;
bottom: 0;
display: flex;
justify-content: space-between;
font-size: var(--fs-xs);
color: var(--chalk-3);
font-variant-numeric: tabular-nums;
}
.outcomes {
margin-top: 30px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1px;
background: var(--board-line-soft);
border: 1px solid var(--board-line-soft);
border-radius: 6px;
overflow: hidden;
}
.outcome {
background: var(--board-deep);
padding: 18px;
}
.outcome-what {
font-size: var(--fs-xs);
color: var(--chalk-3);
}
.outcome-where {
font-family: var(--display);
font-size: 1.05rem;
margin: 4px 0 8px;
}
.outcome-say {
font-size: var(--fs-sm);
line-height: 1.8;
color: var(--chalk-2);
}
.outcome-alert .outcome-say {
color: var(--chalk-red);
}
.policy-note {
margin-top: 26px;
padding-top: 22px;
border-top: 1px solid var(--board-line-soft);
font-size: var(--fs-sm);
line-height: 1.9;
color: var(--chalk-2);
max-width: 62em;
}
/* ════════════════════════════════ 表格 ═════════════════════════════════ */
.table-scroll {
margin-top: 36px;
overflow-x: auto;
}
.sheet {
width: 100%;
min-width: 620px;
border-collapse: collapse;
font-size: var(--fs-sm);
background: var(--paper-raise);
border: 1px solid var(--paper-rule);
border-radius: 4px;
}
.sheet th,
.sheet td {
text-align: left;
padding: 15px 18px;
border-bottom: 1px solid var(--paper-rule-soft);
vertical-align: top;
line-height: 1.75;
}
.sheet thead th {
font-weight: 600;
color: var(--ink-2);
border-bottom: 1px solid var(--paper-rule);
white-space: nowrap;
}
.sheet tbody th {
font-weight: 600;
white-space: nowrap;
}
.sheet tbody tr:last-child th,
.sheet tbody tr:last-child td {
border-bottom: 0;
}
.sheet td:first-of-type {
color: var(--ink-2);
}
/* ════════════════════════════════ 实机截图 ═════════════════════════════ */
.shots {
margin-top: 40px;
}
/* 分段控件 —— beUI Tabs:一个 layoutId 指示器在选项之间滑移(位置由 SPRING_LAYOUT 驱动) */
.shots-tabs {
position: relative;
display: inline-flex;
gap: 4px;
padding: 4px;
border: 1px solid var(--board-line);
border-radius: 12px;
background: var(--board-deep);
max-width: 100%;
overflow-x: auto;
scrollbar-width: none;
}
.shots-tabs::-webkit-scrollbar {
display: none;
}
.shots-tab {
position: relative;
z-index: 1;
font: inherit;
font-size: var(--fs-sm);
color: var(--chalk-2);
background: transparent;
border: 0;
border-radius: 9px;
padding: 8px 16px;
cursor: pointer;
white-space: nowrap;
transition: color 0.18s var(--ease-out);
}
.shots-tab:hover {
color: var(--chalk);
}
.shots-tab[aria-selected='true'] {
color: var(--chalk);
}
.shots-indicator {
position: absolute;
top: 4px;
left: 0;
height: calc(100% - 8px);
width: 0;
border-radius: 9px;
background: var(--board-raise);
pointer-events: none;
}
.shots-grid {
margin-top: 26px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 22px;
}
.shots-grid:focus-visible {
outline: 2px solid var(--blue);
outline-offset: 6px;
}
.shot-card {
display: flex;
flex-direction: column;
gap: 10px;
font: inherit;
text-align: left;
color: inherit;
background: transparent;
border: 0;
padding: 0;
cursor: zoom-in;
transform-origin: center;
}
.shot-frame {
display: grid;
place-items: center;
aspect-ratio: 16 / 9;
padding: 0;
border: 1px solid var(--board-line);
border-radius: 8px;
background: #0d1512;
overflow: hidden;
transition: border-color 0.18s var(--ease-out);
}
.shot-card:hover .shot-frame {
border-color: var(--chalk-3);
}
.shot-frame img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top center;
display: block;
}
/* 灵动岛的图是贴着岛体裁下来的小图,原尺寸居中摆,不放大也不裁 */
.shot-card[data-kind='island'] .shot-frame {
aspect-ratio: 16 / 9;
padding: 16px;
}
.shot-card[data-kind='island'] .shot-frame img {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
/* 手机端是竖图,给个竖版的框 */
.shot-card[data-kind='phone'] .shot-frame {
aspect-ratio: 4 / 5;
padding: 12px;
}
.shot-card[data-kind='phone'] .shot-frame img {
width: auto;
height: 100%;
object-fit: contain;
border-radius: 6px;
}
.shot-cap {
font-size: var(--fs-xs);
line-height: 1.75;
color: var(--chalk-2);
}
.shots-hint {
margin-top: 22px;
font-size: var(--fs-xs);
color: var(--chalk-3);
}
/* ════════════════════════════ 大图浮层 ════════════════════════════
从被点的那张缩略图原地长到大图(beUI 的 morphing lightbox 是共享 layoutId,
这里用 FLIP 做等价的事:面板先摆到缩略图矩形,再用弹簧走到目标矩形)。 */
.lightbox {
position: fixed;
inset: 0;
z-index: 60;
}
.lightbox[hidden] {
display: none;
}
.lightbox-backdrop {
position: absolute;
inset: 0;
background: rgba(6, 10, 9, 0.88);
backdrop-filter: blur(6px);
opacity: 0;
}
.lightbox-panel {
position: fixed;
left: 0;
top: 0;
overflow: hidden;
border-radius: 8px;
background: #0d1512;
box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.8);
will-change: transform, width, height;
}
.lightbox-img {
width: 100%;
height: 100%;
display: block;
object-fit: contain;
}
.lightbox-cap {
position: fixed;
left: 50%;
bottom: 26px;
transform: translateX(-50%);
max-width: min(720px, 88vw);
text-align: center;
font-size: var(--fs-xs);
line-height: 1.7;
color: var(--chalk-2);
opacity: 0;
}
.lightbox-close {
position: fixed;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
display: grid;
place-items: center;
border-radius: 999px;
border: 1px solid var(--board-line);
background: rgba(232, 238, 233, 0.06);
color: var(--chalk);
cursor: pointer;
opacity: 0;
transition: background-color 0.18s var(--ease-out);
}
.lightbox-close:hover {
background: rgba(232, 238, 233, 0.14);
}
.lightbox-close svg {
width: 18px;
height: 18px;
}
/* ════════════════════════════════ 功能清单 ═════════════════════════════ */
.feature-groups {
margin-top: 40px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: clamp(32px, 5vw, 64px);
}
/* ── 特性块 ──────────────────────────────────────────────────────────────────
照 ClassIsland 官网的特性写法:#彩色标签 → 粗体短标题 → 一句说明。
「#」那行是它的招牌,先用蓝色承担(这一页的蓝只表示「这是产品里真有的东西」)。 */
.feature-blocks {
display: flex;
flex-direction: column;
gap: 26px;
margin-top: 4px;
}
.feature-tag {
font-size: var(--fs-xs);
font-weight: 600;
letter-spacing: 0.02em;
color: var(--blue-deep);
margin-bottom: 2px;
}
.feature-title {
font-size: clamp(1.1rem, 1.9vw, 1.4rem);
font-weight: 700;
line-height: 1.45;
color: var(--ink);
margin-bottom: 6px;
}
.feature-body {
font-size: var(--fs-sm);
line-height: 1.85;
color: var(--ink-2);
opacity: 0.85;
max-width: 34em;
}
/* ════════════════════════════════ 命令块 ═══════════════════════════════ */
.snippet {
margin-top: 40px;
border: 1px solid rgba(232, 238, 233, 0.12);
border-radius: 6px;
background: var(--board-deep);
overflow: hidden;
}
.snippet-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 10px 16px;
border-bottom: 1px solid rgba(232, 238, 233, 0.08);
}
.snippet-label {
font-size: var(--fs-xs);
color: var(--chalk-2);
}
.copy {
font: inherit;
font-size: var(--fs-xs);
color: var(--chalk-2);
background: transparent;
border: 1px solid var(--board-line);
border-radius: 3px;
padding: 5px 12px;
cursor: pointer;
overflow: hidden;
transition:
color 0.16s var(--ease-out),
border-color 0.16s var(--ease-out);
}
/* action-swap 的 roll 变体:文案整块从下方滚上来,容器裁掉溢出 */
.copy-label {
display: inline-block;
white-space: nowrap;
will-change: transform, opacity, filter;
}
.copy:hover {
color: var(--chalk);
border-color: var(--chalk-3);
}
.copy[data-done='1'] {
color: #4ac07a;
border-color: rgba(74, 192, 122, 0.4);
}
.snippet pre {
margin: 0;
padding: 18px 16px;
font-family: var(--mono);
font-size: 0.82rem;
line-height: 1.85;
color: var(--chalk);
tab-size: 2;
/* 折行而不是横向滚动:这条命令是要被抄走的,看全比保持一行更重要 */
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.snippet code {
font-size: inherit;
background: none;
padding: 0;
}
.band-paper .snippet {
border-color: #1d2e27;
}
/* ════════════════════════════════ 验收账目 ═════════════════════════════ */
.ledger {
margin-top: 44px;
border-top: 1px solid var(--board-line);
}
.ledger-row {
display: grid;
grid-template-columns: minmax(240px, 300px) 1fr auto;
align-items: baseline;
gap: 20px;
padding: 16px 0;
border-bottom: 1px solid var(--board-line-soft);
}
.ledger-cmd {
font-size: 0.85rem;
color: var(--chalk);
background: rgba(232, 238, 233, 0.06);
padding: 3px 8px;
border-radius: 3px;
justify-self: start;
white-space: nowrap;
}
.ledger-what {
font-size: var(--fs-sm);
line-height: 1.8;
color: var(--chalk-2);
}
.ledger-num {
font-size: 1.25rem;
font-weight: 600;
font-variant-numeric: tabular-nums;
color: var(--chalk);
}
.ledger-num::after {
content: ' 项';
font-size: 0.72rem;
font-weight: 400;
color: var(--chalk-3);
}
/* 数字滚动 —— beUI NumberTicker:每一位是一列 0–9 的竖排,靠 translateY 滚到目标位,
进场时逐位错开(0.04s),滚动本身 0.9s EASE_OUT,滚动期间带一点模糊。
静息位置 = 目标位,所以没轮到滚动时显示的也是正确的数字。 */
.ticker {
display: inline-flex;
align-items: center;
}
.ticker-digit {
position: relative;
display: inline-block;
width: 1ch;
height: 1.1em;
overflow: hidden;
}
.ticker-roll {
position: absolute;
left: 0;
right: 0;
top: 0;
display: flex;
flex-direction: column;
align-items: center;
filter: blur(0);
transform: translateY(var(--roll-to, 0em));
transition:
transform 0.9s var(--ease-out) var(--roll-delay, 0s),
filter 0.32s var(--ease-out) var(--roll-delay, 0s);
will-change: transform, filter;
}
.ticker-roll > span {
display: flex;
height: 1.1em;
align-items: center;
justify-content: center;
line-height: 1;
}
.ticker-digit.is-arming .ticker-roll {
transform: translateY(0);
filter: blur(10px);
transition: none;
}
.ledger-foot {
margin-top: 20px;
font-size: var(--fs-xs);
line-height: 1.85;
color: var(--chalk-3);
max-width: 44em;
}
/* ════════════════════════════════ 页脚 ═════════════════════════════════ */
.foot {
background: var(--board-deep);
padding-block: 46px;
}
.foot-inner {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 32px;
flex-wrap: wrap;
}
.foot-brand {
display: flex;
align-items: center;
gap: 14px;
}
.foot-brand img {
border-radius: 9px;
}
.foot-name {
font-family: var(--display);
font-size: 1.2rem;
}
.foot-name span {
font-size: 0.8rem;
color: var(--chalk-3);
margin-left: 6px;
}
.foot-ver {
font-size: var(--fs-xs);
color: var(--chalk-3);
margin-top: 2px;
}
.foot-nav {
display: flex;
gap: 26px;
flex-wrap: wrap;
font-size: var(--fs-sm);
}
.foot-nav a {
color: var(--chalk-2);
text-decoration: none;
border-bottom: 1px solid transparent;
padding-bottom: 2px;
}
.foot-nav a:hover {
color: var(--chalk);
border-bottom-color: var(--chalk-3);
}
/* ════════════════════════════════ 窄屏 ═════════════════════════════════ */
@media (max-width: 1000px) {
.pipeline {
grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
gap: 26px 0;
}
.pipeline li {
padding-right: 24px;
}
}
@media (max-width: 860px) {
.topnav {
display: none;
}
.btn-quiet {
margin-left: auto;
}
.hero-foot {
align-items: flex-start;
}
.ledger-row {
grid-template-columns: 1fr auto;
gap: 8px 16px;
}
.ledger-cmd {
grid-column: 1 / -1;
}
}
@media (max-width: 560px) {
:root {
--gutter: 18px;
}
/* 窄屏上让标题自己折行,硬换行会留下一个孤零零的字 */
.display br {
display: none;
}
.brand-ver {
display: none;
}
.screen-stage {
padding-bottom: 22px;
}
.screen-body {
gap: 18px;
}
.clock-now {
border-left-width: 2px;
padding-left: 14px;
}
.clock-next {
margin-left: 0;
text-align: left;
padding-left: 14px;
}
.actions .btn {
flex: 1 1 auto;
}
.policy {
padding: 18px 14px;
}
.outcomes {
grid-template-columns: 1fr;
}
}
/* ════════════════════════════════ 少动效 ═══════════════════════════════
beUI motion guide 的口径:少动效是一个设计过的状态,不是把动效全删掉 ——
保留透明度与颜色反馈(有信息量),去掉位移、缩放、模糊与回弹。 */
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-property: opacity, color, background-color, border-color !important;
transition-duration: 0.2s !important;
transition-delay: 0s !important;
}
}
/* ════════════════════════════════ 下载页 ═══════════════════════════════
download.html 专用。页面骨架与官网首页同一套(同样的色带、按钮、页脚),
这里只有三样这一页才有的东西:
.dl-seg 分段控件 —— 版本选择与「装什么」选择共用一套(beUI Tabs 的指示器滑移)
.dl-card 交付物卡片 —— 图标 / 名称 / 系统要求 / 主按钮 + 变体下拉(照 ClassIsland 官网那排)
.dl-menu 变体下拉
两条口径与全站一致:
蓝(--blue-deep) 产品自己的强调色 —— 这里表示「这是一份真的能下的文件」
卡片一律纸面(--paper-raise)——「谁装什么、下哪个文件」是要一项项核对的,跟名单一样 */
/* ── 分段控件 ────────────────────────────────────────────────────────────
一个指示器在选项之间滑移,位置由 SPRING_LAYOUT 驱动(见 assets/download.js)。
版本列表与「装什么」两处都用它 —— 所以这里是通用件,不含任何"版本"的语义。 */
.dl-pick {
margin: 34px 0 30px;
}
.dl-groups {
margin-top: 38px;
}
.dl-pick-label {
font-size: var(--fs-xs);
letter-spacing: 0.08em;
color: var(--chalk-3);
margin-bottom: 10px;
}
.band-paper .dl-pick-label {
color: var(--ink-3);
}
.dl-seg {
position: relative;
display: inline-flex;
gap: 4px;
padding: 4px;
border: 1px solid var(--board-line);
border-radius: 12px;
background: var(--board-deep);
max-width: 100%;
overflow-x: auto;
scrollbar-width: none;
}
.dl-seg::-webkit-scrollbar {
display: none;
}
.dl-seg-item {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
gap: 7px;
font: inherit;
font-size: var(--fs-sm);
font-variant-numeric: tabular-nums;
color: var(--chalk-2);
background: transparent;
border: 0;
border-radius: 9px;
padding: 8px 16px;
cursor: pointer;
white-space: nowrap;
transition: color 0.18s var(--ease-out);
}
.dl-seg-item:hover,
.dl-seg-item[aria-selected='true'] {
color: var(--chalk);
}
/* 旗标:最新版 / 预发布。整排版本里只有它有,是那一行唯一的信息栅格 */
.dl-seg-flag {
font-size: 0.68rem;
line-height: 1;
padding: 3px 6px;
border-radius: 999px;
color: #cfe6fb;
background: rgba(47, 143, 230, 0.18);
border: 1px solid rgba(47, 143, 230, 0.4);
}
.dl-seg-flag-quiet {
color: var(--chalk-2);
background: rgba(232, 238, 233, 0.07);
border-color: var(--board-line);
}
.dl-seg-indicator {
position: absolute;
top: 4px;
left: 0;
height: calc(100% - 8px);
width: 0;
border-radius: 9px;
background: var(--board-raise);
pointer-events: none;
}
/* 分段控件下面那行说明 —— 这一页的"数据来源声明"与"现在选中的是什么"都写在这里 */
.dl-pick-note {
margin-top: 12px;
font-size: var(--fs-xs);
line-height: 1.85;
color: var(--chalk-3);
max-width: 52em;
}
.dl-pick-note a {
color: var(--chalk-2);
}
.band-paper .dl-pick-note {
color: var(--ink-3);
}
.band-paper .dl-pick-note a {
color: var(--blue-deep);
}
/* ── 交付物卡片(纸面) ─────────────────────────────────────────────────── */
/* 按组显示:一屏最多两张卡(服务端是 Windows + Linux)。
用 flex 而不是 grid 的 auto-fit:auto-fit 在只有一张卡时会把空轨道折叠掉、
让这张卡撑满整行;这里要的是"卡片有自己的宽度"。
整排**左对齐**(跟着这一节的标题走)—— 参考图那一页整体居中,但本站在每一带
都是左对齐的节奏(`.lead` 也只占 34em),孤零零一张卡居中会跟标题对不上。 */
.dl-grid {
margin-top: 26px;
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
gap: 20px;
}
.dl-card {
flex: 1 1 360px;
min-width: 0;
max-width: 470px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
background: var(--paper-raise);
border: 1px solid var(--paper-rule);
border-radius: 6px;
padding: 30px 26px 26px;
}
/* 平台标记。单色描边,跟着卡片的文字色走 —— 换成彩色 logo 会在纸面上显得像贴纸 */
.dl-card-mark {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
color: var(--ink);
margin-bottom: 14px;
}
.dl-card-mark svg {
width: 44px;
height: 44px;
}
.dl-card-name {
font-size: 1.22rem;
font-weight: 700;
line-height: 1.4;
color: var(--ink);
}
/* 系统要求那行 —— 参考图里「Windows 10 及更高版本」站的就是这个位置 */
.dl-card-req {
margin-top: 5px;
font-size: var(--fs-xs);
line-height: 1.7;
color: var(--ink-3);
}
.dl-card-desc {
margin-top: 12px;
font-size: var(--fs-sm);
line-height: 1.85;
color: var(--ink-2);
max-width: 30em;
}
/* ── 主按钮 + 变体下拉(一个整体的分裂按钮) ───────────────────────────── */
.dl-cta {
position: relative;
display: flex;
margin-top: 20px;
border-radius: var(--radius);
overflow: visible;
}
.dl-cta-main,
.dl-more > summary {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: var(--fs-sm);
font-weight: 500;
line-height: 1;
padding: 14px 20px;
color: #fff;
background: var(--blue-deep);
text-decoration: none;
border: 1px solid var(--blue-deep);
cursor: pointer;
transition: background-color 0.16s var(--ease-out);
}
.dl-cta-main {
border-radius: var(--radius) 0 0 var(--radius);
border-right-width: 0;
padding-inline: 26px;
}
.dl-more > summary {
border-radius: 0 var(--radius) var(--radius) 0;
padding-inline: 12px;
border-left: 1px solid rgba(255, 255, 255, 0.28);
list-style: none;
}
.dl-more > summary::-webkit-details-marker {
display: none;
}
.dl-cta-main:hover,
.dl-more > summary:hover,
.dl-more[open] > summary {
background: #1280dd;
}
.dl-more > summary svg {
width: 16px;
height: 16px;
transition: transform 0.2s var(--ease-out);
}
.dl-more[open] > summary svg {
transform: rotate(180deg);
}
/* 没有变体时(插件那一张)不摆一个点不开的箭头 */
.dl-cta-single .dl-cta-main {
border-radius: var(--radius);
border-right-width: 1px;
}
.dl-menu {
position: absolute;
top: calc(100% + 8px);
right: 0;
z-index: 2;
min-width: 260px;
max-width: min(360px, 88vw);
margin: 0;
padding: 6px;
list-style: none;
text-align: left;
background: var(--paper-raise);
border: 1px solid var(--paper-rule);
border-radius: 6px;
box-shadow: 0 18px 40px -22px rgba(23, 35, 31, 0.5);
}
.dl-menu a {
display: block;
padding: 9px 11px;
border-radius: var(--radius);
text-decoration: none;
transition: background-color 0.16s var(--ease-out);
}
.dl-menu a:hover,
.dl-menu a:focus-visible {
background: var(--paper);
}
.dl-menu-top {
display: flex;
align-items: baseline;
gap: 10px;
}
.dl-menu-role {
font-size: var(--fs-sm);
font-weight: 600;
color: var(--ink);
}
.dl-menu-size {
margin-left: auto;
font-size: var(--fs-xs);
color: var(--ink-3);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.dl-menu-file {
display: block;
margin-top: 3px;
font-family: var(--mono);
font-size: 0.68rem;
line-height: 1.6;
color: var(--ink-2);
overflow-wrap: anywhere;
}
.dl-menu-note {
display: block;
margin-top: 3px;
font-size: var(--fs-xs);
color: var(--ink-3);
}
/* 主按钮下面那行:点下去到底拿到哪个文件(自托管场景里这个要看得见) */
.dl-card-file {
margin-top: 12px;
font-family: var(--mono);
font-size: 0.68rem;
line-height: 1.65;
color: var(--ink-3);
overflow-wrap: anywhere;
max-width: 100%;
}
/* 这一版没打这个包(老版本里真的会缺) */
.dl-missing {
margin-top: 18px;
padding: 12px 14px;
border: 1px dashed var(--paper-rule);
border-radius: var(--radius);
font-size: var(--fs-xs);
color: var(--ink-3);
}
.dl-note {
margin-top: 26px;
font-size: var(--fs-xs);
line-height: 1.9;
color: var(--ink-3);
max-width: 54em;
}
.dl-note code {
background: rgba(23, 35, 31, 0.06);
padding: 1px 5px;
border-radius: 3px;
}
@media (max-width: 560px) {
.dl-card {
padding: 22px 18px 20px;
}
.dl-cta {
width: 100%;
}
.dl-cta-main {
flex: 1 1 auto;
padding-inline: 16px;
}
.dl-menu {
right: auto;
left: 0;
min-width: 0;
width: 100%;
}
.dl-card-file {
font-size: 0.62rem;
}
}