From 0ff8e6fe203bc4a9090852c90f77d76e4747d599 Mon Sep 17 00:00:00 2001 From: fylorn <249551762+fylorn@users.noreply.github.com> Date: Thu, 1 Oct 2026 11:55:13 +0800 Subject: [PATCH] Fix the Lite bento layout, unframe screenshots, stack notifications - Menu bar tile: text on the left, the menu rising from the tile's bottom edge on the right, instead of leaving a large empty area. - Keys, dry run and notifications share one picture height so their headings line up. - Notifications tile shows a macOS-style notification stack using the sentences the app actually sends. - Screenshots on the Lite page and the home page lose the extra frame; they already carry the macOS window and its shadow. - Core hero: the Chinese title breaks after its first phrase so it no longer runs under the terminal. Co-Authored-By: Claude Opus 5.5 --- src/components/home/LiteBand.astro | 17 +- src/components/pages/CorePage.astro | 3 +- src/components/pages/LitePage.astro | 274 +++++++++++++++++++--------- src/i18n/pages/lite.ts | 21 +++ 4 files changed, 217 insertions(+), 98 deletions(-) diff --git a/src/components/home/LiteBand.astro b/src/components/home/LiteBand.astro index c8547f0..0a89e80 100644 --- a/src/components/home/LiteBand.astro +++ b/src/components/home/LiteBand.astro @@ -94,15 +94,20 @@ const zh = lang === "zh-CN"; font-size: 12px; color: #c3c7ce; } + /* The screenshot carries its own window and shadow; only a glow behind it */ .shot { - border-radius: 14px; - box-shadow: - 0 0 0 1px rgba(255, 255, 255, 0.14), - 0 50px 120px -40px rgba(0, 0, 0, 0.9), - 0 30px 100px -40px rgb(var(--a1) / 0.35); - overflow: hidden; + position: relative; transition: transform 500ms cubic-bezier(0.2, 0.7, 0.2, 1); } + .shot::before { + content: ""; + position: absolute; + inset: 12% 8%; + z-index: -1; + border-radius: 40px; + background: radial-gradient(closest-side, rgb(var(--a1) / 0.22), rgb(var(--a2) / 0.1) 60%, transparent); + filter: blur(40px); + } .shot img { display: block; width: 100%; diff --git a/src/components/pages/CorePage.astro b/src/components/pages/CorePage.astro index 7b4b80f..0dc7d1a 100644 --- a/src/components/pages/CorePage.astro +++ b/src/components/pages/CorePage.astro @@ -79,7 +79,8 @@ const fileLink = "break-all text-[var(--color-brand-1)] transition-colors hover: {c.motion.announce(version)}→ )}

- {zh ? {c.hero.titleA} : c.hero.titleA}{c.hero.titleHighlight} + {/* Chinese: the break goes after 的, so neither half runs under the terminal */} + {zh ? {c.hero.titleA.trim()}
: c.hero.titleA}{c.hero.titleHighlight}

{c.hero.sub}

diff --git a/src/components/pages/LitePage.astro b/src/components/pages/LitePage.astro index d030e41..869fd88 100644 --- a/src/components/pages/LitePage.astro +++ b/src/components/pages/LitePage.astro @@ -212,12 +212,12 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; 13.3M$9.34
-
-

{c.menubar.title}

-

{c.menubar.body}

-
+

{c.menubar.title}

+

{c.menubar.body}

+ + - {c.menubar.menuAlt}

{c.motion.bento.clientsCount}

@@ -240,21 +240,36 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const;
+
+

{feat.keys.title}

{feat.keys.body}

- {feat["dry-run"].alt} +
{feat["dry-run"].alt}

{feat["dry-run"].title}

{feat["dry-run"].body}

- +
+ +

{c.motion.bento.noticesTitle}

{c.menubar.notices}

