diff --git a/docs/assets/og-card.png b/docs/assets/og-card.png new file mode 100644 index 0000000..88cf7ed Binary files /dev/null and b/docs/assets/og-card.png differ diff --git a/docs/assets/site.css b/docs/assets/site.css index 7c18ee5..6d62f92 100644 --- a/docs/assets/site.css +++ b/docs/assets/site.css @@ -1,8 +1,12 @@ /*! - * OSGKeyboard 主页设计系统(2026-09 重设计) + * OSGKeyboard 主页设计系统(2026-10 动效版) * 方向:Apple 式产品页 —— 深色产品舞台 + 实机画面优先 + 滚动叙事。 + * 动效母题:「声音变成文字」—— 声波、涟漪、逐字浮现、流式转写。 * 主题跟随系统(site.js 可手动覆盖);Hero / 隐私 / 下载区块在两种 * 主题下都使用固定的深色「舞台」调色板,与新应用图标同一语言。 + * + * 渐进增强约定:所有「先隐藏、再显现」的初始态都挂在 html.js 之下; + * 无 JavaScript 或开启「减弱动态效果」时,页面以完整静态形态呈现。 */ /* -------------------------------------------------------------------------- @@ -36,6 +40,30 @@ --radius-md: 14px; --shadow-card: 0 28px 56px -28px rgba(16, 20, 24, 0.25); --shadow-media: 0 32px 64px -32px rgba(16, 20, 24, 0.35); + + /* 动效令牌:统一的缓动曲线与时长,所有组件只引用这里 */ + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); + --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); + /* 物理弹簧(linear() 采样),用于需要「落定感」的元素:气泡、芯片、胶囊 */ + --ease-spring: linear( + 0, 0.009, 0.035 2.1%, 0.141, 0.281 6.7%, 0.723 12.9%, 0.938 16.7%, 1.017, + 1.077, 1.121, 1.149 24.3%, 1.159, 1.163, 1.161, 1.154 29.9%, 1.129 32.8%, + 1.051 39.6%, 1.017 43.1%, 0.991, 0.977 51%, 0.974 53.8%, 0.975 57.1%, + 0.997 69.8%, 1.003 76.9%, 1.004 83.8%, 1 + ); + --dur-fast: 0.22s; + --dur-base: 0.5s; + --dur-slow: 0.95s; + + /* 嵌入式手机演示的配色(随主题切换) */ + --ph-bg: #ffffff; + --ph-ink: #0f1214; + --ph-muted: #8a939c; + --ph-line: rgba(15, 18, 20, 0.1); + --ph-in: #e9ecef; + --ph-out: #2c8a52; + --ph-kb: #d9dde3; + --ph-key: #ffffff; } html[data-theme="dark"] { @@ -53,6 +81,15 @@ html[data-theme="dark"] { --accent-soft: rgba(104, 191, 141, 0.14); --shadow-card: 0 28px 56px -28px rgba(0, 0, 0, 0.6); --shadow-media: 0 32px 64px -32px rgba(0, 0, 0, 0.65); + + --ph-bg: #0b0c0e; + --ph-ink: #f2f4f5; + --ph-muted: #7c858d; + --ph-line: rgba(255, 255, 255, 0.1); + --ph-in: #23262b; + --ph-out: #2f9a5b; + --ph-kb: #1a1d21; + --ph-key: #34383e; } /* -------------------------------------------------------------------------- @@ -70,7 +107,9 @@ html { body { margin: 0; + /* clip 不会生成滚动容器,滚动叙事的 position: sticky 才能正常工作 */ overflow-x: hidden; + overflow-x: clip; background: var(--canvas); color: var(--ink); font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "PingFang SC", @@ -154,6 +193,19 @@ html[data-lang="zh"] h3 { transform: translateY(0); } +/* 仅供读屏:逐字拆分的标题由它提供完整句子 */ +.sr-only { + position: absolute; + overflow: hidden; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + border: 0; + clip: rect(0 0 0 0); + white-space: nowrap; +} + /* 内联 SVG 图标库容器:不可见但可被 引用 */ .svg-defs { position: absolute; @@ -175,6 +227,21 @@ html[data-lang="zh"] h3 { vertical-align: -0.28em; } +/* 页面滚动进度条:由 site.js 直接写 transform,避免每帧触发全局样式重算 */ +.scroll-progress { + position: fixed; + z-index: 60; + top: 0; + right: 0; + left: 0; + height: 2px; + background: linear-gradient(90deg, var(--stage-accent), #b5ecc9 60%, var(--stage-accent)); + box-shadow: 0 0 12px rgba(111, 196, 145, 0.6); + pointer-events: none; + transform: scaleX(0); + transform-origin: 0 50%; +} + /* -------------------------------------------------------------------------- * 3. 导航 * -------------------------------------------------------------------------- */ @@ -221,23 +288,52 @@ html[data-lang="zh"] h3 { width: 30px; height: 30px; border-radius: 8px; + transition: transform var(--dur-base) var(--ease-spring); +} + +.brand:hover img { + transform: rotate(-8deg) scale(1.06); } .nav-links { + position: relative; display: flex; align-items: center; gap: 4px; } .nav-links a { + position: relative; + z-index: 1; padding: 8px 14px; border-radius: 999px; font-size: 0.92rem; opacity: 0.72; - transition: opacity 0.2s ease, background 0.2s ease; + transition: opacity 0.2s ease; } -.nav-links a:hover { +.nav-links a:hover, +.nav-links a.is-active { + opacity: 1; +} + +/* 导航滑块:位置与宽度由 site.js 写入,弹簧曲线让它「跟手」 */ +.nav-indicator { + position: absolute; + top: 50%; + left: 0; + width: var(--iw, 0px); + height: 34px; + border-radius: 999px; + background: color-mix(in srgb, currentColor 11%, transparent); + opacity: 0; + pointer-events: none; + transform: translate3d(var(--ix, 0px), -50%, 0); + transition: transform 0.55s var(--ease-spring), width 0.55s var(--ease-spring), + opacity 0.3s ease; +} + +.nav-links.has-indicator .nav-indicator { opacity: 1; } @@ -260,7 +356,7 @@ html[data-lang="zh"] h3 { background: transparent; cursor: pointer; opacity: 0.78; - transition: opacity 0.2s ease, background 0.2s ease; + transition: opacity 0.2s ease, background 0.2s ease, transform 0.4s var(--ease-spring); } .language-button { @@ -271,12 +367,23 @@ html[data-lang="zh"] h3 { .icon-button:hover, .language-button:hover { + background: color-mix(in srgb, currentColor 9%, transparent); opacity: 1; } +.icon-button:active, +.language-button:active { + transform: scale(0.9); +} + .icon-button .i { width: 19px; height: 19px; + transition: transform 0.6s var(--ease-spring); +} + +#themeToggle:hover .i { + transform: rotate(-24deg); } .mobile-menu-button { @@ -296,8 +403,9 @@ html[data-lang="zh"] h3 { min-height: 100svh; flex-direction: column; align-items: center; - padding: 148px 0 0; + padding: 136px 0 0; overflow: hidden; + isolation: isolate; background: radial-gradient(120% 90% at 50% 110%, rgba(111, 196, 145, 0.14), transparent 55%), linear-gradient(180deg, var(--stage-1) 0%, var(--stage-2) 52%, var(--stage-3) 100%); @@ -305,28 +413,125 @@ html[data-lang="zh"] h3 { text-align: center; } -.hero-glow { +/* 背景层:极光 + 声波涟漪 + 点阵 + 指针光斑 */ +.hero-backdrop { position: absolute; - top: -320px; - left: 50%; - width: 900px; - height: 640px; - border-radius: 50%; - background: radial-gradient(closest-side, rgba(111, 196, 145, 0.16), transparent 70%); + z-index: -1; + inset: 0; + overflow: hidden; pointer-events: none; - transform: translateX(-50%); } +.aurora { + position: absolute; + border-radius: 50%; + filter: blur(70px); + mix-blend-mode: screen; + opacity: 0.6; + will-change: transform; +} + +.aurora-a { + top: -220px; + left: calc(50% - 640px); + width: 640px; + height: 640px; + background: radial-gradient(circle, rgba(111, 196, 145, 0.5), transparent 66%); + animation: aurora-a 24s ease-in-out infinite alternate; +} + +.aurora-b { + top: 40px; + right: calc(50% - 660px); + width: 560px; + height: 560px; + background: radial-gradient(circle, rgba(70, 150, 190, 0.42), transparent 66%); + animation: aurora-b 28s ease-in-out infinite alternate; +} + +.aurora-c { + bottom: -220px; + left: 50%; + width: 900px; + height: 460px; + margin-left: -450px; + background: radial-gradient(closest-side, rgba(111, 196, 145, 0.32), transparent); + animation: aurora-c 19s ease-in-out infinite alternate; +} + +@keyframes aurora-a { + 0% { transform: translate3d(0, 0, 0) scale(1); } + 50% { transform: translate3d(140px, 80px, 0) scale(1.18); } + 100% { transform: translate3d(60px, 160px, 0) scale(0.94); } +} + +@keyframes aurora-b { + 0% { transform: translate3d(0, 0, 0) scale(1.05); } + 50% { transform: translate3d(-120px, 60px, 0) scale(0.9); } + 100% { transform: translate3d(-40px, -60px, 0) scale(1.15); } +} + +@keyframes aurora-c { + 0% { transform: translate3d(-80px, 0, 0) scale(1); } + 100% { transform: translate3d(80px, -40px, 0) scale(1.12); } +} + +/* 声波涟漪:三圈错相扩散,像一次持续的「开口」 */ +.hero-rings i { + position: absolute; + top: 36%; + left: 50%; + width: 560px; + height: 560px; + margin: -280px 0 0 -280px; + border: 1px solid rgba(111, 196, 145, 0.28); + border-radius: 50%; + opacity: 0; + animation: ring-pulse 7.5s cubic-bezier(0.2, 0.6, 0.3, 1) infinite; +} + +.hero-rings i:nth-child(2) { animation-delay: 2.5s; } +.hero-rings i:nth-child(3) { animation-delay: 5s; } + +@keyframes ring-pulse { + 0% { opacity: 0; transform: scale(0.3); } + 14% { opacity: 0.9; } + 100% { opacity: 0; transform: scale(2); } +} + +.hero-grid { + position: absolute; + inset: 0; + background-image: radial-gradient(rgba(255, 255, 255, 0.09) 1px, transparent 1px); + background-size: 28px 28px; + -webkit-mask-image: radial-gradient(ellipse 70% 55% at 50% 28%, #000 25%, transparent 75%); + mask-image: radial-gradient(ellipse 70% 55% at 50% 28%, #000 25%, transparent 75%); +} + +/* 指针光斑:--hx / --hy 由 site.js 以插值方式写入,移动有「拖尾」 */ +.hero-spot { + position: absolute; + inset: 0; + background: radial-gradient( + 560px circle at var(--hx, 50%) var(--hy, 30%), + rgba(111, 196, 145, 0.13), + transparent 62% + ); +} + +/* 文字层随滚动轻微上移淡出(--hp 为 Hero 滚动进度 0 → 1) */ .hero-inner { position: relative; width: var(--content); + opacity: calc(1 - var(--hp, 0) * 1.15); + transform: translate3d(0, calc(var(--hp, 0) * -70px), 0); } .launch-pill { display: inline-flex; align-items: center; gap: 9px; - margin-bottom: 28px; + margin-bottom: 26px; padding: 8px 16px; border: 1px solid var(--stage-line); border-radius: 999px; @@ -340,6 +545,7 @@ html[data-lang="zh"] h3 { } .launch-dot { + position: relative; width: 7px; height: 7px; border-radius: 50%; @@ -347,29 +553,73 @@ html[data-lang="zh"] h3 { box-shadow: 0 0 12px rgba(111, 196, 145, 0.9); } -.hero h1 { - margin-bottom: 22px; - font-size: clamp(2.7rem, 6.2vw, 4.5rem); - font-weight: 700; - letter-spacing: -0.032em; - line-height: 1.06; +.launch-dot::after { + position: absolute; + inset: 0; + border-radius: 50%; + background: var(--stage-accent); + content: ""; + animation: ping 2.4s cubic-bezier(0, 0, 0.2, 1) infinite; } -.hero h1 span { +@keyframes ping { + 0% { opacity: 0.8; transform: scale(1); } + 80%, 100% { opacity: 0; transform: scale(3); } +} + +/* H1 承载品类关键词;视觉上是一行流光小标,主口号在其下方 */ +.hero-kicker { + margin: 0 auto 18px; + background: linear-gradient( + 100deg, + var(--stage-accent) 0%, + var(--stage-accent) 38%, + #e2f7ea 50%, + var(--stage-accent) 62%, + var(--stage-accent) 100% + ); + background-size: 250% 100%; + -webkit-background-clip: text; + background-clip: text; + color: transparent; + font-size: clamp(0.92rem, 1.4vw, 1.06rem); + font-weight: 650; + letter-spacing: 0.04em; + line-height: 1.5; + animation: shimmer 6.5s linear infinite; +} + +@keyframes shimmer { + from { background-position: 125% 0; } + to { background-position: -125% 0; } +} + +.hero-slogan { + margin: 0 0 24px; +} + +.slogan-main { display: block; + font-size: clamp(3rem, 7.6vw, 5.7rem); + font-weight: 700; + letter-spacing: -0.045em; + line-height: 1.02; + text-shadow: 0 0 60px rgba(111, 196, 145, 0.18); } -.hero-muted { - margin-top: 10px; +.slogan-sub { + display: block; + margin-top: 14px; color: var(--stage-ink-2); font-size: clamp(1.35rem, 2.8vw, 2.1rem); font-weight: 600; letter-spacing: -0.02em; + line-height: 1.25; } .hero-copy { - max-width: 620px; - margin: 0 auto 36px; + max-width: 640px; + margin: 0 auto 34px; color: var(--stage-ink-2); font-size: 1.08rem; line-height: 1.7; @@ -381,16 +631,17 @@ html[data-lang="zh"] h3 { justify-content: center; flex-wrap: wrap; gap: 18px; - margin-bottom: 30px; + margin-bottom: 28px; } .store-badge { display: inline-flex; - transition: transform 0.25s ease; + border-radius: 12px; + transition: transform 0.5s var(--ease-spring), filter 0.3s ease; } .store-badge:hover { - transform: translateY(-2px); + filter: drop-shadow(0 10px 24px rgba(111, 196, 145, 0.25)); } .store-badge img { @@ -409,17 +660,22 @@ html[data-lang="zh"] h3 { color: var(--stage-ink); font-size: 0.95rem; font-weight: 600; - transition: background 0.25s ease, transform 0.25s ease; + transition: background 0.25s ease, border-color 0.25s ease, transform 0.5s var(--ease-spring); } .ghost-action:hover { - background: rgba(255, 255, 255, 0.12); - transform: translateY(-2px); + border-color: rgba(111, 196, 145, 0.45); + background: rgba(111, 196, 145, 0.12); } .ghost-action .i { width: 17px; height: 17px; + transition: transform 0.5s var(--ease-spring); +} + +.ghost-action:hover .i { + transform: translate(3px, -3px) rotate(-8deg); } .text-action { @@ -429,11 +685,12 @@ html[data-lang="zh"] h3 { text-decoration: underline; text-decoration-color: var(--stage-line); text-underline-offset: 5px; - transition: color 0.2s ease; + transition: color 0.2s ease, text-decoration-color 0.2s ease; } .text-action:hover { color: var(--stage-ink); + text-decoration-color: var(--stage-accent); } .hero-trust { @@ -442,7 +699,7 @@ html[data-lang="zh"] h3 { justify-content: center; flex-wrap: wrap; gap: 10px 28px; - margin: 0 0 52px; + margin: 0 0 44px; padding: 0; color: var(--stage-ink-2); font-size: 0.86rem; @@ -461,10 +718,18 @@ html[data-lang="zh"] h3 { color: var(--stage-accent); } -.hero-devices { +/* 主视觉舞台:透视容器,内部设备图随滚动从后仰 14° 立到正视 */ +.hero-stage { position: relative; width: min(1060px, 94vw); margin: auto 0 0; + perspective: 1600px; +} + +.hero-devices { + transform: rotateX(calc((1 - var(--hp, 0)) * 14deg)) scale(calc(0.92 + var(--hp, 0) * 0.08)); + transform-origin: 50% 100%; + will-change: transform; } .hero-devices img { @@ -473,8 +738,295 @@ html[data-lang="zh"] h3 { filter: drop-shadow(0 48px 80px rgba(0, 0, 0, 0.5)); } +/* 语音胶囊:毛玻璃深色药丸,悬浮在 Mac 屏幕下沿 */ +.voice-capsule { + position: absolute; + z-index: 2; + bottom: 22%; + left: 50%; + display: flex; + max-width: 92%; + align-items: center; + gap: 14px; + padding: 9px 22px 9px 9px; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 999px; + background: rgba(14, 16, 19, 0.74); + -webkit-backdrop-filter: blur(18px) saturate(1.6); + backdrop-filter: blur(18px) saturate(1.6); + box-shadow: 0 28px 60px -18px rgba(0, 0, 0, 0.75), inset 0 1px 0 rgba(255, 255, 255, 0.08); + color: var(--stage-ink); + translate: -50% 0; +} + +.capsule-orb { + position: relative; + display: grid; + width: 42px; + height: 42px; + flex: none; + place-items: center; + border-radius: 50%; + background: radial-gradient(circle at 35% 30%, #a8e6c1, var(--stage-accent) 55%, #3c9c63); + color: #0c0e12; +} + +.capsule-orb .i { + width: 19px; + height: 19px; + stroke-width: 2; +} + +/* 录音态:橙绿色涟漪从按钮向外扩散 */ +.capsule-orb::before, +.capsule-orb::after { + position: absolute; + inset: 0; + border: 1.5px solid var(--stage-accent); + border-radius: 50%; + content: ""; + opacity: 0; +} + +.voice-capsule.is-listening .capsule-orb::before, +.voice-capsule.is-listening .capsule-orb::after { + animation: orb-ripple 1.8s cubic-bezier(0.2, 0.6, 0.3, 1) infinite; +} + +.voice-capsule.is-listening .capsule-orb::after { + animation-delay: 0.9s; +} + +@keyframes orb-ripple { + 0% { opacity: 0.9; transform: scale(1); } + 100% { opacity: 0; transform: scale(1.9); } +} + +/* 声波条:site.js 每帧写 scaleY,只动 transform,不触发布局 */ +.capsule-wave, +.kb-wave { + display: flex; + align-items: center; +} + +.capsule-wave { + height: 28px; + gap: 3px; +} + +.capsule-wave i { + width: 3px; + height: 100%; + border-radius: 3px; + background: linear-gradient(180deg, #c9f3da, var(--stage-accent)); + transform: scaleY(0.14); +} + +.capsule-body { + display: flex; + min-width: min(400px, 52vw); + align-items: center; + gap: 10px; + font-size: 0.98rem; + text-align: left; + white-space: nowrap; +} + +.capsule-tag { + flex: none; + padding: 3px 10px; + border-radius: 999px; + background: rgba(111, 196, 145, 0.16); + color: var(--stage-accent); + font-size: 0.74rem; + font-weight: 700; + letter-spacing: 0.04em; + transition: background 0.3s ease, color 0.3s ease; +} + +.capsule-tag.is-agent { + background: var(--stage-accent); + color: #0c0e12; +} + +.capsule-line { + overflow: hidden; + min-width: 0; + text-overflow: ellipsis; +} + +.caret { + display: inline-block; + width: 2px; + height: 1.1em; + flex: none; + margin-left: 1px; + border-radius: 1px; + background: var(--stage-accent); + vertical-align: -0.18em; + animation: caret-blink 1.05s steps(1) infinite; +} + +@keyframes caret-blink { + 50% { opacity: 0; } +} + +.scroll-cue { + position: absolute; + z-index: 3; + right: 32px; + bottom: 30px; + display: grid; + width: 24px; + height: 38px; + justify-items: center; + padding-top: 7px; + border: 1.5px solid rgba(255, 255, 255, 0.28); + border-radius: 999px; + opacity: calc(1 - var(--hp, 0) * 4); +} + +.scroll-cue span { + width: 3px; + height: 8px; + border-radius: 3px; + background: var(--stage-ink); + animation: cue 2s var(--ease-in-out) infinite; +} + +@keyframes cue { + 0% { opacity: 0; transform: translateY(-3px); } + 30% { opacity: 1; } + 100% { opacity: 0; transform: translateY(12px); } +} + +/* 首屏进场:Hero 元素按 --d 错峰「浮现 + 去模糊」 */ +html.js .hero-in { + animation: rise-in 1.1s var(--ease-out) var(--d, 0s) both; +} + +@keyframes rise-in { + from { opacity: 0; filter: blur(8px); translate: 0 26px; } + to { opacity: 1; filter: none; translate: 0 0; } +} + +html.js .voice-capsule { + animation: capsule-in 1.1s var(--ease-spring) 1.6s both; +} + +@keyframes capsule-in { + from { opacity: 0; transform: translateY(24px) scale(0.86); } + to { opacity: 1; transform: none; } +} + /* -------------------------------------------------------------------------- - * 5. 通用区块节奏 + * 5. 逐字动效(data-split) + * site.js 把文字拆成 .w(词,防止英文断词)与 .ch(字符), + * 每个字符带 --ci 序号,延迟 = 基础延迟 + 序号 × 步长。 + * -------------------------------------------------------------------------- */ +[data-split] .w { + display: inline-block; + white-space: nowrap; +} + +[data-split] .ch { + display: inline-block; +} + +/* 拆分完成前先隐藏,避免「整句出现 → 被拆开重播」的闪烁 */ +html.js [data-split]:not(.is-split) { + opacity: 0; +} + +/* Hero 口号:页面加载即播放 */ +html.js .hero-slogan .ch { + animation: char-in 1s var(--ease-out) both; + animation-delay: calc(var(--base, 0.2s) + var(--ci) * var(--step, 45ms)); +} + +.slogan-main { --base: 0.22s; --step: 52ms; } +.slogan-sub { --base: 0.52s; --step: 24ms; } + +@keyframes char-in { + from { opacity: 0; filter: blur(10px); transform: translateY(0.55em) rotate(3deg); } + to { opacity: 1; filter: none; transform: none; } +} + +/* 区块标题:所在 .reveal 进入视口时播放 */ +html.js .reveal [data-split] .ch { + opacity: 0; + filter: blur(8px); + transform: translateY(0.5em); + transition: opacity 0.7s ease-out, filter 0.7s ease-out, transform 0.95s var(--ease-out); + transition-delay: calc(var(--d, 0s) + 0.08s + var(--ci) * 24ms); +} + +html.js .reveal.is-visible [data-split] .ch { + opacity: 1; + filter: blur(0); + transform: none; +} + +/* -------------------------------------------------------------------------- + * 6. 能力跑马灯 + * -------------------------------------------------------------------------- */ +.marquee { + overflow: hidden; + padding: 20px 0; + border-bottom: 1px solid var(--line); + background: var(--canvas); + -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent); + mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent); +} + +.marquee-track { + display: flex; + width: max-content; + animation: marquee 52s linear infinite; +} + +.marquee:hover .marquee-track { + animation-play-state: paused; +} + +.marquee ul { + display: flex; + margin: 0; + padding: 0; + list-style: none; +} + +.marquee li { + display: flex; + align-items: center; + gap: 30px; + padding-right: 30px; + color: var(--ink-3); + font-size: clamp(1.05rem, 1.8vw, 1.4rem); + font-weight: 650; + letter-spacing: -0.01em; + white-space: nowrap; +} + +.marquee li:nth-child(-n + 3) { + color: var(--ink); +} + +.marquee li::after { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--accent); + content: ""; + opacity: 0.55; +} + +@keyframes marquee { + to { transform: translateX(-50%); } +} + +/* -------------------------------------------------------------------------- + * 7. 通用区块节奏 * -------------------------------------------------------------------------- */ .section { padding: clamp(88px, 11vw, 136px) 0; @@ -512,12 +1064,12 @@ h3 { } .center-heading { - max-width: 680px; + max-width: 700px; margin: 0 auto clamp(48px, 6vw, 72px); text-align: center; } -.center-heading p { +.center-heading p:not(.eyebrow) { color: var(--ink-2); font-size: 1.05rem; } @@ -538,7 +1090,7 @@ h3 { order: 1; } -.feature-copy > p { +.feature-copy > p:not(.eyebrow) { color: var(--ink-2); font-size: 1.05rem; } @@ -567,7 +1119,7 @@ h3 { } /* -------------------------------------------------------------------------- - * 6. 四种输入方式速览 + * 8. 四种输入方式速览 * -------------------------------------------------------------------------- */ .ways-section { padding: clamp(64px, 8vw, 96px) 0; @@ -585,22 +1137,75 @@ h3 { border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface); - transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; + transition: transform 0.5s var(--ease-out), box-shadow 0.35s ease, border-color 0.35s ease; } .way-card:hover { border-color: var(--line-strong); box-shadow: var(--shadow-card); - transform: translateY(-4px); } -.way-card .i { - width: 24px; - height: 24px; - margin-bottom: 16px; +.way-icon { + position: relative; + display: inline-grid; + width: 46px; + height: 46px; + margin-bottom: 18px; + place-items: center; + border-radius: 14px; + background: var(--accent-soft); color: var(--accent); } +.way-icon .i { + width: 23px; + height: 23px; +} + +/* 图标微动效:每张卡片一个与功能对应的小动作 */ +.way-icon-mic::after { + position: absolute; + inset: 0; + border: 1.5px solid var(--accent); + border-radius: inherit; + content: ""; + opacity: 0; +} + +.way-card:hover .way-icon-mic::after { + animation: orb-ripple 1.4s cubic-bezier(0.2, 0.6, 0.3, 1) infinite; +} + +.way-card:hover .way-icon-sparkle .i { + animation: twinkle 0.9s var(--ease-spring); +} + +.way-card:hover .way-icon-bubble .i { + animation: bob 0.9s var(--ease-spring); +} + +.way-card:hover .way-icon-paste .i { + animation: drop 0.9s var(--ease-spring); +} + +@keyframes twinkle { + 0% { transform: scale(1) rotate(0); } + 40% { transform: scale(0.7) rotate(-40deg); } + 100% { transform: scale(1) rotate(0); } +} + +@keyframes bob { + 0% { transform: translateY(0); } + 35% { transform: translateY(-5px) rotate(-6deg); } + 100% { transform: translateY(0); } +} + +@keyframes drop { + 0% { transform: translateY(0); } + 35% { transform: translateY(4px) scale(0.92); } + 100% { transform: translateY(0); } +} + .way-card h3 { margin-bottom: 6px; } @@ -613,7 +1218,774 @@ h3 { } /* -------------------------------------------------------------------------- - * 7. 功能区块细节 + * 9. 滚动叙事(三幕剧) + * 状态类 s1…s8 由 site.js 依滚动进度累加到 .story 上; + * 只有 .story.is-live(脚本已接管)时才进入「未发生 → 隐藏」的初始态, + * 无 JavaScript 时手机直接展示最终画面。 + * -------------------------------------------------------------------------- */ +.story-section { + padding-bottom: 0; +} + +.story-section .center-heading { + margin-bottom: 0; +} + +.story { + position: relative; + height: 430vh; +} + +.story-sticky { + position: sticky; + top: 0; + display: flex; + height: 100vh; + height: 100svh; + align-items: center; + padding-top: 64px; +} + +.story-layout { + display: grid; + width: var(--content); + align-items: center; + gap: clamp(40px, 7vw, 120px); + margin: 0 auto; + grid-template-columns: 1fr auto; +} + +.story-acts { + display: grid; + max-width: 540px; + gap: 12px; + margin: 0; + padding: 0; + list-style: none; +} + +.story-act { + display: flex; + gap: 22px; + padding: 22px 24px; + border-radius: var(--radius-lg); + opacity: 0.36; + transition: opacity 0.5s ease, background 0.5s ease, box-shadow 0.5s ease, + transform 0.7s var(--ease-out); +} + +.story-act.is-active { + background: var(--surface); + box-shadow: var(--shadow-card), inset 0 0 0 1px var(--line); + opacity: 1; + transform: translateX(6px); +} + +.act-index { + padding-top: 3px; + color: var(--accent); + font-size: 0.82rem; + font-variant-numeric: tabular-nums; + font-weight: 700; + letter-spacing: 0.04em; +} + +.act-body { + flex: 1; +} + +.act-kicker { + margin: 0 0 6px; + color: var(--accent); + font-size: 0.8rem; + font-weight: 700; + letter-spacing: 0.06em; +} + +.act-body h3 { + margin-bottom: 8px; + font-size: clamp(1.22rem, 2vw, 1.55rem); + letter-spacing: -0.02em; +} + +.act-body > p:not(.act-kicker) { + margin: 0; + color: var(--ink-2); + font-size: 0.97rem; +} + +/* 幕内进度条:--ap 由 site.js 写入(0 → 1) */ +.act-bar { + display: block; + overflow: hidden; + height: 2px; + margin-top: 16px; + border-radius: 2px; + background: var(--line); +} + +.act-bar i { + display: block; + height: 100%; + background: var(--accent); + transform: scaleX(var(--ap, 0)); + transform-origin: left; +} + +/* 舞台:手机 + 背后呼吸光晕 */ +.story-device { + position: relative; + display: grid; + place-items: center; +} + +.story-device::before { + position: absolute; + width: 140%; + height: 80%; + border-radius: 50%; + background: radial-gradient(closest-side, color-mix(in srgb, var(--accent) 26%, transparent), transparent); + content: ""; + filter: blur(30px); + opacity: 0.55; + transition: opacity 0.6s ease, transform 0.8s var(--ease-out); +} + +.story.s2:not(.s3) .story-device::before { + opacity: 1; + transform: scale(1.08); +} + +/* 手机外壳:容器查询单位(cqw)让内部 UI 随机身等比缩放 */ +.phone { + --w: min(340px, calc((100svh - 150px) * 0.468)); + position: relative; + width: var(--w); + aspect-ratio: 9 / 19.2; + padding: calc(var(--w) * 0.034); + border-radius: calc(var(--w) * 0.16); + background: linear-gradient(145deg, #39404a, #0d0f12 60%); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1), + 0 60px 100px -40px rgba(0, 0, 0, 0.5), 0 30px 60px -30px rgba(16, 20, 24, 0.35); + container-type: inline-size; +} + +.phone-screen { + position: relative; + display: flex; + overflow: hidden; + height: 100%; + flex-direction: column; + border-radius: 12.6cqw; + background: var(--ph-bg); + color: var(--ph-ink); + font-size: 4cqw; + line-height: 1.38; + text-align: left; +} + +.phone-status { + display: flex; + height: 12cqw; + flex: none; + align-items: center; + justify-content: space-between; + padding: 1.6cqw 8cqw 0; + font-size: 3.6cqw; + font-weight: 650; +} + +.phone-island { + position: absolute; + top: 2.8cqw; + left: 50%; + width: 27cqw; + height: 7.6cqw; + margin-left: -13.5cqw; + border-radius: 99px; + background: #000; +} + +.phone-signal { + display: flex; + align-items: flex-end; + gap: 0.7cqw; + height: 2.8cqw; +} + +.phone-signal i { + width: 0.9cqw; + border-radius: 0.4cqw; + background: currentColor; +} + +.phone-signal i:nth-child(1) { height: 40%; } +.phone-signal i:nth-child(2) { height: 70%; } +.phone-signal i:nth-child(3) { height: 100%; } + +.chat-head { + display: flex; + flex: none; + flex-direction: column; + align-items: center; + gap: 1.2cqw; + padding: 1.2cqw 0 3cqw; + border-bottom: 1px solid var(--ph-line); +} + +.chat-avatar { + display: grid; + width: 11cqw; + height: 11cqw; + place-items: center; + border-radius: 50%; + background: linear-gradient(145deg, #7fd0a0, #2c8a52); + color: #fff; + font-size: 4.4cqw; + font-weight: 700; +} + +.chat-name { + display: grid; + justify-items: center; + line-height: 1.2; +} + +.chat-name b { + font-size: 3.4cqw; + font-weight: 650; +} + +.chat-name small { + color: var(--ph-muted); + font-size: 2.7cqw; +} + +.chat-body { + display: flex; + min-height: 0; + flex: 1; + flex-direction: column; + gap: 2.6cqw; + padding: 4cqw 4.4cqw; +} + +.bubble { + position: relative; + max-width: 76%; + padding: 2.3cqw 3.6cqw; + border-radius: 5cqw; + font-size: 3.85cqw; + transition: opacity 0.35s ease, transform 0.7s var(--ease-spring), box-shadow 0.4s ease; +} + +.bubble.in { + align-self: flex-start; + border-bottom-left-radius: 1.4cqw; + background: var(--ph-in); + transform-origin: 0 100%; +} + +.bubble.out { + align-self: flex-end; + border-bottom-right-radius: 1.4cqw; + background: var(--ph-out); + color: #fff; + transform-origin: 100% 100%; +} + +.story.is-live .bubble { + opacity: 0; + transform: translateY(3cqw) scale(0.6); +} + +.story.is-live.s1 .b-ask, +.story.is-live.s4 .b-sent, +.story.is-live.s5 .b-addr { + opacity: 1; + transform: none; +} + +/* 第三幕:复制地址 → 气泡高亮 + 「已复制」提示 */ +.story.s6 .b-addr { + box-shadow: 0 0 0 0.8cqw color-mix(in srgb, var(--ph-out) 40%, transparent); +} + +.copy-toast { + position: absolute; + top: -6.8cqw; + left: 4cqw; + display: flex; + align-items: center; + gap: 1cqw; + padding: 1.1cqw 2.6cqw; + border-radius: 99px; + background: var(--ph-ink); + color: var(--ph-bg); + font-size: 2.9cqw; + font-weight: 600; + opacity: 0; + transform: translateY(2cqw) scale(0.8); + transition: opacity 0.3s ease, transform 0.6s var(--ease-spring); +} + +.copy-toast .i { + width: 3.4cqw; + height: 3.4cqw; + stroke-width: 2.4; +} + +.story.s6:not(.s7) .copy-toast { + opacity: 1; + transform: none; +} + +/* 日历通知横幅:从刘海下方落下 */ +.event-banner { + position: absolute; + z-index: 3; + top: 2.6cqw; + right: 3.4cqw; + left: 3.4cqw; + display: flex; + align-items: center; + gap: 3cqw; + padding: 3cqw 3.4cqw; + border-radius: 6cqw; + background: color-mix(in srgb, var(--ph-bg) 82%, transparent); + -webkit-backdrop-filter: blur(20px) saturate(1.5); + backdrop-filter: blur(20px) saturate(1.5); + box-shadow: 0 6cqw 12cqw -4cqw rgba(0, 0, 0, 0.35), inset 0 0 0 1px var(--ph-line); + transition: transform 0.9s var(--ease-spring), opacity 0.4s ease; +} + +.story.is-live .event-banner { + opacity: 0; + transform: translateY(-130%) scale(0.92); +} + +.story.is-live.s8 .event-banner { + opacity: 1; + transform: none; +} + +.event-icon { + display: grid; + width: 10cqw; + height: 10cqw; + flex: none; + place-items: center; + border-radius: 2.8cqw; + background: var(--ph-out); + color: #fff; +} + +.event-icon .i { + width: 5.4cqw; + height: 5.4cqw; +} + +.event-copy { + display: grid; + min-width: 0; + flex: 1; + line-height: 1.3; +} + +.event-copy small { + color: var(--ph-muted); + font-size: 2.7cqw; +} + +.event-copy b { + font-size: 3.5cqw; +} + +.event-copy span { + overflow: hidden; + color: var(--ph-muted); + font-size: 2.9cqw; + text-overflow: ellipsis; + white-space: nowrap; +} + +.event-check { + display: grid; + width: 6.4cqw; + height: 6.4cqw; + flex: none; + place-items: center; + border-radius: 50%; + background: color-mix(in srgb, var(--ph-out) 18%, transparent); + color: var(--ph-out); + transition: transform 0.7s var(--ease-spring) 0.35s; +} + +.event-check .i { + width: 4cqw; + height: 4cqw; + stroke-width: 2.6; +} + +.story.is-live .event-check { + transform: scale(0); +} + +.story.is-live.s8 .event-check { + transform: none; +} + +/* 输入框:文字由 site.js 按滚动进度逐字写入 */ +.compose { + display: flex; + flex: none; + align-items: flex-end; + gap: 2.4cqw; + padding: 2.4cqw 3.6cqw 2.8cqw; +} + +.compose-field { + position: relative; + display: block; + min-height: 9.4cqw; + flex: 1; + padding: 2cqw 3.6cqw; + border-radius: 4.8cqw; + box-shadow: inset 0 0 0 1px var(--ph-line); + font-size: 3.7cqw; +} + +.compose-placeholder { + position: absolute; + top: 2cqw; + left: 3.6cqw; + color: var(--ph-muted); + transition: opacity 0.2s ease; +} + +.story.has-text .compose-placeholder { + opacity: 0; +} + +.compose-text { + position: relative; +} + +.compose-text.is-swapping { + animation: text-swap 0.45s var(--ease-out); +} + +@keyframes text-swap { + from { opacity: 0; filter: blur(4px); } + to { opacity: 1; filter: blur(0); } +} + +.compose .caret { + background: var(--ph-out); + visibility: hidden; +} + +.story.s2:not(.s4) .compose .caret { + visibility: visible; +} + +.compose-send { + display: grid; + width: 9.4cqw; + height: 9.4cqw; + flex: none; + place-items: center; + border-radius: 50%; + background: var(--ph-line); + color: #fff; + transition: background 0.3s ease, transform 0.6s var(--ease-spring); +} + +.compose-send .i { + width: 5cqw; + height: 5cqw; + stroke-width: 2.4; +} + +.story.has-text .compose-send { + background: var(--ph-out); + transform: scale(1.05); +} + +/* 键盘:顶部条在「聆听 / 语气 / 技能」三种状态间切换 */ +.kb { + display: flex; + flex: none; + flex-direction: column; + gap: 2.4cqw; + padding: 2.6cqw 2.6cqw 6.4cqw; + background: var(--ph-kb); +} + +.kb-bar { + position: relative; + height: 8.6cqw; +} + +.kb-status, +.kb-tones, +.kb-skills { + position: absolute; + inset: 0; + display: flex; + align-items: center; + opacity: 0; + pointer-events: none; + transform: translateY(2cqw); + transition: opacity 0.35s ease, transform 0.6s var(--ease-spring); +} + +.story.s2:not(.s3) .kb-status, +.story.s3:not(.s4) .kb-tones, +.story.s7 .kb-skills, +.story:not(.is-live) .kb-skills { + opacity: 1; + transform: none; +} + +.kb-status { + justify-content: center; + gap: 2cqw; + color: var(--ph-out); + font-size: 3.4cqw; + font-weight: 650; +} + +.kb-status i { + width: 2cqw; + height: 2cqw; + border-radius: 50%; + background: currentColor; + animation: caret-blink 1s steps(1) infinite; +} + +.kb-tones { + gap: 1.8cqw; +} + +.kb-tones > span, +.kb-skills > span { + display: inline-flex; + align-items: center; + gap: 1cqw; + padding: 1.5cqw 3cqw; + border-radius: 99px; + background: var(--ph-key); + font-size: 3.2cqw; + font-weight: 600; + transition: background 0.3s ease, color 0.3s ease, transform 0.6s var(--ease-spring), + opacity 0.3s ease; +} + +.story.tone-0 [data-tone="0"], +.story.tone-1 [data-tone="1"], +.story.tone-2 [data-tone="2"] { + background: var(--ph-out); + color: #fff; + transform: scale(1.07); +} + +.kb-tones em { + margin-left: auto; + color: var(--ph-muted); + font-size: 2.6cqw; + font-style: normal; +} + +.kb-skills { + gap: 1.6cqw; +} + +.kb-skills .i { + width: 3.6cqw; + height: 3.6cqw; +} + +/* 技能芯片依次弹出,模拟「从技能目录里挑出相关操作」 */ +.story.is-live .kb-skills > span { + opacity: 0; + transform: translateY(3cqw) scale(0.7); +} + +.story.is-live.s7 .kb-skills > span { + opacity: 1; + transform: none; +} + +.kb-skills > span:nth-child(2) { transition-delay: 0.06s; } +.kb-skills > span:nth-child(3) { transition-delay: 0.12s; } +.kb-skills > span:nth-child(4) { transition-delay: 0.18s; } + +.story.is-live.s8 .kb-skills .skill-event, +.story:not(.is-live) .kb-skills .skill-event { + background: var(--ph-out); + color: #fff; + transform: scale(1.1); + transition-delay: 0s; +} + +.kb-main { + display: flex; + height: 21cqw; + align-items: center; + justify-content: center; + gap: 4cqw; +} + +.kb-wave { + height: 12cqw; + gap: 1.2cqw; +} + +.kb-wave i { + width: 1.1cqw; + height: 100%; + border-radius: 1cqw; + background: var(--ph-out); + opacity: 0.75; + transform: scaleY(0.1); +} + +.kb-mic { + position: relative; + display: grid; + width: 17cqw; + height: 17cqw; + place-items: center; + border-radius: 50%; + background: var(--ph-out); + box-shadow: 0 2cqw 6cqw -1cqw color-mix(in srgb, var(--ph-out) 70%, transparent); + color: #fff; + transition: transform 0.6s var(--ease-spring); +} + +.kb-mic .i { + width: 7.4cqw; + height: 7.4cqw; + stroke-width: 2; +} + +.kb-mic::before, +.kb-mic::after { + position: absolute; + inset: 0; + border: 0.5cqw solid var(--ph-out); + border-radius: 50%; + content: ""; + opacity: 0; +} + +.story.s2:not(.s3) .kb-mic { + transform: scale(1.08); +} + +.story.s2:not(.s3) .kb-mic::before, +.story.s2:not(.s3) .kb-mic::after { + animation: orb-ripple 1.6s cubic-bezier(0.2, 0.6, 0.3, 1) infinite; +} + +.story.s2:not(.s3) .kb-mic::after { + animation-delay: 0.8s; +} + +.kb-keys { + display: grid; + gap: 1.8cqw; + grid-template-columns: 1fr 3.4fr 1.5fr; +} + +.kb-key { + display: grid; + height: 10cqw; + place-items: center; + border-radius: 2.4cqw; + background: var(--ph-key); + box-shadow: 0 0.4cqw 0 rgba(0, 0, 0, 0.16); + font-size: 3.4cqw; + transition: transform 0.4s var(--ease-spring), background 0.3s ease; +} + +.kb-key .i { + width: 4.8cqw; + height: 4.8cqw; +} + +.kb-return { + background: var(--ph-out); + color: #fff; +} + +.story.s4:not(.s5) .kb-return { + animation: key-press 0.5s var(--ease-out); +} + +@keyframes key-press { + 30% { transform: scale(0.9); } +} + +/* -------------------------------------------------------------------------- + * 10. 数字带 + * -------------------------------------------------------------------------- */ +.numbers-section { + padding: clamp(64px, 8vw, 104px) 0; +} + +.numbers-grid { + display: grid; + gap: 28px; + grid-template-columns: repeat(4, 1fr); +} + +.number { + position: relative; + padding-top: 24px; +} + +/* 顶部细线随进场从左向右「画」出 */ +.number::before { + position: absolute; + top: 0; + right: 0; + left: 0; + height: 1px; + background: linear-gradient(90deg, var(--accent), var(--line-strong) 40%); + content: ""; + transform-origin: 0 50%; + transition: transform 1.3s var(--ease-out) var(--d, 0s); +} + +html.js .number:not(.is-visible)::before { + transform: scaleX(0); +} + +.number strong { + display: block; + margin-bottom: 12px; + background: linear-gradient(180deg, var(--ink) 30%, color-mix(in srgb, var(--ink) 50%, var(--accent))); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + font-size: clamp(2.4rem, 4.8vw, 3.7rem); + font-variant-numeric: tabular-nums; + font-weight: 700; + letter-spacing: -0.045em; + line-height: 1; +} + +.number span { + display: block; + max-width: 24ch; + color: var(--ink-2); + font-size: 0.94rem; +} + +/* -------------------------------------------------------------------------- + * 11. 功能区块细节 * -------------------------------------------------------------------------- */ .step-list { display: grid; @@ -633,8 +2005,8 @@ h3 { padding-top: 2px; color: var(--accent); font-size: 0.82rem; - font-weight: 700; font-variant-numeric: tabular-nums; + font-weight: 700; letter-spacing: 0.04em; } @@ -688,6 +2060,13 @@ h3 { background: var(--surface); font-size: 0.88rem; font-weight: 550; + transition: transform 0.5s var(--ease-spring), border-color 0.25s ease, color 0.25s ease; +} + +.skill-chips > span:hover { + border-color: color-mix(in srgb, var(--accent) 50%, transparent); + color: var(--accent); + transform: translateY(-3px); } .skill-chips .i { @@ -696,6 +2075,21 @@ h3 { color: var(--accent); } +/* 进场用 animation 而非 transition,避免进场延迟拖慢之后的悬停反馈 */ +html.js .reveal:not(.is-visible) .skill-chips > span { + opacity: 0; +} + +html.js .reveal.is-visible .skill-chips > span { + animation: chip-in 0.8s var(--ease-spring) both; + animation-delay: calc(0.3s + var(--ci, 0) * 60ms); +} + +@keyframes chip-in { + from { opacity: 0; transform: translateY(12px) scale(0.85); } + to { opacity: 1; transform: none; } +} + .quiet-list { display: grid; gap: 12px; @@ -726,7 +2120,7 @@ h3 { } /* -------------------------------------------------------------------------- - * 8. 个人化(2.1 卖点) + * 12. 嘴替输入法(2.1 卖点) * -------------------------------------------------------------------------- */ .personal-grid { display: grid; @@ -739,12 +2133,12 @@ h3 { border: 1px solid var(--line); border-radius: var(--radius-xl); background: var(--surface); - transition: transform 0.3s ease, box-shadow 0.3s ease; + transition: transform 0.5s var(--ease-out), box-shadow 0.35s ease; } .personal-card:hover { box-shadow: var(--shadow-card); - transform: translateY(-4px); + transform: translateY(-6px); } .personal-media { @@ -759,6 +2153,12 @@ h3 { height: 100%; object-fit: cover; object-position: top center; + transition: transform 1.2s var(--ease-out); +} + +.personal-card:hover .personal-media img, +.personal-card:hover .personal-media video { + transform: scale(1.04); } .personal-card h3 { @@ -773,17 +2173,96 @@ h3 { } /* -------------------------------------------------------------------------- - * 9. 隐私:三种服务路径(深色舞台) + * 13. 隐私:三种服务路径(深色舞台) * -------------------------------------------------------------------------- */ .privacy-section { + --spot-color: var(--stage-accent); background: linear-gradient(180deg, var(--stage-2), var(--stage-3)); color: var(--stage-ink); } -.center-heading.inverse p { +.center-heading.inverse p:not(.eyebrow) { color: var(--stage-ink-2); } +.privacy-section .eyebrow { + color: var(--stage-accent); +} + +/* 端侧徽记:声波在设备轮廓内跳动,轨道粒子绕行但从不越界 */ +.device-emblem { + position: relative; + width: 124px; + height: 124px; + margin: 0 auto 26px; +} + +.device-emblem::before { + position: absolute; + inset: -20%; + border-radius: 50%; + background: radial-gradient(closest-side, rgba(111, 196, 145, 0.28), transparent); + content: ""; + animation: breathe 4s ease-in-out infinite; +} + +@keyframes breathe { + 50% { opacity: 0.45; transform: scale(0.9); } +} + +.device-emblem svg { + position: relative; + width: 100%; + height: 100%; + overflow: visible; +} + +.emblem-orbit { + fill: none; + stroke: rgba(111, 196, 145, 0.4); + stroke-dasharray: 2 6; + stroke-width: 1; +} + +.emblem-spin { + fill: var(--stage-accent); + transform-origin: 80px 80px; + animation: spin 10s linear infinite; +} + +.emblem-spin-rev { + opacity: 0.6; + animation-direction: reverse; + animation-duration: 15s; +} + +@keyframes spin { + to { transform: rotate(360deg); } +} + +.emblem-device { + fill: rgba(255, 255, 255, 0.04); + stroke: rgba(255, 255, 255, 0.42); + stroke-width: 1.5; +} + +.emblem-wave rect { + fill: var(--stage-accent); + transform-box: fill-box; + transform-origin: center; + animation: eq 1.3s ease-in-out infinite; +} + +.emblem-wave rect:nth-child(2) { animation-delay: -0.2s; } +.emblem-wave rect:nth-child(3) { animation-delay: -0.45s; } +.emblem-wave rect:nth-child(4) { animation-delay: -0.7s; } +.emblem-wave rect:nth-child(5) { animation-delay: -0.95s; } + +@keyframes eq { + 0%, 100% { transform: scaleY(0.45); } + 50% { transform: scaleY(1); } +} + .service-paths { display: grid; align-items: stretch; @@ -897,10 +2376,15 @@ h3 { .inverse-link .i { width: 15px; height: 15px; + transition: transform 0.5s var(--ease-spring); +} + +.inverse-link:hover .i { + transform: translate(3px, -3px); } /* -------------------------------------------------------------------------- - * 10. 平台 + * 14. 平台 * -------------------------------------------------------------------------- */ .platform-grid { display: grid; @@ -913,6 +2397,11 @@ h3 { border: 1px solid var(--line); border-radius: var(--radius-xl); background: var(--surface); + transition: box-shadow 0.35s ease; +} + +.platform-card:hover { + box-shadow: var(--shadow-card); } .platform-card .media-frame { @@ -927,6 +2416,11 @@ h3 { height: 100%; object-fit: cover; object-position: top center; + transition: transform 1.4s var(--ease-out); +} + +.platform-card:hover .media-frame img { + transform: scale(1.035); } .platform-label { @@ -958,7 +2452,7 @@ h3 { } /* -------------------------------------------------------------------------- - * 11. 稳定性信任条 + * 15. 稳定性信任条 * -------------------------------------------------------------------------- */ .reliability-section { padding: clamp(48px, 6vw, 72px) 0; @@ -1007,7 +2501,7 @@ h3 { } /* -------------------------------------------------------------------------- - * 12. FAQ + * 16. FAQ(展开高度由 site.js 用 Web Animations 过渡) * -------------------------------------------------------------------------- */ .faq-layout { display: grid; @@ -1016,7 +2510,13 @@ h3 { grid-template-columns: 1fr 1.4fr; } +.faq-heading { + position: sticky; + top: 100px; +} + .faq-item { + overflow: hidden; border-top: 1px solid var(--line); } @@ -1050,7 +2550,7 @@ h3 { -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5l6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9.5l6 6 6-6'/%3E%3C/svg%3E") center / contain no-repeat; background: currentColor; - transition: transform 0.3s ease; + transition: transform 0.5s var(--ease-spring); } .faq-item[open] summary::after { @@ -1068,31 +2568,100 @@ h3 { font-size: 0.95rem; } +.faq-item[open] p { + animation: faq-in 0.5s var(--ease-out); +} + +@keyframes faq-in { + from { opacity: 0; transform: translateY(-6px); } + to { opacity: 1; transform: none; } +} + /* -------------------------------------------------------------------------- - * 13. 下载 CTA(深色舞台,与 Hero 呼应) + * 17. 下载 CTA(深色舞台,与 Hero 呼应) * -------------------------------------------------------------------------- */ .download-section { padding-top: 0; } +.faq-section + .download-section { + padding-top: clamp(72px, 9vw, 120px); +} + .download-panel { + position: relative; display: flex; + overflow: hidden; align-items: center; justify-content: space-between; gap: 40px; - padding: clamp(40px, 6vw, 72px); + padding: clamp(40px, 6vw, 80px); border-radius: calc(var(--radius-xl) + 4px); + isolation: isolate; background: radial-gradient(110% 130% at 90% 120%, rgba(111, 196, 145, 0.16), transparent 55%), linear-gradient(160deg, var(--stage-1), var(--stage-3)); color: var(--stage-ink); } -.download-panel h2 { - margin-bottom: 12px; +.download-backdrop { + position: absolute; + z-index: -1; + inset: 0; + pointer-events: none; } -.download-panel p { +.download-backdrop .aurora-a { + top: -60%; + left: -12%; + width: 520px; + height: 520px; +} + +.download-backdrop .aurora-b { + top: auto; + right: -14%; + bottom: -70%; + width: 520px; + height: 520px; +} + +/* 声波路径:虚线沿曲线流动,两条反向错速 */ +.download-wave { + position: absolute; + right: 0; + bottom: 0; + left: 0; + width: 100%; + height: 55%; + opacity: 0.45; +} + +.download-wave path { + fill: none; + stroke: var(--stage-accent); + stroke-dasharray: 6 12; + stroke-width: 1.5; + vector-effect: non-scaling-stroke; + animation: dash-flow 5s linear infinite; +} + +.download-wave path:nth-child(2) { + opacity: 0.5; + animation-direction: reverse; + animation-duration: 8s; +} + +@keyframes dash-flow { + to { stroke-dashoffset: -180; } +} + +.download-copy h2 { + margin-bottom: 12px; + font-size: clamp(2rem, 4.2vw, 3.1rem); +} + +.download-copy p { max-width: 460px; margin: 0; color: var(--stage-ink-2); @@ -1121,10 +2690,11 @@ h3 { color: var(--stage-ink); font-size: 0.92rem; font-weight: 600; - transition: background 0.25s ease; + transition: background 0.25s ease, border-color 0.25s ease, transform 0.5s var(--ease-spring); } .outline-action:hover { + border-color: rgba(111, 196, 145, 0.45); background: rgba(255, 255, 255, 0.08); } @@ -1134,7 +2704,7 @@ h3 { } /* -------------------------------------------------------------------------- - * 14. 页脚 + * 18. 页脚 * -------------------------------------------------------------------------- */ .site-footer { padding: 44px 0 52px; @@ -1165,42 +2735,185 @@ h3 { } /* -------------------------------------------------------------------------- - * 15. 滚动显现动效(尊重减弱动效设置) + * 19. 动效系统:滚动显现 / 遮罩揭示 / 光斑 / 倾斜 / 磁吸 / 主题过渡 * -------------------------------------------------------------------------- */ -.reveal { +/* + * 进场只动 opacity + translate(独立属性),把 transform 留给悬停倾斜与视差, + * 两套动效互不覆盖。进场延迟只作用在进场属性上,悬停反馈始终即时。 + */ +html.js .reveal { opacity: 0; - transform: translateY(22px); - transition: opacity 0.85s cubic-bezier(0.16, 1, 0.3, 1), - transform 0.85s cubic-bezier(0.16, 1, 0.3, 1); - transition-delay: var(--d, 0s); + translate: 0 30px; + transition-delay: var(--d, 0s), var(--d, 0s), 0s, 0s, 0s, var(--d, 0s); + transition-duration: 0.9s, 1.05s, 0.5s, 0.35s, 0.35s, 1.25s; + transition-property: opacity, translate, transform, box-shadow, border-color, clip-path; + transition-timing-function: ease-out, var(--ease-out), var(--ease-out), ease, ease, var(--ease-out); } -.reveal.is-visible { +html.js .reveal.is-visible { opacity: 1; + translate: 0 0; +} + +/* 视差元素的 transform 每帧由脚本写入,不再叠加过渡 */ +html.js .reveal[data-parallax] { + transition-duration: 0.9s, 1.05s, 0s, 0.35s, 0.35s, 1.25s; +} + +/* 遮罩揭示:媒体框从内缩的圆角矩形展开,内部画面同时从 112% 回落 */ +html.js .unmask.reveal { + clip-path: inset(9% 7% 9% 7% round var(--radius-lg)); +} + +html.js .unmask.reveal.is-visible { + clip-path: inset(0 0 0 0 round var(--radius-lg)); +} + +html.js .unmask > video, +html.js .unmask > img { + transform: scale(1.12); + transition: transform 1.6s var(--ease-out) var(--d, 0s); +} + +html.js .unmask.is-visible > video, +html.js .unmask.is-visible > img { transform: none; } +/* 指针光斑:卡片内部高光 + 边框描光,--mx / --my 由脚本写入 */ +.spot { + position: relative; + isolation: isolate; +} + +.spot::before, +.spot::after { + position: absolute; + inset: 0; + border-radius: inherit; + content: ""; + opacity: 0; + pointer-events: none; + transition: opacity 0.4s ease; +} + +.spot::before { + z-index: -1; + background: radial-gradient( + 420px circle at var(--mx, 50%) var(--my, 50%), + color-mix(in srgb, var(--spot-color, var(--accent)) 13%, transparent), + transparent 46% + ); +} + +.spot::after { + z-index: 2; + padding: 1px; + background: radial-gradient( + 260px circle at var(--mx, 50%) var(--my, 50%), + color-mix(in srgb, var(--spot-color, var(--accent)) 70%, transparent), + transparent 62% + ); + -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); + -webkit-mask-composite: xor; + mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0); +} + +.spot:hover::before, +.spot:hover::after { + opacity: 1; +} + +/* 3D 倾斜:--rx / --ry 由脚本根据指针位置写入 */ +.tilt { + transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) + translateY(var(--lift, 0px)); + transform-style: preserve-3d; +} + +.tilt:hover { + --lift: -5px; +} + +/* 主题切换:View Transitions 以按钮为圆心扩散(site.js 驱动 clip-path) */ +html.vt-theme::view-transition-old(root), +html.vt-theme::view-transition-new(root) { + animation: none; + mix-blend-mode: normal; +} + +html.vt-theme::view-transition-new(root) { + z-index: 1; +} + +/* -------------------------------------------------------------------------- + * 20. 减弱动态效果:所有持续动画停止,内容以最终态直接呈现 + * -------------------------------------------------------------------------- */ @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } - .reveal { - opacity: 1; - transform: none; - transition: none; + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + animation-delay: 0s !important; + transition-duration: 0.01ms !important; + transition-delay: 0s !important; } - .way-card, - .personal-card, - .store-badge, - .ghost-action { - transition: none; + html.js .reveal, + html.js .hero-in { + opacity: 1; + translate: none; + filter: none; + } + + html.js .unmask.reveal { + clip-path: none; + } + + html.js .unmask > video, + html.js .unmask > img { + transform: none; + } + + html.js .reveal [data-split] .ch { + opacity: 1; + filter: none; + transform: none; + } + + .hero-inner, + .hero-devices { + opacity: 1; + transform: none; + } + + .marquee-track { + width: auto; + flex-wrap: wrap; + justify-content: center; + } + + .marquee ul[aria-hidden="true"] { + display: none; + } + + .marquee ul { + flex-wrap: wrap; + justify-content: center; + } + + .tilt { + transform: none; } } /* -------------------------------------------------------------------------- - * 16. 响应式 + * 21. 响应式 * -------------------------------------------------------------------------- */ @media (max-width: 960px) { .nav-links { @@ -1221,6 +2934,10 @@ h3 { color: var(--ink); } + .mobile-nav[hidden] { + display: none; + } + .mobile-nav a { display: block; padding: 12px 8px; @@ -1233,6 +2950,10 @@ h3 { grid-template-columns: 1fr; } + .faq-heading { + position: static; + } + .split-layout.reverse .feature-copy { order: 1; } @@ -1241,7 +2962,8 @@ h3 { order: 2; } - .ways-grid { + .ways-grid, + .numbers-grid { grid-template-columns: 1fr 1fr; } @@ -1270,6 +2992,57 @@ h3 { flex-direction: column; align-items: flex-start; } + + /* 滚动叙事:手机在上,当前一幕的文案在下方叠放切换 */ + .story { + height: 400vh; + } + + .story-layout { + align-content: center; + gap: 22px; + grid-template-columns: 1fr; + justify-items: center; + } + + .story-device { + order: -1; + } + + .phone { + --w: min(290px, calc((100svh - 360px) * 0.468)); + } + + .story-acts { + position: relative; + display: block; + width: 100%; + max-width: 520px; + height: 196px; + } + + .story-act { + position: absolute; + inset: 0; + padding: 16px 20px; + opacity: 0; + pointer-events: none; + transform: translateY(14px); + } + + .story-act.is-active { + opacity: 1; + pointer-events: auto; + transform: none; + } + + .act-body > p:not(.act-kicker) { + font-size: 0.92rem; + } + + .scroll-cue { + display: none; + } } @media (max-width: 600px) { @@ -1278,7 +3051,7 @@ h3 { } .hero { - padding-top: 120px; + padding-top: 116px; } .ways-grid { @@ -1289,6 +3062,26 @@ h3 { gap: 14px; } + .voice-capsule { + bottom: 12%; + gap: 10px; + padding: 6px 14px 6px 6px; + } + + .capsule-orb { + width: 34px; + height: 34px; + } + + .capsule-wave { + display: none; + } + + .capsule-body { + min-width: min(260px, 70vw); + font-size: 0.84rem; + } + .privacy-proof { flex-direction: column; align-items: flex-start; @@ -1299,3 +3092,24 @@ h3 { align-items: flex-start; } } + +/* 无 JavaScript:滚动叙事退化为静态展示,不保留 430vh 的空白滚动距离 */ +html.no-js .story { + height: auto; +} + +html.no-js .story-sticky { + position: static; + height: auto; + padding: 48px 0 0; +} + +html.no-js .story-act { + position: static; + opacity: 1; + transform: none; +} + +html.no-js .story-acts { + height: auto; +} diff --git a/docs/assets/site.js b/docs/assets/site.js index 65d9926..004c5d0 100644 --- a/docs/assets/site.js +++ b/docs/assets/site.js @@ -1,10 +1,14 @@ /*! - * OSGKeyboard 主页交互。 + * OSGKeyboard 主页交互与动效引擎。 * 无 JavaScript 时页面内容完整可读;本文件负责: * 1. 中 / 英双语切换(data-zh / data-en 系列属性,含视频源) - * 2. 深浅色主题(默认跟随系统,手动选择后持久化) - * 3. 移动端导航开合 - * 4. 导航栏滚动状态、滚动显现动效、离屏视频暂停 + * 2. 深浅色主题(默认跟随系统,手动选择后持久化;支持时以圆形扩散过渡) + * 3. 移动端导航开合、导航滑块与当前区块高亮 + * 4. 逐字拆分标题(data-split) + * 5. 滚动引擎:进度条、Hero 设备「立起」、媒体视差、三幕滚动叙事 + * 6. 声波渲染、Hero 语音胶囊打字机、数字计数 + * 7. 指针光斑 / 3D 倾斜 / 磁吸按钮、FAQ 高度过渡、离屏视频暂停 + * 开启「减弱动态效果」时,持续动画与指针动效全部停用,内容直接呈现最终态。 */ (function () { "use strict"; @@ -21,6 +25,78 @@ const themeMedia = window.matchMedia("(prefers-color-scheme: dark)"); const supportedLanguages = new Set(["zh", "en"]); + const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)").matches; + const canObserve = "IntersectionObserver" in window; + + const TITLES = { + zh: "OSGKeyboard|AI 语音输入法 · 嘴替输入法 · iPhone / iPad / Mac", + en: "OSGKeyboard — Agentic Keyboard & AI Voice Keyboard for iPhone and Mac" + }; + + const clamp = (value, min = 0, max = 1) => Math.min(max, Math.max(min, value)); + + // 语言切换后需要重算的模块(胶囊文案、叙事文案、导航滑块)在此登记 + const languageHooks = []; + + /* ---------------- 逐字拆分 ---------------- */ + + // 每个分组是一段不可断开的文字:英文按词,中文按「到下一个标点为止」的短句, + // 与原标题的 word-break: keep-all 断行效果一致,标点也不会落到行首 + const CJK_PUNCTUATION = /[,。、!?:;)」』》…~]/; + + function groupCharacters(token) { + const groups = []; + let current = []; + for (const character of token) { + current.push(character); + if (CJK_PUNCTUATION.test(character)) { + groups.push(current); + current = []; + } + } + if (current.length) groups.push(current); + return groups; + } + + function splitText(element) { + const text = element.textContent.trim(); + // 读屏读取完整句子,逐字拆分的视觉层对辅助技术隐藏 + const readable = document.createElement("span"); + readable.className = "sr-only"; + readable.textContent = text; + const visual = document.createElement("span"); + visual.setAttribute("aria-hidden", "true"); + + let index = 0; + text.split(/(\s+)/).forEach((token) => { + if (!token) return; + if (/^\s+$/.test(token)) { + visual.append(" "); + return; + } + groupCharacters(token).forEach((group) => { + const word = document.createElement("span"); + word.className = "w"; + group.forEach((character) => { + const span = document.createElement("span"); + span.className = "ch"; + span.style.setProperty("--ci", String(index++)); + span.textContent = character; + word.append(span); + }); + visual.append(word); + }); + }); + + element.replaceChildren(readable, visual); + element.classList.add("is-split"); + } + + function splitAll() { + document.querySelectorAll("[data-split]").forEach(splitText); + } + /* ---------------- 语言 ---------------- */ function preferredLanguage() { @@ -50,7 +126,8 @@ // 同时覆盖 与