feat(docs): Mac/iPad/iPhone device-family hero on landing page

Replace the bordered hero screenshot card with an elegant nested
device mockup cluster; wire iPad home shots into the lang/theme matrix.
This commit is contained in:
Rocky
2026-07-12 13:40:10 +08:00
parent 47214fb917
commit a57bba9ea0
6 changed files with 195 additions and 39 deletions
+1
View File
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- **Unified welcome slogan**: iPad Home now reuses the iOS onboarding brand line, and macOS onboarding shows the same “Speak it. Its typed.” welcome slogan. / **统一欢迎口号**:iPad 首页复用 iOS 引导页品牌句,macOS 引导页也显示同一句「开口即文字。」欢迎口号。
- **GitHub Pages landing**: redesign as a commercial product page with zh/en, light/dark, brand mark, scroll motion, and App Store screenshots; emphasizes free, cross-platform, open source, privacy, and BYOK. / **GitHub Pages 落地页**:改版为商业产品页,支持中英与日夜模式、品牌标、滚动动效与 App Store 截图;突出免费、跨端、开源、隐私与 BYOK。
- **Landing hero device family**: Mac + iPad + iPhone nested in one mockup cluster (no outer card stroke); screens swap with language/theme. / **落地页 Hero 设备组**Mac、iPad、iPhone 叠放在同一组设备框内(无外卡片描边);截图随语言/主题切换。
## [0.5.3] - 2026-07-11
Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

