diff --git a/selfdrive/carrot/web/css/components.css b/selfdrive/carrot/web/css/components.css index 0cd99e50b2d..4794fb9f7a8 100644 --- a/selfdrive/carrot/web/css/components.css +++ b/selfdrive/carrot/web/css/components.css @@ -991,11 +991,11 @@ body[data-page="terminal"] .app-toast-host { background: color-mix(in srgb, var(--md-error-cont) 18%, var(--md-surface-cont-h)); } +/* ── Setting Item ─────────────────────────────────────────── */ .ui-action-grid > .git-pull-btn { justify-content: space-between; } -/* ── Setting Item ─────────────────────────────────────────── */ .setting { padding: var(--sp-lg) 0; margin: 0; diff --git a/selfdrive/carrot/web/css/pages/settings/base.css b/selfdrive/carrot/web/css/pages/settings/base.css index fc24b6a6db1..76cba158ad5 100644 --- a/selfdrive/carrot/web/css/pages/settings/base.css +++ b/selfdrive/carrot/web/css/pages/settings/base.css @@ -13,8 +13,27 @@ --setting-profile-toolbar-height: 58px; } -.page--setting.setting-profile-active { - --page-gutter-block: 0px; +.page--setting #settingScreenHost.setting-screen-transitioning { + overflow: hidden !important; +} + +.page--setting #settingScreenHost.setting-screen-transitioning > .page-transitioning { + overflow: hidden; + background: var(--md-surface); + pointer-events: none; + contain: paint; +} + +.page--setting.setting-screen-transitioning #carrotTabContent, +.page--setting.setting-screen-transitioning #deviceTabContent, +.page--setting.setting-screen-transitioning #items, +.page--setting.setting-screen-transitioning #deviceItems { + animation: none !important; +} + +.page--setting .setting-screen-snapshot, +.page--setting .setting-screen-snapshot * { + animation: none !important; } .page--setting .setting.is-restored-live .val { @@ -160,6 +179,16 @@ flex-direction: column; align-items: flex-end; pointer-events: auto; + transform-origin: right bottom; + transition: + opacity 0.12s ease, + transform 0.16s cubic-bezier(0.2, 0, 0, 1); +} + +/* 전환 중 FAB 를 숨기지 않는다 — FAB 는 position:fixed 라 슬라이드와 무관하고, + 끝에 다시 나타나며(opacity/scale) 한 번 깜박이던 원인이었다. 클릭만 잠시 막는다. */ +.page--setting.setting-screen-transitioning .setting-fab-menu { + pointer-events: none; } .setting-fab-actions { @@ -294,14 +323,17 @@ } .setting-fab-menu.is-open .fab--setting-menu { - animation: setting-fab-pop 0.28s cubic-bezier(0.2, 0, 0, 1); box-shadow: 0 16px 34px rgba(255, 135, 58, 0.34); } +/* 터치/클릭 시 가벼운 바운스 (JS 가 is-bouncing 을 토글) */ +.fab--setting-menu.is-bouncing { + animation: setting-fab-bounce 0.3s cubic-bezier(0.2, 0, 0, 1); +} + .fab--setting-menu svg { width: 26px; height: 26px; - transition: transform 0.26s cubic-bezier(0.2, 0, 0, 1); } .fab--setting-menu .setting-fab-menu-icon { @@ -317,16 +349,15 @@ stroke-linejoin: round; } -.fab--setting-menu[aria-expanded="true"] svg { - transform: rotate(90deg) scale(0.92); -} - -@keyframes setting-fab-pop { +@keyframes setting-fab-bounce { 0% { - transform: scale(0.92); + transform: scale(1); + } + 30% { + transform: scale(0.93); } - 58% { - transform: scale(1.04); + 62% { + transform: scale(1.03); } 100% { transform: scale(1); @@ -339,7 +370,7 @@ .fab--setting-menu svg { transition: none !important; } - .setting-fab-menu.is-open .fab--setting-menu { + .fab--setting-menu.is-bouncing { animation: none !important; } .setting-fab-menu.is-open .setting-fab-action { diff --git a/selfdrive/carrot/web/css/pages/settings/device.css b/selfdrive/carrot/web/css/pages/settings/device.css index 59d9201c56a..0a1a7c2d2f9 100644 --- a/selfdrive/carrot/web/css/pages/settings/device.css +++ b/selfdrive/carrot/web/css/pages/settings/device.css @@ -27,35 +27,10 @@ white-space: nowrap; } -.setting-profile-info { - display: grid; - gap: 7px; - min-width: 0; - white-space: normal; -} - -.setting-profile-info--empty { - color: var(--md-on-surface-var); - font-weight: 800; -} - -.page--setting #carrotTabContent, -.page--setting #deviceTabContent, -.page--setting #items, -.page--setting #deviceItems { - animation: setting-content-enter 0.16s ease both; -} - -@keyframes setting-content-enter { - from { - opacity: 0; - transform: translateY(4px); - } - to { - opacity: 1; - transform: translateY(0); - } -} +/* setting-content-enter(opacity 0→1 + translateY 4px→0) 제거 — + 단계 전환이 끝나며(전환 중 `animation:none` 억제가 풀릴 때) 이 애니메이션이 재생돼 + 콘텐츠가 한 번 페이드(깜박)+4px 세로 점프(하단 위아래 흔들)하던 직접 원인이었다. + 좌우 슬라이드가 이미 화면 진입을 표현하므로 별도 콘텐츠 진입 애니메이션은 불필요하다. */ .setting-search-result { width: 100%; @@ -236,51 +211,67 @@ .page--setting #settingScreenItems.setting-screen-items--profile { padding-top: 0; --setting-profile-panel-sticky-top: 0px; - --setting-profile-section-sticky-top: var(--setting-profile-toolbar-height); + --setting-profile-section-sticky-top: 72px; } .page--setting #settingScreenItems.setting-screen-items--profile #items { padding-top: 0; } - .page--setting #settingScreenItems.setting-screen-items--profile .setting-profile-panel + .setting-profile-section { - margin-top: 0; + .page--setting #settingScreenItems.setting-screen-items--profile .setting-profile-row { + min-height: 46px; + gap: var(--sp-sm); } - .page--setting #settingScreenItems.setting-screen-items--profile .setting-profile-panel { - margin-left: calc(-1 * var(--sp-lg)); - margin-right: calc(-1 * var(--sp-lg)); - padding-left: calc(var(--sp-lg) + 14px); - padding-right: var(--sp-lg); + .page--setting #settingScreenItems.setting-screen-items--profile .setting-profile-action__icon { + width: 20px; + height: 20px; } - .page--setting #settingScreenItems.setting-screen-items--profile .setting-profile-panel::before { - left: var(--sp-lg); + .page--setting #settingScreenItems.setting-screen-items--profile .setting-profile-section__header { + margin-left: 0; + margin-right: 0; + padding-left: 2px; + padding-right: 2px; } - .page--setting #settingScreenItems.setting-screen-items--profile .setting-profile-panel__titleRow { - grid-template-columns: minmax(0, 1fr) auto; - gap: 7px; + .page--setting #settingScreenItems.setting-screen-items--profile > .row-between.mb-sm { + display: grid; } - .page--setting #settingScreenItems.setting-screen-items--profile .setting-toolbar-action { - min-width: 50px; - padding-inline: 10px; - font-size: 12.5px; + /* ── 세로(phone): 설정을 뷰포트 고정 높이로, 두 화면을 absolute inset:0 로 '항상' 겹쳐 + 각자 내부 스크롤시킨다 → 단계 전환이 순수 transform(레이아웃 변화 0)이라 끝에 + 흔들림/깜박임이 구조적으로 생기지 않는다. (가로 split 모드와 동일 원리) ── */ + .page.page--setting { + display: flex; + flex-direction: column; + height: calc(var(--app-vv-height, 100dvh) - var(--nav-bar-height) - env(safe-area-inset-bottom, 0px)); + min-block-size: 0; + padding-bottom: 0; + overflow: hidden; } - .page--setting #settingScreenItems.setting-screen-items--profile .setting-profile-section__header { - margin-left: calc(-1 * var(--sp-lg)); - margin-right: calc(-1 * var(--sp-lg)); - padding-left: var(--sp-lg); - padding-right: var(--sp-lg); + .page.page--setting > #settingsMeta { + flex: 0 0 auto; } - .page--setting #settingScreenHost, - .page--setting #settingScreenItems:not(.hidden) { - overflow: visible; - transform: none !important; - will-change: auto !important; + .page.page--setting > #settingScreenHost { + flex: 1 1 auto; + position: relative; + min-height: 0; + overflow: hidden; + } + + .page.page--setting > #settingScreenHost > #settingScreenGroups, + .page.page--setting > #settingScreenHost > #settingScreenItems { + position: absolute; + inset: 0; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + scrollbar-gutter: stable; + padding-bottom: calc(var(--nav-bar-height) + 24px + env(safe-area-inset-bottom, 0px)); + will-change: transform; } .page--setting #settingSubnavWrap { @@ -376,7 +367,7 @@ .page.page--setting > #settingScreenHost > #settingScreenItems.setting-screen-items--profile { padding-top: 0; --setting-profile-panel-sticky-top: 0px; - --setting-profile-section-sticky-top: var(--setting-profile-toolbar-height); + --setting-profile-section-sticky-top: 68px; } .page--setting #settingCarRow { diff --git a/selfdrive/carrot/web/css/pages/settings/panels.css b/selfdrive/carrot/web/css/pages/settings/panels.css index 7fcc29fe5e5..e88e5c781e0 100644 --- a/selfdrive/carrot/web/css/pages/settings/panels.css +++ b/selfdrive/carrot/web/css/pages/settings/panels.css @@ -196,36 +196,33 @@ .setting-profile-section { --setting-profile-accent: var(--md-primary, #ffab66); - --setting-profile-surface: color-mix(in srgb, var(--md-surface) 96%, #000); --setting-profile-divider: color-mix(in srgb, var(--setting-profile-accent) 34%, var(--md-outline-var)); min-width: 0; - display: grid; border-top: 0; scroll-margin-top: calc(var(--setting-profile-section-sticky-top, 64px) + 8px); } .setting-profile-section + .setting-profile-section { - border-top: 1px solid color-mix(in srgb, var(--md-outline-var) 42%, transparent); + border-top: 0; } .setting-profile-section__header { - position: sticky; - top: var(--setting-profile-section-sticky-top, 60px); - z-index: 4; + position: static; min-width: 0; - min-height: 46px; + width: 100%; + box-sizing: border-box; + min-height: 0; display: grid; grid-template-columns: minmax(0, 1fr) auto 18px; align-items: center; - gap: 10px; - padding: 11px 0 10px; + gap: 8px; + padding: 0 2px; border: 0; - border-bottom: 1px solid color-mix(in srgb, var(--md-outline-var) 42%, transparent); - background: var(--setting-profile-surface); - box-shadow: 0 1px 0 color-mix(in srgb, var(--md-outline-var) 18%, transparent); - color: var(--md-on-surface); + background: transparent; + box-shadow: none; + color: var(--setting-profile-accent); font: inherit; - font-size: 13px; + font-size: var(--fs-title-sm); font-weight: 900; letter-spacing: 0; text-align: left; @@ -235,8 +232,8 @@ .setting-profile-section__header:hover, .setting-profile-section__header:focus-visible { - background: color-mix(in srgb, var(--md-primary) 10%, var(--setting-profile-surface)); - color: var(--setting-profile-accent); + background: transparent; + color: color-mix(in srgb, var(--setting-profile-accent) 82%, var(--md-on-surface)); outline: none; } @@ -248,7 +245,7 @@ } .setting-profile-section__count { - min-width: 28px; + min-width: 0; padding: 2px 0; color: var(--setting-profile-accent); font-size: 12px; @@ -310,148 +307,161 @@ .setting-profile-panel { --setting-profile-accent: var(--md-primary, #ffab66); --setting-profile-ink: var(--md-on-surface); - --setting-profile-surface: color-mix(in srgb, var(--md-surface-cont-h) 74%, var(--md-surface)); - --setting-profile-divider: color-mix(in srgb, var(--md-outline-var) 50%, transparent); - --setting-profile-section-sticky-top: var(--setting-profile-toolbar-height); - position: sticky; - top: var(--setting-profile-panel-sticky-top, 0px); - z-index: 5; + --setting-profile-section-sticky-top: 68px; min-width: 0; - box-sizing: border-box; - height: var(--setting-profile-toolbar-height); - min-height: var(--setting-profile-toolbar-height); - margin-bottom: 0; - padding: 7px 0 7px 14px; - border: 1px solid var(--setting-profile-divider); - border-left: 0; - border-right: 0; - border-bottom: 1px solid var(--setting-profile-divider); - background: var(--setting-profile-surface); color: var(--setting-profile-ink); - box-shadow: - 0 8px 18px rgba(0, 0, 0, 0.22), - inset 0 -1px 0 rgba(255, 255, 255, 0.03); animation: setting-profile-toolbar-enter 0.2s cubic-bezier(.2, 0, 0, 1) both; } -.setting-profile-panel::before { - display: none; +/* ── 프로필 관리 카드 (Material 3 / One UI) ── + .setting-group-card 위에 얹는다. 세로 패딩은 헤더/행이 직접 갖는다 — + .setting-group-card 의 `padding: 0 var(--sp-lg)` 가 소스 순서상 뒤에 와서 + 단축속성으로 상하 패딩을 0 으로 덮어쓰므로 여기서 세로값을 주지 않는다. */ +.setting-profile-manage-card { + min-width: 0; + display: grid; + gap: 0; } -.setting-profile-panel__titleRow { - position: relative; - z-index: 1; +/* ── 라벨/값 행 — 설정 리스트의 .setting 행과 동일한 hairline 리듬 ── */ +.setting-profile-card__rows { min-width: 0; display: grid; - grid-template-columns: minmax(104px, 1fr) auto; - gap: 8px; - align-items: center; } -.page--setting #settingScreenItems.setting-screen-items--profile > .row-between.mb-sm { - display: none; +/* 첫 행(이름)은 카드 상단과 맞닿으므로 구분선 제거 + 상단 여백 */ +.setting-profile-card__rows > .setting-profile-row:first-child { + border-top: none; + padding-top: var(--sp-md); } -.setting-profile-panel__name { +.setting-profile-row { min-width: 0; - height: 42px; - padding: 0 2px 1px 0; - border: 0; - border-bottom: 1px solid color-mix(in srgb, var(--md-outline-var) 58%, transparent); - border-radius: 0; - background: transparent; - color: var(--setting-profile-ink); - font: inherit; - font-weight: 850; -} - -.setting-profile-panel__name:focus { - border-bottom-color: color-mix(in srgb, var(--setting-profile-accent) 76%, var(--md-outline-var)); - outline: none; + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-md); + min-height: 52px; + padding: var(--sp-sm) 0; + border-top: 1px solid color-mix(in srgb, var(--md-stroke-soft) 32%, transparent); } -.setting-profile-panel__name.is-saving { - color: color-mix(in srgb, var(--md-on-surface) 72%, var(--setting-profile-accent)); +.setting-profile-row:last-child { + padding-bottom: var(--sp-md); } -.setting-profile-panel__meta { - display: grid; - gap: 6px; +.setting-profile-row__label { + flex: 0 0 auto; + color: var(--md-on-surface-var); + font-size: var(--fs-label-md); + font-weight: 800; + line-height: 1.2; } -.setting-profile-panel__metaRow { +.setting-profile-row__value { min-width: 0; - display: grid; - grid-template-columns: 74px minmax(0, 1fr); - gap: 8px; - align-items: baseline; - color: var(--md-on-surface-var); - font-size: 12px; - line-height: 1.35; + flex: 1 1 auto; + overflow: hidden; + color: var(--md-on-surface); + font-size: var(--fs-body-sm); + font-weight: 700; + line-height: 1.3; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; } -.setting-profile-panel__metaRow span { +.setting-profile-row__value a { + color: var(--md-primary); font-weight: 800; + text-decoration: none; } -.setting-profile-panel__metaRow strong { +.setting-profile-row--name .setting-profile-row__label { + color: var(--md-primary); + font-size: var(--fs-title-sm); + font-weight: 900; +} + +/* ── 이름 편집 — Material filled text field ── */ +.setting-profile-name-input { + flex: 1 1 auto; min-width: 0; - overflow-wrap: anywhere; + height: 40px; + box-sizing: border-box; + padding: 0 var(--sp-md); + border: 1px solid color-mix(in srgb, var(--md-outline-var) 30%, transparent); + border-radius: var(--r-sm); + background: color-mix(in srgb, var(--md-surface-cont-h) 60%, transparent); color: var(--md-on-surface); - font-weight: 850; + font: inherit; + font-size: var(--fs-body-sm); + font-weight: 800; + text-align: right; + outline: none; + transition: border-color 0.14s ease, background 0.14s ease, color 0.14s ease; } -.setting-profile-panel__metaRow a { - color: var(--md-primary); - text-decoration: none; +.setting-profile-name-input:focus { + border-color: color-mix(in srgb, var(--md-primary) 58%, var(--md-outline-var)); + background: color-mix(in srgb, var(--md-surface-cont-h) 82%, var(--md-primary)); } -.setting-profile-action-menu { - justify-self: end; +.setting-profile-name-input.is-saving { + color: color-mix(in srgb, var(--md-on-surface) 72%, var(--md-primary)); } -.setting-profile-action-menu__button { - width: 42px; - height: 42px; - min-width: 42px; - min-height: 42px; - border-color: transparent; - background: transparent; - color: var(--md-on-surface-var); +/* ── ⋮ 메뉴 (카드 우상단 오버플로우) ── */ +.setting-profile-menu { + flex: 0 0 auto; } -.setting-profile-action-menu__button:hover, -.setting-profile-action-menu__button:focus-visible, -.setting-profile-action-menu.is-open .setting-profile-action-menu__button { - border-color: transparent; - background: color-mix(in srgb, var(--md-primary) 10%, transparent); - color: var(--md-on-surface); +.setting-profile-menu__button { + width: 40px; + min-width: 40px; + min-height: 40px; + border-color: color-mix(in srgb, var(--md-outline-var) 24%, transparent); + background: color-mix(in srgb, var(--md-surface-cont-h) 48%, transparent); } -.setting-profile-action-menu__button svg { +.setting-profile-menu__button svg { width: 22px; height: 22px; } -.setting-profile-action-menu__panel { +.setting-profile-menu__panel { position: absolute; top: calc(100% + 8px); right: 0; - z-index: 8; + z-index: 9; + min-width: 168px; } -.setting-profile-action-menu__item { - display: flex; +.setting-profile-menu__item .setting-profile-action__icon { + width: 18px; + height: 18px; } -.setting-profile-action-menu__item--primary { +.setting-profile-menu__item--primary, +.setting-profile-menu__item--primary .setting-profile-action__icon { color: var(--md-primary); } -.setting-profile-action-menu__item--danger { +.setting-profile-menu__item--danger, +.setting-profile-menu__item--danger .setting-profile-action__icon { color: var(--md-error); } +.setting-profile-action__icon { + width: 22px; + height: 22px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2; +} + .setting-toolbar-action { min-width: 56px; min-height: 42px; diff --git a/selfdrive/carrot/web/css/pages/tools/base.css b/selfdrive/carrot/web/css/pages/tools/base.css index 5ff831ba515..06a025e81a9 100644 --- a/selfdrive/carrot/web/css/pages/tools/base.css +++ b/selfdrive/carrot/web/css/pages/tools/base.css @@ -82,28 +82,6 @@ mask-image: none; } -.tools-meta__unreadBadge { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - min-width: 18px; - height: 18px; - padding: 0 5px; - border: 1px solid color-mix(in srgb, #ff8a8a 48%, transparent); - border-radius: 999px; - background: color-mix(in srgb, #ff3b30 82%, #3a0608); - color: #fff; - box-shadow: - 0 0 0 1px color-mix(in srgb, #fff 10%, transparent) inset, - 0 0 12px color-mix(in srgb, #ff5252 30%, transparent); - font-family: inherit; - font-size: 11px; - font-weight: 900; - line-height: 1; - letter-spacing: 0; -} - .tools-meta__status:focus-visible { outline: 2px solid color-mix(in srgb, var(--md-primary) 72%, transparent); outline-offset: 2px; diff --git a/selfdrive/carrot/web/index.html b/selfdrive/carrot/web/index.html index 535b6f152e7..e30cb95c610 100644 --- a/selfdrive/carrot/web/index.html +++ b/selfdrive/carrot/web/index.html @@ -78,9 +78,9 @@ - - - + + + @@ -149,10 +149,7 @@