diff --git a/src/web-ui/src/app/components/GalleryLayout/GalleryLayout.scss b/src/web-ui/src/app/components/GalleryLayout/GalleryLayout.scss index 91d4727a..b669cdfb 100644 --- a/src/web-ui/src/app/components/GalleryLayout/GalleryLayout.scss +++ b/src/web-ui/src/app/components/GalleryLayout/GalleryLayout.scss @@ -1,4 +1,5 @@ @use '../../../component-library/styles/tokens' as *; +@use '../../../component-library/styles/btn-primary-tokens.scss' as btn-primary; $gutter: clamp(40px, 6vw, 80px); $content-max: 1480px; @@ -271,12 +272,14 @@ $content-max: 1480px; font-weight: $font-weight-medium; &--primary { - color: var(--color-text-primary); - background: color-mix(in srgb, var(--color-accent-500) 10%, var(--element-bg-soft)); + @include btn-primary.btn-primary-surface-default; &:hover:not(:disabled) { - background: color-mix(in srgb, var(--color-accent-500) 16%, var(--element-bg-medium)); - color: var(--color-text-primary); + @include btn-primary.btn-primary-surface-hover; + } + + &:active:not(:disabled) { + @include btn-primary.btn-primary-surface-active; } } } diff --git a/src/web-ui/src/app/components/NavPanel/MainNav.tsx b/src/web-ui/src/app/components/NavPanel/MainNav.tsx index f6c85d01..b2c801c9 100644 --- a/src/web-ui/src/app/components/NavPanel/MainNav.tsx +++ b/src/web-ui/src/app/components/NavPanel/MainNav.tsx @@ -411,11 +411,14 @@ const MainNav: React.FC = ({ onClick={() => setSearchOpen(true)} aria-label={t('nav.search.triggerTooltip')} > - + {t('nav.search.triggerPlaceholder')} - ⌘K setSearchOpen(false)} /> diff --git a/src/web-ui/src/app/components/NavPanel/NavPanel.scss b/src/web-ui/src/app/components/NavPanel/NavPanel.scss index 774d6087..0b366f4c 100644 --- a/src/web-ui/src/app/components/NavPanel/NavPanel.scss +++ b/src/web-ui/src/app/components/NavPanel/NavPanel.scss @@ -7,6 +7,7 @@ */ @use '../../../component-library/styles/tokens.scss' as *; +@use '../../../component-library/styles/btn-primary-tokens.scss' as btn-primary; $_nav-width: 240px; // Scene-inner fade: used when switching between different SceneNav components @@ -353,7 +354,7 @@ $_section-header-height: 24px; border: none; border-radius: $size-radius-base 0 0 $size-radius-base; background: var(--element-bg-soft); - color: var(--color-primary); + color: var(--color-text-primary); font-size: 12px; font-weight: 600; cursor: pointer; @@ -417,7 +418,7 @@ $_section-header-height: 24px; border: none; border-radius: 0 $size-radius-base $size-radius-base 0; background: var(--element-bg-soft); - color: var(--color-text-muted); + color: var(--color-text-primary); cursor: pointer; transition: background $motion-fast $easing-standard, color $motion-fast $easing-standard, @@ -430,7 +431,7 @@ $_section-header-height: 24px; &.is-active { background: var(--element-bg-medium); - color: var(--color-primary); + color: var(--color-text-primary); } &:active { transform: translateY(1px); } @@ -490,12 +491,9 @@ $_section-header-height: 24px; &__mode-dropdown-item-icon { flex-shrink: 0; - &.is-code { - color: color-mix(in srgb, var(--color-primary) 75%, var(--color-text-muted)); - } - + &.is-code, &.is-cowork { - color: color-mix(in srgb, var(--color-accent-500) 75%, var(--color-text-muted)); + color: color-mix(in srgb, var(--color-text-secondary) 82%, var(--color-text-muted)); } } @@ -508,7 +506,7 @@ $_section-header-height: 24px; &__mode-dropdown-item-check { flex-shrink: 0; - color: var(--color-primary); + color: var(--color-text-primary); margin-left: auto; } @@ -579,10 +577,10 @@ $_section-header-height: 24px; &__section-label { font-size: 11px; - font-weight: 700; - letter-spacing: 0.06em; + font-weight: 600; + letter-spacing: 0.05em; text-transform: uppercase; - color: var(--color-text-primary); + color: var(--color-text-muted); flex: 1; white-space: nowrap; overflow: hidden; @@ -680,7 +678,7 @@ $_section-header-height: 24px; border: none; border-radius: 10px; background: transparent; - color: var(--color-text-primary); + color: var(--color-text-secondary); cursor: pointer; text-align: left; transition: background $motion-fast $easing-standard, @@ -766,28 +764,45 @@ $_section-header-height: 24px; box-shadow: none; overflow: hidden; transition: transform $motion-fast $easing-standard, - box-shadow $motion-fast $easing-standard; + box-shadow $motion-fast $easing-standard, + filter $motion-fast $easing-standard, + opacity $motion-fast $easing-standard; & + & { margin-left: -8px; } + &:not(.is-active):not(.bitfun-nav-panel__miniapp-bubble--more) { + filter: saturate(0.72); + opacity: 0.9; + } + &:hover { transform: translateY(-2px) scale(1.06); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.24); + filter: saturate(1); + opacity: 1; } &.is-active { + filter: saturate(1); + opacity: 1; box-shadow: 0 8px 18px rgba(0, 0, 0, 0.22), 0 0 0 3px color-mix(in srgb, var(--color-primary) 28%, transparent); } &--more { background: color-mix(in srgb, var(--element-bg-medium) 92%, transparent); - color: var(--color-text-primary); + color: var(--color-text-secondary); font-size: 10px; font-weight: 700; letter-spacing: 0.01em; + filter: none; + opacity: 1; + + &:hover { + color: var(--color-text-primary); + } } } @@ -826,7 +841,7 @@ $_section-header-height: 24px; border: none; border-radius: 4px; background: transparent; - color: var(--color-text-muted); + color: var(--color-text-primary); cursor: pointer; transition: color $motion-fast $easing-standard, background $motion-fast $easing-standard; @@ -1148,12 +1163,12 @@ $_section-header-height: 24px; transform: translateY(-50%); width: 2px; height: 16px; - background: var(--color-primary); + background: var(--btn-primary-bg, var(--color-text-primary)); border-radius: 0 2px 2px 0; } .bitfun-nav-panel__item-icon { - color: var(--color-primary); + color: var(--color-text-primary); opacity: 1; } } @@ -1244,13 +1259,13 @@ $_section-header-height: 24px; height: 22px; margin-left: auto; border-radius: $size-radius-sm; - color: var(--color-text-muted); + color: var(--color-text-primary); cursor: pointer; transition: color $motion-fast $easing-standard, background $motion-fast $easing-standard; &:hover { - color: var(--color-primary); + color: var(--color-text-secondary); background: var(--element-bg-soft); } @@ -1405,6 +1420,13 @@ $_section-header-height: 24px; color: #ca8a04; } +.bitfun-nav-panel__footer .bitfun-notification-btn.bitfun-notification-btn--nav-hover-icon:not(.bitfun-notification-btn--has-progress) { + width: 28px; + height: 28px; + padding: 0; + justify-content: center; +} + .bitfun-nav-panel__footer-btn--icon { display: flex; align-items: center; @@ -1436,6 +1458,48 @@ $_section-header-height: 24px; } } +// Footer icon buttons: default icon fades to alternate on hover / focus-visible +.bitfun-nav-panel__footer-btn-icon-swap { + position: relative; + width: 15px; + height: 15px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + + > svg { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + transition: opacity $motion-fast $easing-standard; + } +} + +.bitfun-nav-panel__footer-btn-icon-swap-default { + opacity: 1; +} + +.bitfun-nav-panel__footer-btn-icon-swap-hover { + opacity: 0; +} + +// is-hover-open: multimodal submenu is open but pointer may be over the menu, not the button — keep “hover” icon +.bitfun-nav-panel__footer-btn--icon:hover .bitfun-nav-panel__footer-btn-icon-swap, +.bitfun-nav-panel__footer-btn--icon:focus-visible .bitfun-nav-panel__footer-btn-icon-swap, +.bitfun-nav-panel__footer-btn--icon.is-hover-open .bitfun-nav-panel__footer-btn-icon-swap, +.bitfun-nav-panel__footer .bitfun-notification-btn--nav-hover-icon:hover .bitfun-nav-panel__footer-btn-icon-swap, +.bitfun-nav-panel__footer .bitfun-notification-btn--nav-hover-icon:focus-visible .bitfun-nav-panel__footer-btn-icon-swap { + .bitfun-nav-panel__footer-btn-icon-swap-default { + opacity: 0; + } + + .bitfun-nav-panel__footer-btn-icon-swap-hover { + opacity: 1; + } +} + // ────────────────────────────────────────────── // More-options popup menu // ────────────────────────────────────────────── @@ -1527,7 +1591,7 @@ $_section-header-height: 24px; background: var(--element-bg-soft); .bitfun-nav-panel__footer-multimodal-item-icon { - color: var(--color-primary); + color: var(--color-text-primary); opacity: 1; } } @@ -1761,7 +1825,7 @@ $_section-header-height: 24px; .bitfun-nav-panel__brand-header { display: flex; flex-direction: column; - padding: $size-gap-2 $size-gap-2 $size-gap-1; + padding: 0 $size-gap-2 $size-gap-1; flex-shrink: 0; } @@ -1886,7 +1950,7 @@ $_section-header-height: 24px; &.is-active { background: var(--element-bg-soft); - color: var(--color-primary); + color: var(--color-text-primary); } &--sub { @@ -1945,17 +2009,20 @@ $_section-header-height: 24px; align-items: center; justify-content: center; border-radius: 50%; - background: var(--element-bg-medium); - color: var(--color-text-muted); + @include btn-primary.btn-primary-surface-default; transform: scale(1); transform-origin: center; transition: background $motion-fast $easing-standard, color $motion-fast $easing-standard, + box-shadow $motion-fast $easing-standard, transform $motion-fast $easing-standard; .bitfun-nav-panel__top-action-btn:hover & { - background: var(--element-bg-strong); - color: var(--color-primary); + @include btn-primary.btn-primary-surface-hover; transform: scale(1.07); } + + .bitfun-nav-panel__top-action-btn:active & { + @include btn-primary.btn-primary-surface-active; + } } diff --git a/src/web-ui/src/app/components/NavPanel/NavSearchDialog.scss b/src/web-ui/src/app/components/NavPanel/NavSearchDialog.scss index d6954e2f..5d44e447 100644 --- a/src/web-ui/src/app/components/NavPanel/NavSearchDialog.scss +++ b/src/web-ui/src/app/components/NavPanel/NavSearchDialog.scss @@ -1,73 +1,89 @@ @use '../../../component-library/styles/tokens.scss' as *; -// ── Overlay ────────────────────────────────────────────────────────────────── - .bitfun-nav-search-dialog__overlay { position: fixed; inset: 0; z-index: 600; - background: rgba(0, 0, 0, 0.35); + background: transparent; display: flex; align-items: flex-start; justify-content: center; padding-top: 80px; - animation: bitfun-nav-search-overlay-in 0.12s ease; } -@keyframes bitfun-nav-search-overlay-in { - from { opacity: 0; } - to { opacity: 1; } -} - -// ── Card ───────────────────────────────────────────────────────────────────── +// ── Card — matches .bitfun-scene-viewport surface (--color-bg-scene) .bitfun-nav-search-dialog__card { width: 520px; max-width: calc(100vw - 32px); - background: var(--color-bg-elevated); - border-radius: $size-radius-lg; + background: var(--color-bg-scene); + border-radius: $size-radius-base; + border: 1px solid var(--border-subtle); box-shadow: - 0 8px 32px rgba(0, 0, 0, 0.25), - 0 2px 8px rgba(0, 0, 0, 0.15), - inset 0 0 0 1px var(--border-subtle); + 0 1px 2px rgba(15, 23, 42, 0.05), + 0 12px 32px -10px rgba(15, 23, 42, 0.14); overflow: hidden; display: flex; flex-direction: column; max-height: calc(100vh - 120px); - animation: bitfun-nav-search-card-in 0.14s ease; + animation: bitfun-nav-search-card-in 0.28s cubic-bezier(0.22, 1, 0.36, 1) both; +} + +:root[data-theme-type='light'] .bitfun-nav-search-dialog__card { + border-color: var(--border-medium); +} + +:root[data-theme-type='dark'] .bitfun-nav-search-dialog__card { + box-shadow: + 0 1px 0 color-mix(in srgb, #fff 4%, transparent), + 0 14px 40px -12px rgba(0, 0, 0, 0.55); } @keyframes bitfun-nav-search-card-in { - from { opacity: 0; transform: translateY(-8px) scale(0.98); } - to { opacity: 1; transform: translateY(0) scale(1); } + from { + opacity: 0; + transform: translateY(-7px) scale(0.988); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } } // ── Input row ──────────────────────────────────────────────────────────────── .bitfun-nav-search-dialog__input-row { - padding: $size-gap-3; + padding: $size-gap-2; border-bottom: 1px solid var(--border-subtle); flex-shrink: 0; + background: var(--color-bg-scene); } -// Search component — capsule style matching the nav trigger +// Search component — capsule matches .bitfun-nav-panel__search-trigger .bitfun-nav-search-dialog__search { width: 100%; .search__wrapper { height: 32px; + min-height: 32px; + padding: 0 $size-gap-2; + gap: $size-gap-2; + border: none; border-radius: $size-radius-full; - background: var(--element-bg-soft); + background: var(--color-bg-scene); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-subtle) 65%, transparent); + transition: + background 0.2s ease, + box-shadow 0.2s ease; &:hover { - background: var(--element-bg-medium); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-medium) 50%, transparent); + background: color-mix(in srgb, var(--color-bg-scene) 88%, var(--element-bg-medium)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-medium) 55%, transparent); } &:focus-within { - background: var(--element-bg-medium); - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-medium) 65%, transparent); + background: color-mix(in srgb, var(--color-bg-scene) 82%, var(--element-bg-medium)); + box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-medium) 68%, transparent); } } @@ -84,32 +100,63 @@ } } +@keyframes bitfun-nav-search-fade-rise { + from { + opacity: 0; + transform: translateY(5px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + // ── Results ────────────────────────────────────────────────────────────────── .bitfun-nav-search-dialog__results { overflow-y: auto; flex: 1; - padding: $size-gap-1 0; + padding: $size-gap-1 $size-gap-2 $size-gap-2; &::-webkit-scrollbar { - width: 4px; + width: 3px; } + &::-webkit-scrollbar-thumb { - background: var(--border-medium); + background: var(--border-subtle); border-radius: 2px; } + + &::-webkit-scrollbar-track { + background: transparent; + } } .bitfun-nav-search-dialog__empty { - padding: $size-gap-4; + padding: $size-gap-4 $size-gap-2; text-align: center; color: var(--color-text-muted); font-size: $font-size-sm; + animation: bitfun-nav-search-fade-rise 0.32s cubic-bezier(0.22, 1, 0.36, 1) 0.06s both; } // ── Groups ─────────────────────────────────────────────────────────────────── .bitfun-nav-search-dialog__group { + animation: bitfun-nav-search-fade-rise 0.34s cubic-bezier(0.22, 1, 0.36, 1) both; + + &:nth-of-type(1) { + animation-delay: 0.04s; + } + + &:nth-of-type(2) { + animation-delay: 0.08s; + } + + &:nth-of-type(3) { + animation-delay: 0.12s; + } + & + & { margin-top: $size-gap-1; border-top: 1px solid var(--border-subtle); @@ -118,13 +165,14 @@ } .bitfun-nav-search-dialog__group-label { - padding: $size-gap-1 $size-gap-3; - font-size: $font-size-xs; - font-weight: 600; + padding: $size-gap-1 $size-gap-1 $size-gap-1 2px; + font-size: 10px; + font-weight: 700; color: var(--color-text-muted); text-transform: uppercase; - letter-spacing: 0.05em; + letter-spacing: 0.06em; user-select: none; + opacity: 0.88; } // ── Result item ────────────────────────────────────────────────────────────── @@ -134,13 +182,14 @@ align-items: center; gap: $size-gap-2; width: 100%; - padding: 5px $size-gap-3; + padding: 6px $size-gap-2; border: none; + border-radius: 4px; background: transparent; cursor: pointer; text-align: left; color: var(--color-text-primary); - transition: background 0.08s ease; + transition: background 0.2s cubic-bezier(0.22, 1, 0.36, 1); &:hover { background: var(--element-bg-soft); @@ -152,10 +201,17 @@ } .bitfun-nav-search-dialog__item-icon { - color: var(--color-text-muted); + color: var(--color-text-primary); display: flex; align-items: center; flex-shrink: 0; + opacity: 0.82; + transition: opacity 0.2s ease; + + .bitfun-nav-search-dialog__item:hover &, + .bitfun-nav-search-dialog__item--active & { + opacity: 1; + } } .bitfun-nav-search-dialog__item-content { @@ -183,6 +239,22 @@ } // ── Trigger button (in NavPanel brand-header) ──────────────────────────────── +// Hover: (C) capsule “breath” via horizontal padding + (A) one-shot icon wiggle on inner span. + +@keyframes bitfun-search-trigger-icon-wiggle { + 0% { + transform: rotate(0deg); + } + 22% { + transform: rotate(-5.5deg); + } + 52% { + transform: rotate(4deg); + } + 100% { + transform: rotate(0deg); + } +} .bitfun-nav-panel__search-trigger { display: flex; @@ -192,37 +264,57 @@ min-height: 32px; padding: 0 $size-gap-2; border: none; - // Capsule shape border-radius: $size-radius-full; - background: var(--element-bg-soft); + background: var(--color-bg-scene); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--border-subtle) 65%, transparent); cursor: text; - color: var(--color-text-muted); + color: var(--color-text-primary); font-size: $font-size-xs; text-align: left; + box-sizing: border-box; transition: - background 0.15s ease, - box-shadow 0.15s ease, - transform 0.12s ease, - color 0.15s ease; + background 0.2s ease, + box-shadow 0.2s ease, + color 0.2s ease, + padding 0.42s cubic-bezier(0.22, 1, 0.36, 1); &:hover { - background: var(--element-bg-medium); + padding: 0 calc(#{$size-gap-2} + 2px); + background: color-mix(in srgb, var(--color-bg-scene) 88%, var(--element-bg-medium)); box-shadow: - inset 0 0 0 1px color-mix(in srgb, var(--border-medium) 55%, transparent), - 0 2px 8px rgba(0, 0, 0, 0.12), - 0 1px 3px rgba(0, 0, 0, 0.08); - transform: translateY(-1px); - color: var(--color-text-secondary); + inset 0 0 0 1px color-mix(in srgb, var(--border-medium) 55%, transparent); + color: var(--color-text-primary); cursor: text; } &:active { - transform: translateY(0); + padding: 0 calc(#{$size-gap-2} + 1px); box-shadow: - inset 0 0 0 1px color-mix(in srgb, var(--border-medium) 45%, transparent), - 0 1px 3px rgba(0, 0, 0, 0.08); + inset 0 0 0 1px color-mix(in srgb, var(--border-medium) 45%, transparent); + } + + &__icon { + display: inline-flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1); + } + + &:hover &__icon { + transform: translateX(2px); + } + + &__icon-inner { + display: inline-flex; + align-items: center; + justify-content: center; + transform-origin: 50% 62%; + } + + &:hover &__icon-inner { + animation: bitfun-search-trigger-icon-wiggle 0.58s cubic-bezier(0.34, 1.12, 0.52, 1) 1 both; } } @@ -238,19 +330,45 @@ } } -.bitfun-nav-panel__search-trigger__kbd { - flex-shrink: 0; - font-size: 10px; - color: var(--color-text-muted); - background: var(--element-bg-subtle); - border: 1px solid var(--border-subtle); - border-radius: $size-radius-sm; - padding: 1px 5px; - line-height: 1.4; - opacity: 0.6; - transition: opacity 0.15s ease; +@media (prefers-reduced-motion: reduce) { + .bitfun-nav-search-dialog__card, + .bitfun-nav-search-dialog__group, + .bitfun-nav-search-dialog__empty { + animation: none !important; + } - .bitfun-nav-panel__search-trigger:hover & { - opacity: 0.85; + .bitfun-nav-search-dialog__group { + animation-delay: 0s !important; + opacity: 1; + transform: none; + } + + .bitfun-nav-search-dialog__empty { + opacity: 1; + transform: none; + } + + .bitfun-nav-search-dialog__item { + transition: background 0.12s ease; + } + + .bitfun-nav-panel__search-trigger { + transition: + background 0.15s ease, + box-shadow 0.15s ease, + color 0.15s ease; + + &:hover, + &:active { + padding: 0 $size-gap-2; + } + + &:hover .bitfun-nav-panel__search-trigger__icon { + transform: none; + } + + &:hover .bitfun-nav-panel__search-trigger__icon-inner { + animation: none; + } } } diff --git a/src/web-ui/src/app/components/NavPanel/components/BranchQuickSwitch.scss b/src/web-ui/src/app/components/NavPanel/components/BranchQuickSwitch.scss index c2537cc1..bfa45ffc 100644 --- a/src/web-ui/src/app/components/NavPanel/components/BranchQuickSwitch.scss +++ b/src/web-ui/src/app/components/NavPanel/components/BranchQuickSwitch.scss @@ -95,8 +95,8 @@ background: var(--color-accent-100); cursor: default; - .branch-quick-switch__item-icon { color: var(--color-accent-500); } - .branch-quick-switch__item-name { color: var(--color-accent-600); font-weight: 500; } + .branch-quick-switch__item-icon { color: var(--color-text-secondary); } + .branch-quick-switch__item-name { color: var(--color-text-primary); font-weight: 500; } } &--switching { @@ -105,9 +105,9 @@ } } -.branch-quick-switch__item-icon { flex-shrink: 0; color: var(--color-text-muted); } +.branch-quick-switch__item-icon { flex-shrink: 0; color: var(--color-text-primary); } .branch-quick-switch__item-name { flex: 1; font-size: 12px; color: var(--color-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.branch-quick-switch__item-check { flex-shrink: 0; color: var(--color-accent-500); } +.branch-quick-switch__item-check { flex-shrink: 0; color: var(--color-text-primary); } .branch-quick-switch__spinner { animation: branch-quick-switch-spin 1s linear infinite; diff --git a/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx b/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx index d52a3025..0ac90872 100644 --- a/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx +++ b/src/web-ui/src/app/components/NavPanel/components/PersistentFooterActions.tsx @@ -1,5 +1,20 @@ import React, { useState, useCallback, useRef } from 'react'; -import { Settings, Info, MoreVertical, PictureInPicture2, SquareTerminal, Smartphone, Globe, Network, Layers, BarChart3 } from 'lucide-react'; +import { + Settings, + Info, + MoreVertical, + PictureInPicture2, + SquareTerminal, + Terminal, + Smartphone, + Globe, + Network, + Layers, + PanelsTopLeft, + BarChart3, + LineChart, + ChevronUp, +} from 'lucide-react'; import { Tooltip, Modal } from '@/component-library'; import { useI18n } from '@/infrastructure/i18n/hooks/useI18n'; import { useSceneManager } from '../../../hooks/useSceneManager'; @@ -181,7 +196,14 @@ const PersistentFooterActions: React.FC = () => { aria-expanded={menuOpen} onClick={toggleMenu} > - + {menuOpen ? ( +