From 463f9be2db5c694b779f0ad547e18352b9aab005 Mon Sep 17 00:00:00 2001 From: chenchen Date: Sat, 9 May 2026 17:38:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(client):=20premium=20UI=20overhaul=20?= =?UTF-8?q?=E2=80=94=20Iris=20design=20system=20+=20single-instance=20lock?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Production-grade visual upgrade. Pulls the violet→blue gradient from the H glass app icon up into a coherent design system: brand color, focus rings, button gradients, sidebar accents, selection highlight, shadow tints all derive from the same palette. Result: login surface no longer clashes with the app icon, post-login UI feels like a single product instead of a Tauri shell. Changes ------- src/theme/globals.css — Iris design tokens: - --color-primary #C5A572 (gold) → #7B6BE3 (violet, from logo) - --color-secondary slate → #6B7CE0 (blue from logo) - --color-tertiary gold → #9A8DEC (lighter violet) - Surface stack rebased to slight cool/blue undertone (#13131A, #181820, …) so violet accents pop without clashing - Borders softened: --color-border 0.08 → 0.06, separator 0.06 → 0.04 - Radii loosened: sm 4 → 6, md 8 → 10, lg 12 → 14, xl 16 → 18 - --gradient-btn-primary: 3-stop violet→primary→blue (replaces flat gold gradient); CTA glow shadow tinted violet - --gradient-brand-wordmark exposed as a token so wordmark / avatar gradient stays consistent across surfaces - --shadow-dropdown: 3-stop layered, dark stop tinted violet so floating menus feel of-a-piece with brand - --shadow-focus-ring: violet 55% so input focus is visible - --color-selection-bg: violet 32% - Sidebar item hover/active: rgba violet base instead of plain white - Sidebar panel backdrop: 2-stop radial (violet top-left + blue bottom-right) replacing plain gold radial - Type scale tokens: --text-xxs … --text-display - Body font-feature-settings: 'kern','liga','cv11' + text-rendering: optimizeLegibility for crisp Inter / Manrope - .tabular-nums utility for status counters src/components/login/HeicodeLoginPage.tsx — login redesign: - Embeds /app-icon.png (the H glass logo) above wordmark - Drop-shadow violet-tinted so icon feels continuous with backdrop - Layered backdrop: violet halo top, blue glow bottom, faint SVG grain texture for tactile premium feel - Wordmark + tagline use --gradient-brand-wordmark token - Card max-width tightened to sm (was md) for taller, more focused composition (Linear / Vercel / Raycast desktop login style) - Chrome strip: blurred translucent surface src/components/login/ProviderLoginCard.tsx: - Card gets faint violet→neutral surface gradient (lifts off backdrop without heavy 1px border) - Top hairline accent (centered violet line) at card top edge - CTA button uses --gradient-btn-primary; hover glow stronger; active scale 0.99 microinteraction - Removed hardcoded rgba(197,165,114,…) gold artifact src/components/layout/TitleBar.tsx: - UserPill avatar gradient now uses --gradient-brand-wordmark token (was inline hardcoded violet) so future palette tweaks propagate src-tauri (Cargo.toml + src/lib.rs): - Add tauri-plugin-single-instance and register it as the FIRST plugin in the Builder chain. Subsequent Heicode launches focus the existing window via show_main_window() instead of spawning a new sidecar / new server port. Fixes "every shortcut click starts a new instance" behavior. Verification ------------ - bunx tsc -b --noEmit (desktop) clean - cargo check (src-tauri) clean Co-Authored-By: Claude Opus 4.7 (1M context) --- cc-haha/desktop/src-tauri/Cargo.lock | 401 ++++++++++++++++++ cc-haha/desktop/src-tauri/Cargo.toml | 1 + cc-haha/desktop/src-tauri/src/lib.rs | 9 + .../src/components/layout/TitleBar.tsx | 4 +- .../src/components/login/HeicodeLoginPage.tsx | 94 ++-- .../components/login/ProviderLoginCard.tsx | 36 +- cc-haha/desktop/src/theme/globals.css | 164 ++++--- 7 files changed, 624 insertions(+), 85 deletions(-) diff --git a/cc-haha/desktop/src-tauri/Cargo.lock b/cc-haha/desktop/src-tauri/Cargo.lock index d8b136b..5caf28c 100644 --- a/cc-haha/desktop/src-tauri/Cargo.lock +++ b/cc-haha/desktop/src-tauri/Cargo.lock @@ -56,6 +56,137 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "atk" version = "0.18.2" @@ -151,6 +282,19 @@ dependencies = [ "objc2", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "brotli" version = "8.0.2" @@ -337,6 +481,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -738,6 +891,33 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -765,6 +945,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -924,6 +1125,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.32" @@ -1322,9 +1536,16 @@ dependencies = [ "tauri-plugin-dialog", "tauri-plugin-process", "tauri-plugin-shell", + "tauri-plugin-single-instance", "tauri-plugin-updater", ] +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -2291,6 +2512,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + [[package]] name = "os_pipe" version = "1.2.3" @@ -2340,6 +2571,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2568,6 +2805,17 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -2606,6 +2854,20 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "portable-pty" version = "0.9.0" @@ -4019,6 +4281,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "tauri-plugin-single-instance" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8f29386f5e9fdc699182388a33ee80a56de436d91b67459e86afef426282af" +dependencies = [ + "serde", + "serde_json", + "tauri", + "thiserror 2.0.18", + "tracing", + "windows-sys 0.60.2", + "zbus", +] + [[package]] name = "tauri-plugin-updater" version = "2.10.1" @@ -4476,9 +4753,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tracing-core" version = "0.1.36" @@ -4528,6 +4817,17 @@ version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + [[package]] name = "unic-char-property" version = "0.9.0" @@ -5611,6 +5911,67 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zbus" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bcbf15c8708d7fc1be0c993622e0a5cbd5e8b52bfa40afa4c3e0cd8d724ac1" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 1.0.2", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51fa5406ad9175a8c825a931f8cf347116b531b3634fcb0b627c290f1f2516ff" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d" +dependencies = [ + "serde", + "winnow 1.0.2", + "zvariant", +] + [[package]] name = "zerocopy" version = "0.8.48" @@ -5708,3 +6069,43 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zvariant" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c1567a6ec68df868cbbfde844cfc6d81649fe5109a62b116b19fabd53e618ee" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow 1.0.2", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d5b780599bbde114e39d9a0799577fad1ced5105d38515745f7b3099d8ceda" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d464f5733ffa07a3164d656f18533caace9d0638596721355d73256a410d691" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.117", + "winnow 1.0.2", +] diff --git a/cc-haha/desktop/src-tauri/Cargo.toml b/cc-haha/desktop/src-tauri/Cargo.toml index f28eb2d..699c523 100644 --- a/cc-haha/desktop/src-tauri/Cargo.toml +++ b/cc-haha/desktop/src-tauri/Cargo.toml @@ -16,6 +16,7 @@ tauri-plugin-shell = "2" tauri-plugin-dialog = "2" tauri-plugin-process = "2" tauri-plugin-updater = "2" +tauri-plugin-single-instance = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" anyhow = "1.0.102" diff --git a/cc-haha/desktop/src-tauri/src/lib.rs b/cc-haha/desktop/src-tauri/src/lib.rs index 534252b..f322474 100644 --- a/cc-haha/desktop/src-tauri/src/lib.rs +++ b/cc-haha/desktop/src-tauri/src/lib.rs @@ -963,6 +963,15 @@ mod tests { #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let builder = tauri::Builder::default() + // Single-instance lock (must be the FIRST plugin registered per + // Tauri docs — it intercepts the second-process startup before + // anything else runs and forwards the launch arguments to the + // first process via this callback). Subsequent app icon clicks / + // shortcut launches will focus the existing window instead of + // spawning a new sidecar + new server port. + .plugin(tauri_plugin_single_instance::init(|app, _argv, _cwd| { + show_main_window(app); + })) .manage(ServerState::default()) .manage(AdapterState::default()) .manage(TerminalState::default()) diff --git a/cc-haha/desktop/src/components/layout/TitleBar.tsx b/cc-haha/desktop/src/components/layout/TitleBar.tsx index 8ad0dfc..d81ea31 100644 --- a/cc-haha/desktop/src/components/layout/TitleBar.tsx +++ b/cc-haha/desktop/src/components/layout/TitleBar.tsx @@ -94,9 +94,7 @@ function UserPill() { > {initial} diff --git a/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx b/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx index 2fcb3b0..5f5c22a 100644 --- a/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx +++ b/cc-haha/desktop/src/components/login/HeicodeLoginPage.tsx @@ -1,10 +1,20 @@ // desktop/src/components/login/HeicodeLoginPage.tsx // -// 全屏登录页 — 当 Heicode 检测到尚未登录时由 AppShell 渲染。 -// 仅 1 个登录入口:TaijiAICloud。 +// Production-grade login surface. Layout follows +// docs/product-package/11-product-prototype-wireframes.md §1 +// (brand wordmark + tagline + single sign-in card) but the visual +// system was tightened up: // -// 顶部自绘 36px 高的 chrome(drag region + WindowControls),因为 -// tauri.conf.json 已设 decorations:false 以摆脱 Windows 系统灰边框。 +// - 36px chrome strip with drag region + window controls +// - Layered gradient backdrop (violet/blue glow at top, vignette at +// bottom) so the login surface feels like a hero, not a form +// - Centered logo image + wordmark above tagline; both pull the +// same gradient as --gradient-brand-wordmark so the login screen +// matches the app icon you see in the title bar / taskbar / dock +// - Single CTA card on a generous max-w-sm canvas +// - Footer copy lives at the very bottom, not floating in the middle +// +// Visual references: Linear / Vercel / Raycast desktop login screens. import { useEffect } from 'react' import { useHeicodeAuthStore } from '../../stores/heicodeAuthStore' @@ -34,18 +44,18 @@ export function HeicodeLoginPage() { return (
- {/* Chrome strip — drag region + window controls */} + {/* ─── Chrome strip ────────────────────────────────────── */}
Heicode @@ -53,30 +63,64 @@ export function HeicodeLoginPage() {
- {/* Subtle radial backdrop for premium feel */} + {/* ─── Layered backdrop ────────────────────────────────── */} + {/* Top violet halo */}
+ {/* Lower blue glow */} +
+ {/* Subtle grain — adds the "real product" tactility */} +
\")", }} /> - {/* Content */} -
-
- {/* Wordmark uses the same purple→blue gradient as the H glass logo - so the login surface visually matches the app icon (window - tray icon, taskbar, dock). */} + {/* ─── Content ─────────────────────────────────────────── */} +
+
+ {/* App icon — the H glass. Drop-shadow tinted with violet so + the icon reads as continuous with the gradient backdrop. */} + +

