diff --git a/ts/web/nextjs/polaris/next-env.d.ts b/ts/web/nextjs/polaris/next-env.d.ts
index ce4e94a..a419cbe 100644
--- a/ts/web/nextjs/polaris/next-env.d.ts
+++ b/ts/web/nextjs/polaris/next-env.d.ts
@@ -1,7 +1,7 @@
///
///
-import "./.next/types/routes.d.ts";
-import "./.next/types/root-params.d.ts";
+import "./.next/dev/types/routes.d.ts";
+import "./.next/dev/types/root-params.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/ts/web/nextjs/polaris/public/backgrounds/test.jpg b/ts/web/nextjs/polaris/public/backgrounds/test.jpg
new file mode 100644
index 0000000..61ecead
Binary files /dev/null and b/ts/web/nextjs/polaris/public/backgrounds/test.jpg differ
diff --git a/ts/web/nextjs/polaris/public/icons/bing.svg b/ts/web/nextjs/polaris/public/icons/bing.svg
new file mode 100644
index 0000000..762165a
--- /dev/null
+++ b/ts/web/nextjs/polaris/public/icons/bing.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ts/web/nextjs/polaris/public/icons/github.svg b/ts/web/nextjs/polaris/public/icons/github.svg
new file mode 100644
index 0000000..e1aa6fc
--- /dev/null
+++ b/ts/web/nextjs/polaris/public/icons/github.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ts/web/nextjs/polaris/public/icons/google.svg b/ts/web/nextjs/polaris/public/icons/google.svg
new file mode 100644
index 0000000..3cca8a8
--- /dev/null
+++ b/ts/web/nextjs/polaris/public/icons/google.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ts/web/nextjs/polaris/public/icons/local.svg b/ts/web/nextjs/polaris/public/icons/local.svg
new file mode 100644
index 0000000..ddf16c4
--- /dev/null
+++ b/ts/web/nextjs/polaris/public/icons/local.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ts/web/nextjs/polaris/src/app/globals.css b/ts/web/nextjs/polaris/src/app/globals.css
index 495ccef..c8eacca 100644
--- a/ts/web/nextjs/polaris/src/app/globals.css
+++ b/ts/web/nextjs/polaris/src/app/globals.css
@@ -20,15 +20,33 @@
--primary: #3b82f6;
--primary-hover: #2563eb;
--primary-soft: rgba(59, 130, 246, 0.1);
- --surface-sidebar: rgba(255, 255, 255, 0.68);
- --surface-search: rgba(255, 255, 255, 0.72);
- --surface-card: rgba(255, 255, 255, 0.62);
- --surface-card-hover: rgba(255, 255, 255, 0.78);
- --border-glass: rgba(255, 255, 255, 0.58);
+ --surface-sidebar: linear-gradient(
+ 180deg,
+ rgba(235, 243, 251, 0.52),
+ rgba(204, 220, 238, 0.34)
+ );
+ --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);
- --shadow-soft: 0 14px 40px rgba(15, 23, 42, 0.1);
- --shadow-medium: 0 20px 60px rgba(15, 23, 42, 0.12);
- --shadow-panel: 0 24px 80px rgba(15, 23, 42, 0.1);
+ --shadow-soft: inset 0 1px 0 rgba(255, 255, 255, 0.34);
+ --shadow-medium:
+ 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-panel: 24px;
--sidebar-expanded: 240px;
@@ -50,23 +68,12 @@ body {
margin: 0;
overflow-x: hidden;
color: var(--text-primary);
- background:
- radial-gradient(
- circle at 12% 15%,
- rgba(255, 245, 239, 0.92) 0,
- transparent 34%
- ),
- 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%);
+ background-color: #0b102b;
+ background-image: url("/backgrounds/test.jpg");
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-attachment: fixed;
font-family:
Inter,
"SF Pro Display",
@@ -86,19 +93,14 @@ body::before {
z-index: -1;
content: "";
pointer-events: none;
- background:
- linear-gradient(
- 180deg,
- rgba(255, 255, 255, 0.2),
- rgba(255, 255, 255, 0.08)
- ),
- repeating-linear-gradient(
- 165deg,
- transparent 0 84px,
- rgba(255, 255, 255, 0.08) 85px 86px
- );
- -webkit-backdrop-filter: blur(8px);
- backdrop-filter: blur(8px);
+ background: linear-gradient(
+ 180deg,
+ rgba(3, 7, 28, 0.2) 0%,
+ rgba(9, 18, 54, 0.07) 48%,
+ rgba(216, 230, 244, 0.08) 100%
+ );
+ -webkit-backdrop-filter: none;
+ backdrop-filter: none;
}
button,
@@ -122,8 +124,8 @@ a {
border-radius: var(--radius-panel);
background: var(--surface-card);
box-shadow: var(--shadow-soft);
- -webkit-backdrop-filter: blur(20px);
- backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(16px) saturate(134%);
+ backdrop-filter: blur(16px) saturate(134%);
}
.app-shell {
@@ -153,8 +155,8 @@ a {
border-radius: var(--radius-panel);
background: var(--surface-sidebar);
box-shadow: var(--shadow-panel);
- -webkit-backdrop-filter: blur(24px);
- backdrop-filter: blur(24px);
+ -webkit-backdrop-filter: blur(18px) saturate(132%);
+ backdrop-filter: blur(18px) saturate(132%);
transition: width 180ms ease;
}
@@ -165,7 +167,7 @@ a {
align-items: center;
gap: 10px;
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 {
@@ -322,19 +324,21 @@ a {
.clock-greeting__time {
min-height: 1.2em;
margin: 0;
- color: var(--text-primary);
+ color: rgba(248, 250, 252, 0.98);
font-size: clamp(44px, 4.2vw, 56px);
font-weight: 700;
line-height: 1.16;
letter-spacing: -0.035em;
+ text-shadow: 0 3px 24px rgba(2, 6, 23, 0.48);
text-wrap: balance;
}
.clock-greeting__date {
margin: 12px 0 0;
- color: var(--text-secondary);
+ color: rgba(241, 245, 249, 0.86);
font-size: 16px;
line-height: 1.6;
+ text-shadow: 0 2px 16px rgba(2, 6, 23, 0.42);
}
.search-box {
@@ -352,8 +356,8 @@ a {
border-radius: var(--radius-panel);
background: var(--surface-search);
box-shadow: var(--shadow-medium);
- -webkit-backdrop-filter: blur(22px);
- backdrop-filter: blur(22px);
+ -webkit-backdrop-filter: blur(18px) saturate(136%);
+ backdrop-filter: blur(18px) saturate(136%);
transition:
border-color 180ms ease,
box-shadow 180ms ease,
@@ -361,23 +365,35 @@ a {
}
.search-box__form:focus-within {
- border-color: rgba(59, 130, 246, 0.26);
- background: rgba(255, 255, 255, 0.8);
+ border-color: rgba(191, 219, 254, 0.46);
+ background: linear-gradient(
+ 135deg,
+ rgba(245, 249, 253, 0.66),
+ rgba(218, 231, 245, 0.52)
+ );
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);
}
.search-provider-menu__trigger {
- min-width: 126px;
+ width: 48px;
+ min-width: 48px;
height: 48px;
- justify-content: flex-start;
+ justify-content: center;
margin-left: 10px;
- padding-inline: 14px;
+ padding: 0;
color: #334155;
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[aria-expanded="true"] {
color: var(--primary-hover);
@@ -389,7 +405,7 @@ a {
height: 20px;
flex: none;
margin-left: 10px;
- color: var(--text-muted);
+ color: rgba(51, 65, 85, 0.58);
}
.search-box__input {
@@ -406,7 +422,7 @@ a {
}
.search-box__input::placeholder {
- color: var(--text-muted);
+ color: rgba(51, 65, 85, 0.62);
opacity: 1;
}
@@ -509,9 +525,10 @@ a {
.quick-links__title {
margin: 0 0 18px;
- color: var(--text-primary);
+ color: rgba(248, 250, 252, 0.96);
font-size: 16px;
font-weight: 650;
+ text-shadow: 0 2px 14px rgba(2, 6, 23, 0.44);
}
.quick-links__grid {
@@ -533,8 +550,8 @@ a {
border-radius: var(--radius-card);
background: var(--surface-card);
box-shadow: var(--shadow-soft);
- -webkit-backdrop-filter: blur(20px);
- backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(16px) saturate(134%);
+ backdrop-filter: blur(16px) saturate(134%);
transition:
transform 180ms ease,
background 180ms ease,
@@ -544,9 +561,9 @@ a {
.quick-link-card:hover {
transform: translateY(-2px);
- border-color: rgba(255, 255, 255, 0.76);
+ border-color: rgba(255, 255, 255, 0.54);
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 {
diff --git a/ts/web/nextjs/polaris/src/features/home/components/search-box.test.tsx b/ts/web/nextjs/polaris/src/features/home/components/search-box.test.tsx
index b88324a..08dccc1 100644
--- a/ts/web/nextjs/polaris/src/features/home/components/search-box.test.tsx
+++ b/ts/web/nextjs/polaris/src/features/home/components/search-box.test.tsx
@@ -72,6 +72,34 @@ describe("SearchBox", () => {
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 () => {
const user = userEvent.setup()
const { onExternalNavigate } = renderSearchBox()
diff --git a/ts/web/nextjs/polaris/src/features/home/components/search-provider-menu.tsx b/ts/web/nextjs/polaris/src/features/home/components/search-provider-menu.tsx
index 7f9f1f3..1129903 100644
--- a/ts/web/nextjs/polaris/src/features/home/components/search-provider-menu.tsx
+++ b/ts/web/nextjs/polaris/src/features/home/components/search-provider-menu.tsx
@@ -1,12 +1,6 @@
"use client"
-import {
- Database,
- GitFork,
- Globe2,
- Search,
- type LucideIcon,
-} from "lucide-react"
+import Image from "next/image"
import { Button } from "@/components/ui/button"
import {
@@ -26,11 +20,24 @@ const PROVIDER_LABELS: Record = {
local: "本地",
}
-const PROVIDER_ICONS: Record = {
- google: Globe2,
- bing: Search,
- github: GitFork,
- local: Database,
+const PROVIDER_ICON_PATHS: Record = {
+ google: "/icons/google.svg",
+ bing: "/icons/bing.svg",
+ github: "/icons/github.svg",
+ local: "/icons/local.svg",
+}
+
+function ProviderIcon({ provider }: { provider: SearchProvider }) {
+ return (
+
+ )
}
type SearchProviderMenuProps = {
@@ -44,8 +51,6 @@ export function SearchProviderMenu({
enabledProviders,
onProviderChange,
}: SearchProviderMenuProps) {
- const CurrentIcon = PROVIDER_ICONS[provider]
-
return (
@@ -55,8 +60,7 @@ export function SearchProviderMenu({
className="search-provider-menu__trigger"
aria-label={`选择搜索方式,当前为 ${PROVIDER_LABELS[provider]}`}
>
-
- {PROVIDER_LABELS[provider]}
+
@@ -67,13 +71,12 @@ export function SearchProviderMenu({
}}
>
{enabledProviders.map((enabledProvider) => {
- const Icon = PROVIDER_ICONS[enabledProvider]
return (
-
+
{PROVIDER_LABELS[enabledProvider]}
)