feat(polaris): 完善首页背景与搜索入口视觉

使用本地背景和官方 Provider 图标,闭合状态仅展示当前图标。
调整冷蓝磨砂层次,并移除快捷卡片外部阴影以消除底部色带。
补充搜索 Provider 展示行为测试。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 22:25:30 +08:00
co-authored by Claude Opus 4.8
parent c3d2a40c1f
commit 178478190f
9 changed files with 132 additions and 80 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts"; import "./.next/dev/types/routes.d.ts";
import "./.next/types/root-params.d.ts"; import "./.next/dev/types/root-params.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 MiB

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1788674931363" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1253" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M340.5824 70.109867L102.536533 0.682667v851.217066L340.650667 643.345067V70.109867zM102.536533 851.7632l238.045867 171.6224 580.881067-340.923733V411.784533L102.536533 851.831467z" fill="#409EFF" p-id="1254"></path><path d="M409.463467 255.3856l113.732266 238.933333 138.8544 56.866134 259.413334-139.400534-506.0608-156.330666z" fill="#409EFF" p-id="1255"></path></svg>

After

Width:  |  Height:  |  Size: 703 B

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1788674911609" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1089" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M950.930286 512q0 143.433143-83.748571 257.974857t-216.283429 158.573714q-15.433143 2.852571-22.601143-4.022857t-7.168-17.115429l0-120.539429q0-55.442286-29.696-81.115429 32.548571-3.437714 58.587429-10.313143t53.686857-22.308571 46.299429-38.034286 30.281143-59.977143 11.702857-86.016q0-69.12-45.129143-117.686857 21.138286-52.004571-4.534857-116.589714-16.018286-5.12-46.299429 6.290286t-52.589714 25.161143l-21.723429 13.677714q-53.174857-14.848-109.714286-14.848t-109.714286 14.848q-9.142857-6.290286-24.283429-15.433143t-47.689143-22.016-49.152-7.68q-25.161143 64.585143-4.022857 116.589714-45.129143 48.566857-45.129143 117.686857 0 48.566857 11.702857 85.723429t29.988571 59.977143 46.006857 38.253714 53.686857 22.308571 58.587429 10.313143q-22.820571 20.553143-28.013714 58.88-11.995429 5.705143-25.746286 8.557714t-32.548571 2.852571-37.449143-12.288-31.744-35.693714q-10.825143-18.285714-27.721143-29.696t-28.306286-13.677714l-11.410286-1.682286q-11.995429 0-16.603429 2.56t-2.852571 6.582857 5.12 7.972571 7.460571 6.875429l4.022857 2.852571q12.580571 5.705143 24.868571 21.723429t17.993143 29.110857l5.705143 13.165714q7.460571 21.723429 25.161143 35.108571t38.253714 17.115429 39.716571 4.022857 31.744-1.974857l13.165714-2.267429q0 21.723429 0.292571 50.834286t0.292571 30.866286q0 10.313143-7.460571 17.115429t-22.820571 4.022857q-132.534857-44.032-216.283429-158.573714t-83.748571-257.974857q0-119.442286 58.88-220.306286t159.744-159.744 220.306286-58.88 220.306286 58.88 159.744 159.744 58.88 220.306286z" fill="#444444" p-id="1090"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1788674935631" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1414" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M222.087529 515.915294c0-32.527059 5.481412-63.698824 15.179295-92.973176l-170.285177-128.90353A496.941176 496.941176 0 0 0 15.058824 515.945412c0 79.721412 18.672941 154.955294 51.802352 221.726117l170.255059-129.114353a293.767529 293.767529 0 0 1-15.028706-92.611764" fill="#FBBC05" p-id="1415"></path><path d="M521.246118 219.979294a294.460235 294.460235 0 0 1 186.337882 65.957647l147.245176-145.648941C765.108706 62.885647 650.089412 15.058824 521.246118 15.058824 321.204706 15.058824 149.293176 128.421647 66.981647 294.038588l170.405647 128.90353c39.243294-118.061176 151.100235-202.992941 283.919059-202.992942" fill="#EA4335" p-id="1416"></path><path d="M523.414588 805.315765c-133.511529 0-245.940706-84.359529-285.394823-201.728l-171.188706 128.12047C149.564235 896.301176 322.349176 1008.941176 523.444706 1008.941176c124.024471 0 242.447059-43.128471 331.38447-124.054588l-162.514823-123.090823c-45.808941 28.310588-103.574588 43.550118-168.929882 43.550117" fill="#34A853" p-id="1417"></path><path d="M1008.941176 511.216941c0-29.394824-4.638118-61.108706-11.565176-90.503529H523.354353v192.301176h272.835765c-13.613176 65.596235-50.718118 115.983059-103.875765 148.781177l162.484706 123.090823c93.394824-84.901647 154.142118-211.425882 154.142117-373.669647" fill="#4285F4" p-id="1418"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1788675424920" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1788" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M468.598154 0a156.750769 156.750769 0 0 1 124.849231 62.464l90.820923 121.304615h183.689846A156.593231 156.593231 0 0 1 1024 340.598154v526.808615A156.593231 156.593231 0 0 1 867.879385 1024H156.120615A156.593231 156.593231 0 0 1 0 867.328V156.672C0 70.262154 69.868308 0.236308 156.199385 0z m399.36 265.688615H81.447385v601.639385c0 41.353846 33.398154 74.830769 74.830769 74.988308h711.68a74.988308 74.988308 0 0 0 74.752-74.988308V340.519385a74.909538 74.909538 0 0 0-74.752-74.83077zM477.971692 315.076923a241.664 241.664 0 0 1 241.664 241.664c0 47.891692-14.414769 92.317538-38.518154 129.969231l91.372308 91.372308a51.751385 51.751385 0 1 1-73.255384 73.255384l-91.372308-91.372308a240.009846 240.009846 0 0 1-129.969231 38.360616 241.664 241.664 0 1 1 0-483.249231z m0 69.001846a172.583385 172.583385 0 1 0 0 345.245539 172.583385 172.583385 0 0 0 0-345.245539z m-9.452307-302.395077H156.041846a74.909538 74.909538 0 0 0-74.673231 74.988308v27.096615h500.972308l-54.272-72.152615a74.988308 74.988308 0 0 0-59.707077-29.932308z" p-id="1789" fill="#e98f36"></path></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+77 -60
View File
@@ -20,15 +20,33 @@
--primary: #3b82f6; --primary: #3b82f6;
--primary-hover: #2563eb; --primary-hover: #2563eb;
--primary-soft: rgba(59, 130, 246, 0.1); --primary-soft: rgba(59, 130, 246, 0.1);
--surface-sidebar: rgba(255, 255, 255, 0.68); --surface-sidebar: linear-gradient(
--surface-search: rgba(255, 255, 255, 0.72); 180deg,
--surface-card: rgba(255, 255, 255, 0.62); rgba(235, 243, 251, 0.52),
--surface-card-hover: rgba(255, 255, 255, 0.78); rgba(204, 220, 238, 0.34)
--border-glass: rgba(255, 255, 255, 0.58); );
--surface-search: linear-gradient(
135deg,
rgba(240, 246, 252, 0.56),
rgba(210, 225, 241, 0.4)
);
--surface-card: linear-gradient(
135deg,
rgba(231, 240, 249, 0.43),
rgba(198, 216, 236, 0.32)
);
--surface-card-hover: linear-gradient(
135deg,
rgba(242, 248, 253, 0.6),
rgba(216, 230, 244, 0.48)
);
--border-glass: rgba(255, 255, 255, 0.38);
--border-subtle: rgba(15, 23, 42, 0.08); --border-subtle: rgba(15, 23, 42, 0.08);
--shadow-soft: 0 14px 40px rgba(15, 23, 42, 0.1); --shadow-soft: inset 0 1px 0 rgba(255, 255, 255, 0.34);
--shadow-medium: 0 20px 60px rgba(15, 23, 42, 0.12); --shadow-medium:
--shadow-panel: 0 24px 80px rgba(15, 23, 42, 0.1); 0 22px 58px rgba(4, 12, 34, 0.17), inset 0 1px 0 rgba(255, 255, 255, 0.4);
--shadow-panel:
0 28px 72px rgba(4, 12, 34, 0.18), inset 0 1px 0 rgba(255, 255, 255, 0.42);
--radius-card: 20px; --radius-card: 20px;
--radius-panel: 24px; --radius-panel: 24px;
--sidebar-expanded: 240px; --sidebar-expanded: 240px;
@@ -50,23 +68,12 @@ body {
margin: 0; margin: 0;
overflow-x: hidden; overflow-x: hidden;
color: var(--text-primary); color: var(--text-primary);
background: background-color: #0b102b;
radial-gradient( background-image: url("/backgrounds/test.jpg");
circle at 12% 15%, background-position: center;
rgba(255, 245, 239, 0.92) 0, background-size: cover;
transparent 34% background-repeat: no-repeat;
), background-attachment: fixed;
radial-gradient(
circle at 82% 14%,
rgba(194, 222, 246, 0.92) 0,
transparent 38%
),
radial-gradient(
circle at 48% 82%,
rgba(226, 239, 247, 0.72) 0,
transparent 44%
),
linear-gradient(135deg, #eef5fa 0%, #dceaf4 48%, #cbdff0 100%);
font-family: font-family:
Inter, Inter,
"SF Pro Display", "SF Pro Display",
@@ -86,19 +93,14 @@ body::before {
z-index: -1; z-index: -1;
content: ""; content: "";
pointer-events: none; pointer-events: none;
background: background: linear-gradient(
linear-gradient( 180deg,
180deg, rgba(3, 7, 28, 0.2) 0%,
rgba(255, 255, 255, 0.2), rgba(9, 18, 54, 0.07) 48%,
rgba(255, 255, 255, 0.08) rgba(216, 230, 244, 0.08) 100%
), );
repeating-linear-gradient( -webkit-backdrop-filter: none;
165deg, backdrop-filter: none;
transparent 0 84px,
rgba(255, 255, 255, 0.08) 85px 86px
);
-webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
} }
button, button,
@@ -122,8 +124,8 @@ a {
border-radius: var(--radius-panel); border-radius: var(--radius-panel);
background: var(--surface-card); background: var(--surface-card);
box-shadow: var(--shadow-soft); box-shadow: var(--shadow-soft);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(16px) saturate(134%);
backdrop-filter: blur(20px); backdrop-filter: blur(16px) saturate(134%);
} }
.app-shell { .app-shell {
@@ -153,8 +155,8 @@ a {
border-radius: var(--radius-panel); border-radius: var(--radius-panel);
background: var(--surface-sidebar); background: var(--surface-sidebar);
box-shadow: var(--shadow-panel); box-shadow: var(--shadow-panel);
-webkit-backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(18px) saturate(132%);
backdrop-filter: blur(24px); backdrop-filter: blur(18px) saturate(132%);
transition: width 180ms ease; transition: width 180ms ease;
} }
@@ -165,7 +167,7 @@ a {
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 0 16px; padding: 0 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.46); border-bottom: 1px solid rgba(255, 255, 255, 0.26);
} }
.sidebar__brand { .sidebar__brand {
@@ -322,19 +324,21 @@ a {
.clock-greeting__time { .clock-greeting__time {
min-height: 1.2em; min-height: 1.2em;
margin: 0; margin: 0;
color: var(--text-primary); color: rgba(248, 250, 252, 0.98);
font-size: clamp(44px, 4.2vw, 56px); font-size: clamp(44px, 4.2vw, 56px);
font-weight: 700; font-weight: 700;
line-height: 1.16; line-height: 1.16;
letter-spacing: -0.035em; letter-spacing: -0.035em;
text-shadow: 0 3px 24px rgba(2, 6, 23, 0.48);
text-wrap: balance; text-wrap: balance;
} }
.clock-greeting__date { .clock-greeting__date {
margin: 12px 0 0; margin: 12px 0 0;
color: var(--text-secondary); color: rgba(241, 245, 249, 0.86);
font-size: 16px; font-size: 16px;
line-height: 1.6; line-height: 1.6;
text-shadow: 0 2px 16px rgba(2, 6, 23, 0.42);
} }
.search-box { .search-box {
@@ -352,8 +356,8 @@ a {
border-radius: var(--radius-panel); border-radius: var(--radius-panel);
background: var(--surface-search); background: var(--surface-search);
box-shadow: var(--shadow-medium); box-shadow: var(--shadow-medium);
-webkit-backdrop-filter: blur(22px); -webkit-backdrop-filter: blur(18px) saturate(136%);
backdrop-filter: blur(22px); backdrop-filter: blur(18px) saturate(136%);
transition: transition:
border-color 180ms ease, border-color 180ms ease,
box-shadow 180ms ease, box-shadow 180ms ease,
@@ -361,23 +365,35 @@ a {
} }
.search-box__form:focus-within { .search-box__form:focus-within {
border-color: rgba(59, 130, 246, 0.26); border-color: rgba(191, 219, 254, 0.46);
background: rgba(255, 255, 255, 0.8); background: linear-gradient(
135deg,
rgba(245, 249, 253, 0.66),
rgba(218, 231, 245, 0.52)
);
box-shadow: box-shadow:
0 0 0 4px rgba(59, 130, 246, 0.09), 0 0 0 4px rgba(147, 197, 253, 0.12),
var(--shadow-medium); var(--shadow-medium);
} }
.search-provider-menu__trigger { .search-provider-menu__trigger {
min-width: 126px; width: 48px;
min-width: 48px;
height: 48px; height: 48px;
justify-content: flex-start; justify-content: center;
margin-left: 10px; margin-left: 10px;
padding-inline: 14px; padding: 0;
color: #334155; color: #334155;
border-radius: 16px; border-radius: 16px;
} }
.search-provider-menu__icon {
width: 24px;
height: 24px;
flex: none;
object-fit: contain;
}
.search-provider-menu__trigger:hover, .search-provider-menu__trigger:hover,
.search-provider-menu__trigger[aria-expanded="true"] { .search-provider-menu__trigger[aria-expanded="true"] {
color: var(--primary-hover); color: var(--primary-hover);
@@ -389,7 +405,7 @@ a {
height: 20px; height: 20px;
flex: none; flex: none;
margin-left: 10px; margin-left: 10px;
color: var(--text-muted); color: rgba(51, 65, 85, 0.58);
} }
.search-box__input { .search-box__input {
@@ -406,7 +422,7 @@ a {
} }
.search-box__input::placeholder { .search-box__input::placeholder {
color: var(--text-muted); color: rgba(51, 65, 85, 0.62);
opacity: 1; opacity: 1;
} }
@@ -509,9 +525,10 @@ a {
.quick-links__title { .quick-links__title {
margin: 0 0 18px; margin: 0 0 18px;
color: var(--text-primary); color: rgba(248, 250, 252, 0.96);
font-size: 16px; font-size: 16px;
font-weight: 650; font-weight: 650;
text-shadow: 0 2px 14px rgba(2, 6, 23, 0.44);
} }
.quick-links__grid { .quick-links__grid {
@@ -533,8 +550,8 @@ a {
border-radius: var(--radius-card); border-radius: var(--radius-card);
background: var(--surface-card); background: var(--surface-card);
box-shadow: var(--shadow-soft); box-shadow: var(--shadow-soft);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(16px) saturate(134%);
backdrop-filter: blur(20px); backdrop-filter: blur(16px) saturate(134%);
transition: transition:
transform 180ms ease, transform 180ms ease,
background 180ms ease, background 180ms ease,
@@ -544,9 +561,9 @@ a {
.quick-link-card:hover { .quick-link-card:hover {
transform: translateY(-2px); transform: translateY(-2px);
border-color: rgba(255, 255, 255, 0.76); border-color: rgba(255, 255, 255, 0.54);
background: var(--surface-card-hover); background: var(--surface-card-hover);
box-shadow: 0 18px 50px rgba(15, 23, 42, 0.14); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
} }
.quick-link-card > span:last-child { .quick-link-card > span:last-child {
@@ -72,6 +72,34 @@ describe("SearchBox", () => {
expect(input).toHaveFocus() expect(input).toHaveFocus()
}) })
it("闭合态只显示当前图标,展开后显示 Provider 名称", async () => {
const user = userEvent.setup()
renderSearchBox()
const trigger = screen.getByRole("button", {
name: "选择搜索方式,当前为 Google",
})
expect(trigger.textContent).toBe("")
expect(trigger.querySelector("img")).toHaveAttribute(
"src",
"/icons/google.svg",
)
await user.click(trigger)
for (const [label, icon] of [
["Google", "google"],
["Bing", "bing"],
["GitHub", "github"],
["本地", "local"],
] as const) {
const item = screen.getByRole("menuitemradio", { name: label })
expect(item.querySelector("img")).toHaveAttribute(
"src",
`/icons/${icon}.svg`,
)
}
})
it("Google 模式回车只执行当前页外部导航", async () => { it("Google 模式回车只执行当前页外部导航", async () => {
const user = userEvent.setup() const user = userEvent.setup()
const { onExternalNavigate } = renderSearchBox() const { onExternalNavigate } = renderSearchBox()
@@ -1,12 +1,6 @@
"use client" "use client"
import { import Image from "next/image"
Database,
GitFork,
Globe2,
Search,
type LucideIcon,
} from "lucide-react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { import {
@@ -26,11 +20,24 @@ const PROVIDER_LABELS: Record<SearchProvider, string> = {
local: "本地", local: "本地",
} }
const PROVIDER_ICONS: Record<SearchProvider, LucideIcon> = { const PROVIDER_ICON_PATHS: Record<SearchProvider, string> = {
google: Globe2, google: "/icons/google.svg",
bing: Search, bing: "/icons/bing.svg",
github: GitFork, github: "/icons/github.svg",
local: Database, local: "/icons/local.svg",
}
function ProviderIcon({ provider }: { provider: SearchProvider }) {
return (
<Image
className="search-provider-menu__icon"
src={PROVIDER_ICON_PATHS[provider]}
alt=""
width={24}
height={24}
aria-hidden="true"
/>
)
} }
type SearchProviderMenuProps = { type SearchProviderMenuProps = {
@@ -44,8 +51,6 @@ export function SearchProviderMenu({
enabledProviders, enabledProviders,
onProviderChange, onProviderChange,
}: SearchProviderMenuProps) { }: SearchProviderMenuProps) {
const CurrentIcon = PROVIDER_ICONS[provider]
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@@ -55,8 +60,7 @@ export function SearchProviderMenu({
className="search-provider-menu__trigger" className="search-provider-menu__trigger"
aria-label={`选择搜索方式,当前为 ${PROVIDER_LABELS[provider]}`} aria-label={`选择搜索方式,当前为 ${PROVIDER_LABELS[provider]}`}
> >
<CurrentIcon aria-hidden="true" /> <ProviderIcon provider={provider} />
<span>{PROVIDER_LABELS[provider]}</span>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start"> <DropdownMenuContent align="start">
@@ -67,13 +71,12 @@ export function SearchProviderMenu({
}} }}
> >
{enabledProviders.map((enabledProvider) => { {enabledProviders.map((enabledProvider) => {
const Icon = PROVIDER_ICONS[enabledProvider]
return ( return (
<DropdownMenuRadioItem <DropdownMenuRadioItem
key={enabledProvider} key={enabledProvider}
value={enabledProvider} value={enabledProvider}
> >
<Icon aria-hidden="true" /> <ProviderIcon provider={enabledProvider} />
{PROVIDER_LABELS[enabledProvider]} {PROVIDER_LABELS[enabledProvider]}
</DropdownMenuRadioItem> </DropdownMenuRadioItem>
) )