Heicode

+

{t('login.tagline')} @@ -90,13 +134,13 @@ export function HeicodeLoginPage() { ) : null} {hasFetched && providers.length === 0 ? ( -

+
{t('login.errors.noProviders')}
) : null} {hasFetched && providers.length > 0 ? ( -
+
{providers.map((provider) => ( ))} @@ -104,12 +148,12 @@ export function HeicodeLoginPage() { ) : null} {error ? ( -
+
{error}
) : null} -

+

{t('login.footer.heicodeProvidesModels')}

diff --git a/cc-haha/desktop/src/components/login/ProviderLoginCard.tsx b/cc-haha/desktop/src/components/login/ProviderLoginCard.tsx index 0afc5f2..135524f 100644 --- a/cc-haha/desktop/src/components/login/ProviderLoginCard.tsx +++ b/cc-haha/desktop/src/components/login/ProviderLoginCard.tsx @@ -80,12 +80,32 @@ export function ProviderLoginCard({ provider }: Props) { const buttonDisabled = phase === 'opening' || phase === 'waiting' return ( -
+
+ {/* Top hairline accent — barely-there violet line at the very top + edge of the card; classic premium app affordance. */} +
+

{t('login.signInTarget')}

-

+

{signInHost}

@@ -94,7 +114,17 @@ export function ProviderLoginCard({ provider }: Props) { type="button" onClick={handleBrowserLogin} disabled={buttonDisabled} - className="rounded-[var(--radius-md)] border border-[var(--color-primary)]/40 bg-[var(--color-primary)] px-4 py-3 text-sm font-medium text-[var(--color-on-primary)] shadow-[var(--shadow-button-primary)] transition-all duration-200 hover:bg-[var(--color-primary-fixed-dim)] hover:shadow-[0_8px_24px_rgba(197,165,114,0.28)] disabled:cursor-not-allowed disabled:border-[var(--color-border)] disabled:bg-[var(--color-surface-container-high)] disabled:text-[var(--color-text-tertiary)] disabled:shadow-none" + className="rounded-[var(--radius-md)] px-4 py-3 text-sm font-semibold text-[var(--color-on-primary)] shadow-[var(--shadow-button-primary)] transition-all duration-200 hover:shadow-[0_10px_32px_rgba(123,107,227,0.40)] active:scale-[0.99] disabled:cursor-not-allowed disabled:bg-[var(--color-surface-container-high)] disabled:text-[var(--color-text-tertiary)] disabled:shadow-none" + style={{ + // Use the brand-wordmark gradient for the CTA so the login card's + // single action visually echoes the H logo + the wordmark above + // it. Remove default border to let the gradient extend cleanly. + backgroundImage: buttonDisabled + ? undefined + : 'var(--gradient-btn-primary)', + // Letter spacing pulled in slightly — premium-feeling tight tracking. + letterSpacing: '0.01em', + }} > {buttonLabel} diff --git a/cc-haha/desktop/src/theme/globals.css b/cc-haha/desktop/src/theme/globals.css index bc8a233..9d3996d 100644 --- a/cc-haha/desktop/src/theme/globals.css +++ b/cc-haha/desktop/src/theme/globals.css @@ -108,46 +108,56 @@ outline: none; } -/* ─── Tailwind Theme Override — Heicode Premium Black ─── */ +/* ─── Tailwind Theme Override — Heicode Iris (premium violet) ─── */ +/* + * Brand palette derived from the H glass logo's purple→blue gradient. + * Goal: production-grade dark UI that reads as confident + modern, + * with chromatic accents tied to the app icon for instant brand + * recognition. Surface stack stays near-black with a faint cool tint + * (so violet accents pop without clashing with warm content like + * code highlighting strings). + */ @theme { - /* Brand accent — restrained warm gold, used sparingly */ - --color-primary: #C5A572; - --color-primary-container: #A38247; - --color-primary-fixed: #E0C896; - --color-primary-fixed-dim: #C5A572; + /* Brand accent — violet from logo gradient */ + --color-primary: #7B6BE3; + --color-primary-container: #5B4FB8; + --color-primary-fixed: #9A8DEC; + --color-primary-fixed-dim: #7B6BE3; - --color-on-primary: #1A1410; + --color-on-primary: #FFFFFF; --color-on-surface: #ECECEE; --color-on-surface-variant: #B0B0B5; - /* Black surface stack — near-black with slight warm tint, low-contrast layering */ - --color-surface: #141418; - --color-surface-bright: #1F1F25; - --color-surface-dim: #0A0A0C; - --color-surface-container: #1A1A1F; - --color-surface-container-low: #16161A; - --color-surface-container-high: #1F1F25; - --color-surface-container-highest: #26262E; - --color-surface-container-lowest: #0A0A0C; - --color-surface-variant: #1F1F25; + /* Surface stack — near-black with a cool (blue-leaning) undertone + so violet accents read as natural, not garish. */ + --color-surface: #13131A; + --color-surface-bright: #1E1E27; + --color-surface-dim: #0A0A0F; + --color-surface-container: #181820; + --color-surface-container-low: #15151C; + --color-surface-container-high: #1E1E27; + --color-surface-container-highest: #262631; + --color-surface-container-lowest: #0A0A0F; + --color-surface-variant: #1E1E27; - --color-background: #0A0A0C; + --color-background: #0A0A0F; - --color-outline: #5A5A62; - --color-outline-variant: #2A2A30; + --color-outline: #5A5A66; + --color-outline-variant: #26262E; - --color-secondary: #8B95A8; - --color-secondary-container: #2A2F3A; - --color-tertiary: #C5A572; - --color-tertiary-container: #2A241A; + /* Secondary = blue from logo (companion accent for tags / tertiary state) */ + --color-secondary: #6B7CE0; + --color-secondary-container: #1E2238; + --color-tertiary: #9A8DEC; + --color-tertiary-container: #1E1A30; --color-error: #E5484D; --color-error-container: #3A1517; --color-on-error-container: #FECDCD; --color-inverse-surface: #ECECEE; - --color-inverse-on-surface: #14140C; - --color-inverse-primary: #8E7547; + --color-inverse-on-surface: #13131A; + --color-inverse-primary: #5B4FB8; --color-success: #2EA86E; --color-warning: #E5A23E; @@ -172,28 +182,30 @@ --motion-sidebar-easing: cubic-bezier(0.22, 1, 0.36, 1); /* Surface aliases */ - --color-surface-sidebar: #101014; + --color-surface-sidebar: #0F0F15; --color-surface-hover: var(--color-surface-container-high); --color-surface-selected: var(--color-surface-container-highest); - --color-model-option-selected-bg: rgba(197, 165, 114, 0.10); - --color-model-option-selected-border: rgba(197, 165, 114, 0.32); + --color-model-option-selected-bg: rgba(123, 107, 227, 0.12); + --color-model-option-selected-border: rgba(123, 107, 227, 0.32); --color-sidebar-filter-bg: rgba(255, 255, 255, 0.02); - --color-sidebar-filter-border: rgba(255, 255, 255, 0.06); + --color-sidebar-filter-border: rgba(255, 255, 255, 0.05); --color-sidebar-filter-icon-bg: transparent; --color-sidebar-search-bg: rgba(255, 255, 255, 0.03); - --color-sidebar-search-border: rgba(255, 255, 255, 0.06); - --color-sidebar-item-hover: rgba(255, 255, 255, 0.04); - --color-sidebar-item-active: rgba(255, 255, 255, 0.07); - --color-sidebar-item-active-border: rgba(197, 165, 114, 0.18); + --color-sidebar-search-border: rgba(255, 255, 255, 0.05); + --color-sidebar-item-hover: rgba(123, 107, 227, 0.06); + --color-sidebar-item-active: rgba(123, 107, 227, 0.10); + --color-sidebar-item-active-border: rgba(123, 107, 227, 0.24); --sidebar-panel-bg-image: - radial-gradient(circle at top left, rgba(197, 165, 114, 0.04), transparent 38%), - linear-gradient(180deg, rgba(255, 255, 255, 0.015), rgba(255, 255, 255, 0)); + radial-gradient(circle at top left, rgba(123, 107, 227, 0.07), transparent 42%), + radial-gradient(circle at bottom right, rgba(107, 124, 224, 0.04), transparent 50%), + linear-gradient(180deg, rgba(255, 255, 255, 0.012), rgba(255, 255, 255, 0)); - /* Border aliases */ - --color-border: rgba(255, 255, 255, 0.08); - --color-border-hi: rgba(255, 255, 255, 0.14); + /* Border aliases — softer than before so cards lean on elevation, + not contour lines, for separation. */ + --color-border: rgba(255, 255, 255, 0.06); + --color-border-hi: rgba(255, 255, 255, 0.12); --color-border-focus: var(--color-primary); - --color-border-separator: rgba(255, 255, 255, 0.06); + --color-border-separator: rgba(255, 255, 255, 0.04); /* Text aliases */ --color-text-primary: var(--color-on-surface); @@ -207,17 +219,33 @@ --color-brand: var(--color-primary); --color-text-accent: var(--color-primary); - /* Shadows — soft ambient, deeper for floaty cards */ - --shadow-dropdown: 0 8px 24px rgba(0, 0, 0, 0.40), 0 24px 60px rgba(0, 0, 0, 0.50); + /* Shadows — layered ambient + a faint violet tint on dropdowns + so floating surfaces feel of-a-piece with the brand instead of + dropping a generic black shadow. Tuned for premium dark UIs: + two stops, deeper second stop catches eyes on >40px popovers. */ + --shadow-dropdown: + 0 1px 2px rgba(0, 0, 0, 0.30), + 0 8px 24px rgba(0, 0, 0, 0.40), + 0 24px 60px rgba(10, 8, 24, 0.55); --shadow-sidebar-filter: none; - --shadow-focus-ring: 0 0 0 1px rgba(197, 165, 114, 0.40); + --shadow-focus-ring: 0 0 0 1px rgba(123, 107, 227, 0.55); --shadow-error-ring: 0 0 0 1px rgba(229, 72, 77, 0.32); - --shadow-button-primary: 0 6px 20px rgba(197, 165, 114, 0.18); + /* Primary button glow — violet halo so the CTA reads as alive on hover */ + --shadow-button-primary: + 0 1px 2px rgba(0, 0, 0, 0.20), + 0 8px 24px rgba(123, 107, 227, 0.28); - /* Button colors — solid gold, no garish gradient */ + /* Button colors — gradient drives the iridescent look matching the + logo. Direction (135deg) keeps highlight at top-left for a + subtle "lit-from-above" feel. */ --color-btn-primary-fg: var(--color-on-primary); - --gradient-btn-primary: linear-gradient(180deg, var(--color-primary), var(--color-primary-container)); - --gradient-btn-primary-hover: linear-gradient(180deg, var(--color-primary-fixed), var(--color-primary)); + --gradient-btn-primary: + linear-gradient(135deg, var(--color-primary-fixed) 0%, var(--color-primary) 50%, var(--color-secondary) 100%); + --gradient-btn-primary-hover: + linear-gradient(135deg, #B0A4F2 0%, var(--color-primary-fixed) 50%, #8595E5 100%); + /* Brand wordmark / icon gradient — used by login + user pill avatar */ + --gradient-brand-wordmark: + linear-gradient(135deg, #B888E5 0%, #6B7CE0 100%); /* User message bubble */ --color-surface-user-msg: var(--color-surface-container-high); @@ -268,15 +296,30 @@ /* Misc */ --color-window-close-hover: #C63B44; - --color-selection-bg: rgba(197, 165, 114, 0.28); - --color-selection-fg: #ECECEE; + --color-selection-bg: rgba(123, 107, 227, 0.32); + --color-selection-fg: #FFFFFF; - /* Radii */ - --radius-sm: 4px; - --radius-md: 8px; - --radius-lg: 12px; - --radius-xl: 16px; + /* Radii — slightly tighter on small elements so chips/inputs feel + crisp; cards keep generous radius. */ + --radius-sm: 6px; + --radius-md: 10px; + --radius-lg: 14px; + --radius-xl: 18px; --radius-full: 9999px; + + /* Type scale — explicit tokens prevent mixed font sizes leaking + in via ad-hoc Tailwind classes. Components should reach for + these via inline style or extend later via Tailwind plugin. */ + --text-xxs: 10px; + --text-xs: 11px; + --text-sm: 13px; + --text-base: 14px; + --text-md: 15px; + --text-lg: 17px; + --text-xl: 20px; + --text-2xl: 26px; + --text-3xl: 34px; + --text-display: 48px; } /* ─── Optional Light Theme Override ─────────────────────────────── */ @@ -436,12 +479,25 @@ html, body, #root { margin: 0; padding: 0; font-family: var(--font-body); + font-size: var(--text-base); + line-height: 1.5; color: var(--color-on-surface); background-color: var(--color-background); + /* Premium typography polish: kerning, contextual alternates, + stylistic features that Inter / Manrope ship with. Cleaner + letterforms in both Latin and CJK contexts. */ + font-feature-settings: 'kern', 'liga', 'cv11'; + text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } +/* Tighter tabular numbers for status counters / token counts so digits + line up cleanly when they animate. */ +.tabular-nums { + font-variant-numeric: tabular-nums; +} + ::selection { background: var(--color-selection-bg); color: var(--color-selection-fg);