feat(client): premium UI overhaul — Iris design system + single-instance lock
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) <noreply@anthropic.com>
This commit is contained in:
Generated
+401
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -94,9 +94,7 @@ function UserPill() {
|
||||
>
|
||||
<span
|
||||
className="flex h-5 w-5 items-center justify-center rounded-full text-[10px] font-bold text-white"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(135deg, #B888E5 0%, #6B7CE0 100%)',
|
||||
}}
|
||||
style={{ backgroundImage: 'var(--gradient-brand-wordmark)' }}
|
||||
>
|
||||
{initial}
|
||||
</span>
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className="relative flex h-screen w-screen flex-col bg-[var(--color-background)]"
|
||||
className="relative flex h-screen w-screen flex-col overflow-hidden bg-[var(--color-background)]"
|
||||
data-testid="heicode-login-page"
|
||||
>
|
||||
{/* Chrome strip — drag region + window controls */}
|
||||
{/* ─── Chrome strip ────────────────────────────────────── */}
|
||||
<div
|
||||
className="flex h-9 w-full items-center justify-between border-b border-[var(--color-border)] bg-[var(--color-surface)]"
|
||||
className="relative z-20 flex h-9 w-full items-center justify-between border-b border-[var(--color-border-separator)] bg-[var(--color-surface)]/70 backdrop-blur-md"
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<div className="flex items-center gap-2 px-4" data-tauri-drag-region>
|
||||
<span
|
||||
className="text-[10px] font-bold tracking-[0.22em] uppercase bg-clip-text text-transparent"
|
||||
style={{ backgroundImage: 'linear-gradient(135deg, #B888E5 0%, #6B7CE0 100%)' }}
|
||||
className="bg-clip-text text-[10px] font-bold uppercase tracking-[0.22em] text-transparent"
|
||||
style={{ backgroundImage: 'var(--gradient-brand-wordmark)' }}
|
||||
>
|
||||
Heicode
|
||||
</span>
|
||||
@@ -53,30 +63,64 @@ export function HeicodeLoginPage() {
|
||||
<WindowControls />
|
||||
</div>
|
||||
|
||||
{/* Subtle radial backdrop for premium feel */}
|
||||
{/* ─── Layered backdrop ────────────────────────────────── */}
|
||||
{/* Top violet halo */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0"
|
||||
className="pointer-events-none absolute inset-x-0 top-0 z-0 h-[60vh]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
'radial-gradient(circle at 50% 30%, rgba(123,107,227,0.08), transparent 55%)',
|
||||
'radial-gradient(ellipse 70% 50% at 50% 0%, rgba(123,107,227,0.18), transparent 60%)',
|
||||
}}
|
||||
/>
|
||||
{/* Lower blue glow */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 bottom-0 z-0 h-[40vh]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
'radial-gradient(ellipse 60% 60% at 50% 100%, rgba(107,124,224,0.10), transparent 60%)',
|
||||
}}
|
||||
/>
|
||||
{/* Subtle grain — adds the "real product" tactility */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 z-0 opacity-[0.025]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>\")",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative flex flex-1 flex-col items-center justify-center overflow-auto px-6 py-10">
|
||||
<div className="mb-10 text-center">
|
||||
{/* 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 ─────────────────────────────────────────── */}
|
||||
<div className="relative z-10 flex flex-1 flex-col items-center justify-center overflow-auto px-6 py-10">
|
||||
<div className="mb-10 flex flex-col items-center text-center">
|
||||
{/* App icon — the H glass. Drop-shadow tinted with violet so
|
||||
the icon reads as continuous with the gradient backdrop. */}
|
||||
<img
|
||||
src="/app-icon.png"
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="h-[88px] w-[88px] select-none"
|
||||
style={{
|
||||
filter:
|
||||
'drop-shadow(0 8px 24px rgba(123,107,227,0.30)) drop-shadow(0 2px 4px rgba(0,0,0,0.24))',
|
||||
}}
|
||||
draggable={false}
|
||||
/>
|
||||
|
||||
<h1
|
||||
className="text-5xl font-bold tracking-[0.18em] uppercase bg-clip-text text-transparent"
|
||||
style={{ backgroundImage: 'linear-gradient(135deg, #B888E5 0%, #6B7CE0 100%)' }}
|
||||
className="mt-6 bg-clip-text text-4xl font-bold uppercase tracking-[0.16em] text-transparent"
|
||||
style={{
|
||||
backgroundImage: 'var(--gradient-brand-wordmark)',
|
||||
fontFamily: 'var(--font-headline)',
|
||||
}}
|
||||
>
|
||||
Heicode
|
||||
</h1>
|
||||
|
||||
<p
|
||||
className="mt-5 text-sm text-[var(--color-text-secondary)]"
|
||||
className="mt-4 max-w-sm text-[13px] leading-relaxed text-[var(--color-text-secondary)]"
|
||||
style={{ fontFamily: 'var(--font-headline)' }}
|
||||
>
|
||||
{t('login.tagline')}
|
||||
@@ -90,13 +134,13 @@ export function HeicodeLoginPage() {
|
||||
) : null}
|
||||
|
||||
{hasFetched && providers.length === 0 ? (
|
||||
<div className="rounded-[var(--radius-md)] border border-[var(--color-border)] bg-[var(--color-surface)] p-4 text-sm text-[var(--color-error)]">
|
||||
<div className="max-w-sm rounded-[var(--radius-md)] border border-[var(--color-error)]/30 bg-[var(--color-error-container)] p-4 text-sm text-[var(--color-error)]">
|
||||
{t('login.errors.noProviders')}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{hasFetched && providers.length > 0 ? (
|
||||
<div className="grid w-full max-w-md grid-cols-1 gap-5">
|
||||
<div className="grid w-full max-w-sm grid-cols-1 gap-4">
|
||||
{providers.map((provider) => (
|
||||
<ProviderLoginCard key={provider.id} provider={provider} />
|
||||
))}
|
||||
@@ -104,12 +148,12 @@ export function HeicodeLoginPage() {
|
||||
) : null}
|
||||
|
||||
{error ? (
|
||||
<div className="mt-6 max-w-md rounded-[var(--radius-md)] border border-[var(--color-error)]/30 bg-[var(--color-error-container)] px-4 py-3 text-sm text-[var(--color-error)]">
|
||||
<div className="mt-6 max-w-sm rounded-[var(--radius-md)] border border-[var(--color-error)]/30 bg-[var(--color-error-container)] px-4 py-3 text-xs text-[var(--color-error)]">
|
||||
{error}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<p className="mt-10 max-w-md text-center text-xs leading-relaxed text-[var(--color-text-tertiary)]">
|
||||
<p className="mt-12 max-w-sm text-center text-[11px] leading-relaxed text-[var(--color-text-tertiary)]">
|
||||
{t('login.footer.heicodeProvidesModels')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -80,12 +80,32 @@ export function ProviderLoginCard({ provider }: Props) {
|
||||
const buttonDisabled = phase === 'opening' || phase === 'waiting'
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col gap-5 rounded-[var(--radius-lg)] border border-[var(--color-border)] bg-[var(--color-surface-container)] p-7 shadow-[var(--shadow-dropdown)] backdrop-blur-sm transition-colors duration-200 hover:border-[var(--color-border-hi,rgba(255,255,255,0.14))]">
|
||||
<div
|
||||
className="relative flex flex-col gap-5 rounded-[var(--radius-xl)] border border-[var(--color-border)] p-7 shadow-[var(--shadow-dropdown)] backdrop-blur-md transition-all duration-200 hover:border-[var(--color-border-hi)]"
|
||||
style={{
|
||||
// Card surface is a subtle violet→neutral gradient over the
|
||||
// base container, so the card "lifts" off the backdrop without
|
||||
// a heavy 1px border. Looks closer to Linear's command-palette.
|
||||
backgroundImage:
|
||||
'linear-gradient(180deg, rgba(123,107,227,0.04) 0%, rgba(255,255,255,0) 60%), linear-gradient(180deg, var(--color-surface-container), var(--color-surface-container))',
|
||||
}}
|
||||
>
|
||||
{/* Top hairline accent — barely-there violet line at the very top
|
||||
edge of the card; classic premium app affordance. */}
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-x-0 top-0 h-px rounded-t-[var(--radius-xl)]"
|
||||
style={{
|
||||
backgroundImage:
|
||||
'linear-gradient(90deg, transparent 0%, rgba(123,107,227,0.55) 50%, transparent 100%)',
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="text-center">
|
||||
<p className="text-[10px] font-medium uppercase tracking-[0.2em] text-[var(--color-text-tertiary)]">
|
||||
{t('login.signInTarget')}
|
||||
</p>
|
||||
<p className="mt-1 font-mono text-xs text-[var(--color-text-secondary)] break-all">
|
||||
<p className="mt-1.5 font-mono text-[12px] text-[var(--color-text-secondary)] break-all">
|
||||
{signInHost}
|
||||
</p>
|
||||
</div>
|
||||
@@ -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}
|
||||
</button>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user