@@ -1357,20 +1372,24 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; .step-shot { display: none; } + /* No frame of its own: the screenshots already carry the macOS window and its + shadow, so the screen only adds a soft brand glow behind them */ .screen { position: sticky; - top: calc(50vh - 220px); + top: calc(50vh - 240px); aspect-ratio: 2360 / 1540; max-width: 100%; - border-radius: 14px; - box-shadow: - 0 0 0 1px rgba(255, 255, 255, 0.14), - 0 50px 120px -40px rgba(0, 0, 0, 0.9), - 0 30px 100px -40px rgb(var(--a1) / 0.35); - overflow: hidden; - background: #0f1012; transition: transform 300ms ease-out; } + .screen::before { + content: ""; + position: absolute; + inset: 12% 8%; + z-index: -1; + border-radius: 40px; + background: radial-gradient(closest-side, rgb(var(--a1) / 0.22), rgb(var(--a2) / 0.1) 60%, transparent); + filter: blur(40px); + } .screen img { position: absolute; inset: 0; @@ -1455,20 +1474,21 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; } .t-menubar { grid-column: span 4; - grid-row: span 2; display: grid; - grid-template-columns: minmax(0, 1fr) 250px; - gap: 24px; - align-items: stretch; + grid-template-columns: minmax(0, 1fr) 260px; + gap: 32px; + align-items: center; + padding-bottom: 0; + min-height: 340px; } - /* the menu bar chip at the top, the text at the bottom, the menu on the right */ .t-menubar .tile-text { - display: flex; - flex-direction: column; - justify-content: space-between; + padding-bottom: 28px; } - .t-menubar .menu-shot { + /* the menu rises from the bottom edge of the tile */ + .menu-stage { align-self: end; + height: 312px; + overflow: hidden; } .chip-bar { align-self: flex-start; @@ -1494,14 +1514,14 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; font-weight: 500; } .menu-shot { - width: 250px; - margin-bottom: -60px; + display: block; + width: 260px; border-radius: 12px; box-shadow: 0 30px 80px -20px rgba(0, 0, 0, 0.8), 0 0 0 1px rgba(255, 255, 255, 0.14); transition: transform 500ms cubic-bezier(0.2, 0.7, 0.2, 1); } .t-menubar:hover .menu-shot { - transform: translateY(-14px); + transform: translateY(-10px); } .t-clients, .t-formats, @@ -1518,7 +1538,7 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; } .t-dry, .t-notices { - grid-column: span 3; + grid-column: span 2; } .crop-wrap { position: relative; @@ -1545,18 +1565,26 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; border-bottom: 1px solid rgb(var(--a1) / 0.6); animation: scan 3.6s ease-in-out infinite; } + /* One picture height for the whole row, so the headings below line up */ + .vis { + height: 168px; + margin-bottom: 22px; + display: flex; + flex-direction: column; + justify-content: center; + } + .t-dry .vis { + height: 196px; + margin: -28px -28px 22px; + } .dry-shot { display: block; width: 100%; - height: 170px; - margin: -28px 0 22px; + height: 100%; object-fit: cover; object-position: 60% 30%; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); -webkit-mask-image: linear-gradient(#000 70%, transparent); mask-image: linear-gradient(#000 70%, transparent); - width: calc(100% + 56px); - margin-left: -28px; } .big { margin: 0 !important; @@ -1601,7 +1629,7 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; animation-delay: 3s; } .keyrows { - margin: 0 0 22px; + margin: 0; padding: 0; list-style: none; font-family: var(--font-mono); @@ -1624,83 +1652,145 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; color: var(--color-text); font-variant-numeric: tabular-nums; } - .bell { - display: flex; - flex-direction: column; - gap: 6px; - margin-bottom: 22px; - } - .bell i { - display: block; - height: 30px; - border-radius: 8px; - border: 1px solid rgba(255, 255, 255, 0.08); - background: rgba(255, 255, 255, 0.02); + /* Notifications stacked the way macOS groups them: the newest in front, + older ones peeking out below and moving back as the next arrives */ + .notifs { + --nh: 92px; position: relative; - overflow: hidden; - } - .bell i::before { - content: ""; - position: absolute; - left: 10px; - top: 12px; - width: 6px; - height: 6px; - border-radius: 99px; - background: rgb(var(--a1)); - box-shadow: 0 0 8px rgb(var(--a1)); + height: calc(var(--nh) + 24px); + margin: 0; + padding: 0; + list-style: none; + /* the next one waits above the top edge, out of sight */ + clip-path: inset(0 -40px -40px -40px); } - .bell i::after { - content: ""; + .notif { position: absolute; - left: 26px; - right: 30%; - top: 13px; - height: 4px; - border-radius: 4px; - background: rgba(255, 255, 255, 0.1); - } - .bell i:nth-child(2)::before { - background: var(--color-accent); - box-shadow: 0 0 8px var(--color-accent); + inset: 0 0 auto; + height: var(--nh); + display: flex; + gap: 10px; + border: 1px solid rgba(255, 255, 255, 0.12); + border-radius: 14px; + padding: 10px 12px; + background: #202227; + box-shadow: 0 12px 30px -12px rgba(0, 0, 0, 0.8); + transform-origin: 50% 100%; + animation: notif 9s ease-in-out infinite; + } + .notif:nth-child(1) { + z-index: 3; + } + .notif:nth-child(2) { + z-index: 2; + transform: translateY(12px) scale(0.94); + filter: brightness(0.7); + animation-delay: -3s; + } + .notif:nth-child(3) { + z-index: 1; + transform: translateY(24px) scale(0.88); + filter: brightness(0.45); + animation-delay: -6s; + } + .notif-icon { + flex-shrink: 0; + display: grid; + place-items: center; + width: 30px; + height: 30px; + border-radius: 8px; + background: linear-gradient(180deg, #0d0f22, #020208); + box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08); } - .bell i:nth-child(2)::after { - right: 45%; + .notif-text { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + font-size: 12.5px; + line-height: 1.4; } - .bell i:nth-child(3)::before { - background: rgb(var(--a2)); - box-shadow: 0 0 8px rgb(var(--a2)); + .notif-head { + display: flex; + justify-content: space-between; + gap: 8px; + font-size: 11px; + color: var(--color-dim); } - .bell i:nth-child(3)::after { - right: 20%; + .notif-head b { + font-weight: 500; } - .bell i { - animation: notice 6s ease-in-out infinite; + .notif-title { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 600; + color: var(--color-text); } - .bell i:nth-child(2) { - animation-delay: 2s; + .notif-body { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; + color: var(--color-muted); } - .bell i:nth-child(3) { - animation-delay: 4s; + @keyframes notif { + /* above the one stepping back while it slides in */ + 0% { + z-index: 4; + opacity: 1; + transform: translateY(calc(-100% - 2px)); + filter: none; + } + 5% { + z-index: 4; + opacity: 1; + transform: none; + filter: none; + } + 33% { + z-index: 3; + opacity: 1; + transform: none; + filter: none; + } + 38%, + 66% { + z-index: 2; + opacity: 1; + transform: translateY(12px) scale(0.94); + filter: brightness(0.7); + } + 71%, + 95% { + z-index: 1; + opacity: 1; + transform: translateY(24px) scale(0.88); + filter: brightness(0.45); + } + 100% { + z-index: 1; + opacity: 0; + transform: translateY(24px) scale(0.88); + filter: brightness(0.45); + } } @media (max-width: 1023px) { .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); } .t-menubar, - .t-mcp { + .t-mcp, + .t-notices { grid-column: span 2; } .t-clients, .t-formats, .t-keys, - .t-dry, - .t-notices { + .t-dry { grid-column: span 1; } - .t-keys { - grid-column: span 2; - } } @media (max-width: 700px) { .bento { @@ -1718,9 +1808,11 @@ const archLabel = { x64: "x64", arm64: "ARM64" } as const; } .t-menubar { grid-template-columns: 1fr; + gap: 0; } - .menu-shot { - margin: 0 auto -80px; + .menu-stage { + justify-self: center; + height: 260px; } .t-mcp { grid-template-columns: 1fr; diff --git a/src/i18n/pages/lite.ts b/src/i18n/pages/lite.ts index f57f069..71eeb00 100644 --- a/src/i18n/pages/lite.ts +++ b/src/i18n/pages/lite.ts @@ -81,6 +81,21 @@ export const liteCopy = { clientsCount: "7", clientsCountSub: "set up in one step · 3 more by instructions", noticesTitle: "System notifications", + notices: [ + { + title: "Blocked Bash Call from “relay”", + body: "It matched the rule “Download and run”, so the response was cut off.", + }, + { + title: "“chatgpt” Subscription Quota Used Up", + body: "The 5-hour usage limit has been reached and resets in about 2 hours. Requests through this upstream will be rejected.", + }, + { + title: "Disconnected from “homelab”", + body: "Reconnecting. Until the connection is restored, the app shows the state at the time of the disconnect.", + }, + ], + now: "now", }, }, status: { @@ -310,6 +325,12 @@ export const liteCopy = { clientsCount: "7", clientsCountSub: "款一键接入 · 另有 3 款按说明配置", noticesTitle: "系统通知", + notices: [ + { title: "已拦截 relay 返回的 Bash 调用", body: "命中规则「下载即执行」,响应已切断。" }, + { title: "chatgpt 的订阅额度已用完", body: "5 小时额度已用完,约 2 小时后重置。经此上游的请求会被拒绝。" }, + { title: "与 homelab 的连接已断开", body: "正在重新连接。连接恢复前,应用中的内容停留在断开时的状态。" }, + ], + now: "现在", }, }, status: {