+194 -39
View File
@@ -286,48 +286,181 @@
}
.hero-meta .material-symbols-outlined { font-size: 16px; color: var(--accent); }
/* —— Hero device family (Mac + iPad + iPhone) —— */
.hero-stage {
position: relative;
margin-top: 2.5rem;
margin-top: 2.75rem;
max-width: 1080px;
margin-left: auto;
margin-right: auto;
}
.hero-stage-frame {
border-radius: 18px;
overflow: hidden;
border: 1px solid var(--line);
box-shadow: var(--shadow);
background: var(--bg-elevated);
transform: translateY(12px);
opacity: 0;
animation: rise-in 1s cubic-bezier(0.22, 1, 0.36, 1) 0.15s forwards;
}
.hero-stage-frame img {
.device-cluster {
position: relative;
width: 100%;
height: auto;
display: block;
aspect-ratio: 16 / 10;
min-height: 280px;
transform: translateY(16px);
opacity: 0;
animation: rise-in 1s cubic-bezier(0.22, 1, 0.36, 1) 0.12s forwards;
--device-shell: #1c1c1e;
--device-shell-2: #2c2c2e;
--device-edge: rgba(255, 255, 255, 0.14);
--device-shadow: 0 28px 60px rgba(0, 0, 0, 0.28);
}
.sonic-ring {
html[data-theme="light"] .device-cluster {
--device-shell: #d8d8dc;
--device-shell-2: #c4c4c8;
--device-edge: rgba(0, 0, 0, 0.12);
--device-shadow: 0 24px 50px rgba(18, 18, 20, 0.16);
}
@media (prefers-color-scheme: light) {
html[data-theme="system"] .device-cluster {
--device-shell: #d8d8dc;
--device-shell-2: #c4c4c8;
--device-edge: rgba(0, 0, 0, 0.12);
--device-shadow: 0 24px 50px rgba(18, 18, 20, 0.16);
}
}
.device {
position: absolute;
box-sizing: border-box;
}
.device img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: top center;
}
/* MacBook — back right */
.device-mac {
right: 0;
top: 4%;
width: 72%;
z-index: 1;
filter: drop-shadow(var(--device-shadow));
}
.mac-lid {
border-radius: 10px 10px 0 0;
background: linear-gradient(180deg, var(--device-shell-2), var(--device-shell));
padding: 1.4% 1.4% 0.9%;
border: 1px solid var(--device-edge);
border-bottom: none;
}
.mac-bezel {
position: relative;
border-radius: 6px 6px 2px 2px;
overflow: hidden;
background: #000;
aspect-ratio: 16 / 10;
}
.mac-notch {
position: absolute;
top: 0;
left: 50%;
transform: translateX(-50%);
width: 18%;
height: 3.2%;
max-height: 12px;
background: #0a0a0b;
border-radius: 0 0 7px 7px;
z-index: 2;
}
.mac-screen {
position: absolute;
inset: 0;
overflow: hidden;
}
.mac-base {
height: 10px;
margin: 0 -1.5% 0;
border-radius: 0 0 10px 10px;
background: linear-gradient(180deg, var(--device-shell), var(--device-shell-2));
border: 1px solid var(--device-edge);
border-top: none;
position: relative;
}
.mac-base::after {
content: "";
position: absolute;
left: 50%;
bottom: -18px;
width: 120px;
height: 120px;
margin-left: -60px;
border-radius: 50%;
border: 1.5px solid var(--accent);
opacity: 0;
pointer-events: none;
animation: sonic 2.8s ease-out infinite;
top: 2px;
transform: translateX(-50%);
width: 16%;
height: 4px;
border-radius: 0 0 4px 4px;
background: rgba(0, 0, 0, 0.22);
}
/* iPad — center */
.device-ipad {
left: 22%;
top: 18%;
width: 36%;
z-index: 2;
filter: drop-shadow(0 22px 40px rgba(0, 0, 0, 0.22));
}
.ipad-bezel {
border-radius: 18px;
background: linear-gradient(160deg, var(--device-shell-2), var(--device-shell));
padding: 2.2%;
border: 1px solid var(--device-edge);
}
.ipad-screen {
border-radius: 10px;
overflow: hidden;
background: #000;
aspect-ratio: 3 / 4;
}
/* iPhone — front left */
.device-phone {
left: 4%;
bottom: 2%;
width: 20%;
z-index: 3;
filter: drop-shadow(0 18px 32px rgba(0, 0, 0, 0.26));
}
.phone-bezel {
position: relative;
border-radius: 22px;
background: linear-gradient(160deg, var(--device-shell-2), var(--device-shell));
padding: 3.2%;
border: 1px solid var(--device-edge);
}
.phone-island {
position: absolute;
top: 5.5%;
left: 50%;
transform: translateX(-50%);
width: 32%;
height: 3.8%;
border-radius: 20px;
background: #0a0a0b;
z-index: 2;
}
.phone-screen {
border-radius: 16px;
overflow: hidden;
background: #000;
aspect-ratio: 9 / 19.5;
}
@media (max-width: 720px) {
.device-cluster { aspect-ratio: 5 / 4; }
.device-mac { width: 78%; right: -2%; top: 0; }
.device-ipad { width: 42%; left: 14%; top: 22%; }
.device-phone { width: 26%; left: 2%; bottom: 0; }
.phone-bezel { border-radius: 18px; }
.phone-screen { border-radius: 12px; }
.ipad-bezel { border-radius: 14px; }
}
.sonic-ring:nth-child(2) { animation-delay: 0.9s; }
.sonic-ring:nth-child(3) { animation-delay: 1.8s; }
@keyframes rise-in {
to { transform: translateY(0); opacity: 1; }
}
@keyframes sonic {
0% { transform: scale(0.4); opacity: 0.45; }
100% { transform: scale(2.2); opacity: 0; }
}
/* —— Reveal —— */
.reveal {
@@ -660,7 +793,7 @@
transition: none !important;
}
.reveal { opacity: 1; transform: none; }
.hero-stage-frame { opacity: 1; transform: none; }
.device-cluster { opacity: 1; transform: none; }
}
</style>
</head>
@@ -723,12 +856,34 @@
</div>
</div>
<div class="hero-stage">
<div class="sonic-ring" aria-hidden="true"></div>
<div class="sonic-ring" aria-hidden="true"></div>
<div class="sonic-ring" aria-hidden="true"></div>
<div class="hero-stage-frame">
<img data-shot="mac-dashboard" src="assets/screenshots/zh/dark/mac-dashboard.png" width="1440" height="900" alt="OSGKeyboard Mac dashboard" loading="eager">
<div class="hero-stage" aria-label="OSGKeyboard on Mac, iPad, and iPhone">
<div class="device-cluster">
<div class="device device-mac">
<div class="mac-lid">
<div class="mac-bezel">
<div class="mac-notch" aria-hidden="true"></div>
<div class="mac-screen">
<img data-shot="mac-dashboard" src="assets/screenshots/zh/dark/mac-dashboard.png" width="1440" height="900" alt="OSGKeyboard on Mac" loading="eager">
</div>
</div>
</div>
<div class="mac-base" aria-hidden="true"></div>
</div>
<div class="device device-ipad">
<div class="ipad-bezel">
<div class="ipad-screen">
<img data-shot="ipad-home" src="assets/screenshots/zh/light/ipad-home.png" width="800" height="1066" alt="OSGKeyboard on iPad" loading="eager">
</div>
</div>
</div>
<div class="device device-phone">
<div class="phone-bezel">
<div class="phone-island" aria-hidden="true"></div>
<div class="phone-screen">
<img data-shot="iphone-home" src="assets/screenshots/zh/light/iphone-home.png" width="552" height="1200" alt="OSGKeyboard on iPhone" loading="eager">
</div>
</div>
</div>
</div>
</div>
</div>
@@ -1202,13 +1357,13 @@
}
// Soft parallax after hero entrance animation settles
const stage = document.querySelector(".hero-stage-frame");
const stage = document.querySelector(".device-cluster");
if (stage && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
let ready = false;
setTimeout(() => { ready = true; }, 1100);
window.addEventListener("scroll", () => {
if (!ready) return;
const y = Math.min(window.scrollY * 0.08, 36);
const y = Math.min(window.scrollY * 0.06, 28);
stage.style.transform = `translateY(${y}px)`;
}, { passive: true });
}