diff --git a/selfdrive/carrot/server/features/settings.py b/selfdrive/carrot/server/features/settings.py index 0ef09efd4a..1ac42344b1 100644 --- a/selfdrive/carrot/server/features/settings.py +++ b/selfdrive/carrot/server/features/settings.py @@ -22,6 +22,7 @@ async def api_settings(request: web.Request) -> web.Response: "apilot": data.get("apilot"), "groups": groups_list, "items_by_group": items_by_group, + "categories": settings_cache.get("categories"), # 대>중>소 트리 (없으면 None → 프런트 폴백) "unit_cycle": UNIT_CYCLE, "has_params": HAS_PARAMS, "has_param_type": bool(ParamKeyType is not None and hasattr(Params(), "get_type")) if HAS_PARAMS else False, diff --git a/selfdrive/carrot/server/services/settings.py b/selfdrive/carrot/server/services/settings.py index 7441d3e2fb..3a5762df57 100644 --- a/selfdrive/carrot/server/services/settings.py +++ b/selfdrive/carrot/server/services/settings.py @@ -15,6 +15,7 @@ "groups": None, # {group: [param,...]} "by_name": None, # {name: param} "groups_list": None, # [{group, egroup, count}, ...] + "categories": None, # 대>중>소 트리 ([{id,ko,en,zh,groups:[...]}]) or None when no "menu" } @@ -56,6 +57,71 @@ def group_index(settings: Dict[str, Any]) -> Tuple[Dict[str, list], Dict[str, Di return groups, by_name, groups_list +def _label(node: Dict[str, Any]) -> Dict[str, Any]: + return {"ko": node.get("ko"), "en": node.get("en"), "zh": node.get("zh")} + + +def build_menu_categories(data: Dict[str, Any], by_name: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]] | None: + """Build the 대>중>소 tree from the optional top-level "menu" block. + + Returns None when no "menu" is present so the frontend falls back to the + flat group_index view. Leaf "items" carry param *names* only; the frontend + resolves definitions from items_by_group to avoid duplicating param data. + + Shape: + [ {id, ko, en, zh, + groups: [ {id, ko, en, zh, count, + sections: [ {id, ko, en, zh, items:[name,...]} ]} ]} ] + + A 중-group whose menu node holds params directly (no nested "groups") is + normalized to a single label-less section. + """ + menu = data.get("menu") + if not menu: + return None + + def join_labels(nodes: List[Dict[str, Any]], key: str) -> str | None: + parts = [str(n.get(key) or "").strip() for n in nodes] + parts = [p for p in parts if p] + return " · ".join(parts) if parts else None + + def sections_from(nodes: List[Dict[str, Any]], parents: List[Dict[str, Any]] | None = None) -> List[Dict[str, Any]]: + sections: List[Dict[str, Any]] = [] + parents = parents or [] + for node in nodes: + path = parents + [node] + children = node.get("groups") or [] + if children: + sections.extend(sections_from(children, path)) + continue + items = [n for n in node.get("params", []) if n in by_name] + if not items: + continue + sections.append({ + "id": "__".join(str(n.get("id") or "") for n in path if n.get("id")), + "ko": join_labels(path, "ko"), + "en": join_labels(path, "en"), + "zh": join_labels(path, "zh"), + "items": items, + }) + return sections + + cats: List[Dict[str, Any]] = [] + for cat in menu: + groups_out: List[Dict[str, Any]] = [] + for grp in cat.get("groups", []): + if "groups" in grp: + sections = sections_from(grp["groups"]) + else: + # params directly under the 중-group → single label-less section + sections = [{"id": grp.get("id"), "ko": None, "en": None, "zh": None, + "items": [n for n in grp.get("params", []) if n in by_name]}] + count = sum(len(s["items"]) for s in sections) + groups_out.append({**_label(grp), "id": grp.get("id"), "count": count, "sections": sections}) + cats.append({**_label(cat), "id": cat.get("id"), "groups": groups_out}) + return cats + + def get_settings_cached() -> Tuple[Dict[str, Any], Dict[str, list], Dict[str, Dict[str, Any]], List[Dict[str, Any]]]: path = settings_cache["path"] st = os.stat(path) @@ -69,6 +135,7 @@ def get_settings_cached() -> Tuple[Dict[str, Any], Dict[str, list], Dict[str, Di "groups": groups, "by_name": by_name, "groups_list": groups_list, + "categories": build_menu_categories(data, by_name), }) return ( settings_cache["data"], diff --git a/selfdrive/carrot/web/css/components.css b/selfdrive/carrot/web/css/components.css index f8c576af42..0cd99e50b2 100644 --- a/selfdrive/carrot/web/css/components.css +++ b/selfdrive/carrot/web/css/components.css @@ -62,7 +62,7 @@ body[data-page="terminal"] .app-toast-host { .app-dialog { position: fixed; inset: 0; - z-index: 160; + z-index: var(--z-modal); display: flex; align-items: center; justify-content: center; @@ -71,7 +71,7 @@ body[data-page="terminal"] .app-toast-host { } #appDialog { - z-index: 180; + z-index: calc(var(--z-modal) + 10); } .app-dialog[hidden] { @@ -87,31 +87,36 @@ body[data-page="terminal"] .app-toast-host { inset: 0; border: 0; padding: 0; - background: color-mix(in srgb, #000 56%, transparent); + background: color-mix(in srgb, #000 62%, transparent); opacity: 0; - transition: opacity 0.18s ease; + backdrop-filter: blur(2px) saturate(106%); + -webkit-backdrop-filter: blur(2px) saturate(106%); + transition: opacity var(--motion-base) var(--ease-linear); } .app-dialog__sheet { position: relative; z-index: 1; - width: min(100%, 420px); - max-height: calc(100dvh - var(--sp-lg) * 2); + width: min(100%, var(--dialog-sheet-width)); + max-height: var(--dialog-sheet-max-height); display: flex; flex-direction: column; padding: var(--sp-lg); border: 1px solid color-mix(in srgb, var(--md-stroke-soft) 92%, transparent); - border-radius: var(--r-lg); + border-radius: var(--dialog-sheet-radius); background: color-mix(in srgb, var(--md-surface-cont) 96%, #000); + box-shadow: var(--shadow-4); opacity: 0; - transform: translateY(14px); - transition: opacity 0.18s ease, transform 0.18s ease; + transform: translateY(14px) scale(0.985); + transition: + opacity var(--motion-base) var(--ease-emphasized), + transform var(--motion-medium) var(--ease-emphasized); } .app-dialog.is-open .app-dialog__backdrop, .app-dialog.is-open .app-dialog__sheet { opacity: 1; - transform: translateY(0); + transform: translateY(0) scale(1); } .app-dialog__head { @@ -157,12 +162,12 @@ body[data-page="terminal"] .app-toast-host { .app-dialog__choices { display: grid; - gap: var(--sp-sm); + gap: 6px; margin-top: var(--sp-md); overflow-y: auto; min-height: 0; flex: 1 1 auto; - padding-right: 4px; + padding-right: 2px; } .app-dialog__choices[hidden] { @@ -170,19 +175,109 @@ body[data-page="terminal"] .app-toast-host { } .app-dialog__choiceBtn { - width: 100%; + display: inline-flex; + align-items: center; justify-content: center; + width: 100%; + min-width: 0; + padding: 0 12px; + border-radius: var(--dialog-choice-radius); + overflow: hidden; + text-overflow: ellipsis; min-height: 44px; + font-size: var(--fs-label-md); + font-weight: 800; + letter-spacing: 0; } .app-dialog__choiceBtn.is-current { border-color: color-mix(in srgb, var(--md-primary) 56%, var(--md-stroke-soft)); - background: var(--md-primary-state); - color: var(--md-primary); + background: color-mix(in srgb, var(--md-surface-cont-hh) 74%, var(--md-primary-state)); + color: var(--md-on-surface); font-weight: 800; - display: flex; - align-items: center; - gap: 8px; +} + +/* 박스 안 박스 금지 — 다이얼로그 시트 자체가 박스이므로 리스트엔 테두리/배경/라운드를 + 두지 않는다. 상·하 hairline 으로만 구역을 나누고 항목 사이는 구분선만. */ +.app-dialog__choices--list { + gap: 0; + padding: 0; + border: 0; + border-top: 1px solid color-mix(in srgb, var(--md-stroke-soft) 30%, transparent); + border-bottom: 1px solid color-mix(in srgb, var(--md-stroke-soft) 30%, transparent); + background: transparent; +} + +/* list 항목은 .app-dialog__choices--list 하위로 묶어 우선순위를 .btn(파일 뒤쪽 + 정의, 알약 라운드+1px 보더)보다 높인다. 그래야 사각·풀폭 행이 확실히 유지돼 + 선택/포커스 시 둥근 박스가 컨테이너 보더와 겹치지 않는다. */ +.app-dialog__choices--list .app-dialog__choiceBtn--action { + justify-content: space-between; + text-align: left; + white-space: normal; + line-height: 1.25; + min-height: 48px; + padding: 0 14px; + border: 0; + border-bottom: 1px solid color-mix(in srgb, var(--md-stroke-soft) 40%, transparent); + border-radius: 0; + background: transparent; + box-shadow: none; +} + +.app-dialog__choices--list .app-dialog__choiceBtn--action:last-child { + border-bottom: 0; +} + +/* 즉시 동작(Play/Upload/Delete…)에 navigation chevron(›)은 오해를 부른다 → 없음 */ +.app-dialog__choices--list .app-dialog__choiceBtn--action::after { + content: ""; + display: none; +} + +/* 호버/포커스: 둥근 포커스링·보더 대신 풀폭 배경만 (선 겹침 방지) */ +.app-dialog__choices--list .app-dialog__choiceBtn--action:hover, +.app-dialog__choices--list .app-dialog__choiceBtn--action:focus-visible { + background: color-mix(in srgb, var(--md-on-surface) 8%, transparent); + border-bottom-color: color-mix(in srgb, var(--md-stroke-soft) 40%, transparent); + border-radius: 0; + outline: none; + box-shadow: none; +} + +.app-dialog__choices--list .app-dialog__choiceBtn--action.is-current { + color: var(--md-on-surface); + background: color-mix(in srgb, var(--md-primary) 14%, transparent); + border-bottom-color: color-mix(in srgb, var(--md-stroke-soft) 40%, transparent); +} + +/* 선택된 항목만 체크(✓) — 자체완결 */ +.app-dialog__choices--list .app-dialog__choiceBtn--action.is-current::after { + content: ""; + display: block; + width: 6px; + height: 11px; + margin-left: 12px; + border-right: 2px solid color-mix(in srgb, var(--md-primary) 82%, var(--md-on-surface)); + border-bottom: 2px solid color-mix(in srgb, var(--md-primary) 82%, var(--md-on-surface)); + transform: rotate(45deg); + flex: 0 0 auto; +} + +.app-dialog__choices--value-grid { + grid-template-columns: repeat(var(--app-dialog-choice-columns, 5), minmax(0, 1fr)); + gap: 6px; +} + +.app-dialog__choiceBtn--value { + min-height: 42px; + padding: 0 6px; + justify-content: center; + border-radius: var(--dialog-choice-grid-radius); + background: var(--md-surface-cont-h); + text-align: center; + white-space: nowrap; + font-variant-numeric: tabular-nums; } .app-dialog__inputWrap { @@ -201,12 +296,66 @@ body[data-page="terminal"] .app-toast-host { .app-dialog__actions { display: flex; justify-content: flex-end; - gap: var(--sp-sm); + gap: 8px; flex-wrap: wrap; - margin-top: var(--sp-lg); + margin-top: var(--sp-md); flex-shrink: 0; } +.app-dialog__actions .smallBtn, +.app-dialog__actions .btn { + border-radius: var(--dialog-control-radius); +} + +.app-dialog--choice-grid .app-dialog__sheet, +.app-dialog--choice-value-grid .app-dialog__sheet { + width: min(100%, var(--dialog-sheet-width)); + max-height: min(82dvh, 640px); + padding: var(--sp-lg); +} + +.app-dialog--choice-grid .app-dialog__body, +.app-dialog--choice-value-grid .app-dialog__body { + flex: 0 0 auto; + font-size: var(--fs-label-sm); + line-height: 1.4; +} + +@media (max-width: 640px) and (orientation: portrait) { + .app-dialog { + align-items: center; + padding: calc(12px + env(safe-area-inset-top, 0px)) 8px calc(12px + env(safe-area-inset-bottom, 0px)); + } + + .app-dialog__sheet { + width: calc(100vw - 16px); + max-height: var(--dialog-sheet-mobile-max-height); + padding: 14px; + border-radius: var(--dialog-sheet-radius-mobile); + transform: translateY(10px) scale(0.985); + } + + .app-dialog__head { + margin-bottom: 6px; + } + + .app-dialog__title { + font-size: var(--fs-title-xs); + } + + .app-dialog__body { + font-size: var(--fs-body-sm); + } + + .app-dialog__choiceBtn { + min-height: 42px; + } + + .app-dialog__choiceBtn--value { + min-height: 40px; + } +} + .app-branch-picker__sheet { width: min(100%, 500px); max-height: min(72vh, 560px); diff --git a/selfdrive/carrot/web/css/hud_card.css b/selfdrive/carrot/web/css/hud_card.css index 606eb2e033..c82e77ce8e 100644 --- a/selfdrive/carrot/web/css/hud_card.css +++ b/selfdrive/carrot/web/css/hud_card.css @@ -734,3 +734,35 @@ width: min(100%, var(--hud-max-width)); } } + +/* ── 기어 띄움(floating) — 설정속도 칼럼 우측 빈 공간(중앙)으로 이동 ── + 기존엔 차간거리 막대 옆 디테일 영역에 끼어 어색했음. 절대배치라 다른 요소 + 레이아웃은 안 건드린다. top/right 값은 실기기 보고 미세조정. */ +.hudSupportColumn { + position: relative; +} + +.hudWrap .hudSupportColumn > .hudGearStatus--floating { + position: absolute; + top: 40%; + right: calc(var(--hud-padding) * 0.20); + transform: translateY(-50%); + min-width: 0; + max-width: none; + align-items: flex-end; + text-align: right; + z-index: 1; +} + +.hudWrap .hudSupportColumn > .hudGearStatus--floating .hudGearStep, +.hudWrap .hudSupportColumn > .hudGearStatus--floating .hudGearLabel, +.hudWrap .hudSupportColumn > .hudGearStatus--floating .hudGearValue { + width: auto; + text-align: right; +} + +/* 모든 초록 HUD에서 기어 모양 동일하게 — veryCompact·micro 의 GEAR 라벨 숨김을 덮어 + 라벨을 항상 노출 (단, 0~7 단수 hudGearStep 의 hidden 동작은 그대로 둠). */ +.hudWrap .hudSupportColumn > .hudGearStatus--floating .hudGearLabel { + display: block; +} diff --git a/selfdrive/carrot/web/css/pages/settings/base.css b/selfdrive/carrot/web/css/pages/settings/base.css index de73beb8ec..fc24b6a6db 100644 --- a/selfdrive/carrot/web/css/pages/settings/base.css +++ b/selfdrive/carrot/web/css/pages/settings/base.css @@ -24,6 +24,8 @@ } .page--setting #items > .setting.ui-stagger-item, +.page--setting #items > .setting-section-block.ui-stagger-item, +.page--setting #items > .setting-group-card.ui-stagger-item, .page--setting #items > .setting-profile-section.ui-stagger-item, .page--setting #deviceItems > .setting.ui-stagger-item { animation-name: setting-item-stagger-in; @@ -272,6 +274,11 @@ border-color: #ffab66; color: #111; box-shadow: 0 12px 28px rgba(255, 135, 58, 0.22); + transition: + background 0.18s ease, + border-color 0.18s ease, + box-shadow 0.2s ease, + transform 0.2s cubic-bezier(0.2, 0, 0, 1); } .fab--setting-menu.active, @@ -282,15 +289,48 @@ box-shadow: 0 12px 30px rgba(255, 135, 58, 0.3); } +.fab--setting-menu:active { + transform: scale(0.94); +} + +.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); +} + .fab--setting-menu svg { width: 26px; height: 26px; - transition: transform 0.24s cubic-bezier(0.2, 0, 0, 1); + transition: transform 0.26s cubic-bezier(0.2, 0, 0, 1); +} + +.fab--setting-menu .setting-fab-menu-icon { + fill: none; +} + +.fab--setting-menu .setting-fab-menu-icon path, +.fab--setting-menu .setting-fab-menu-icon circle { + fill: none; + stroke: currentColor; + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; } -/* Rotate the "+" into an "x" when the menu is open */ .fab--setting-menu[aria-expanded="true"] svg { - transform: rotate(135deg); + transform: rotate(90deg) scale(0.92); +} + +@keyframes setting-fab-pop { + 0% { + transform: scale(0.92); + } + 58% { + transform: scale(1.04); + } + 100% { + transform: scale(1); + } } @media (prefers-reduced-motion: reduce) { @@ -299,6 +339,9 @@ .fab--setting-menu svg { transition: none !important; } + .setting-fab-menu.is-open .fab--setting-menu { + animation: none !important; + } .setting-fab-menu.is-open .setting-fab-action { transition-delay: 0ms !important; } diff --git a/selfdrive/carrot/web/css/pages/settings/device.css b/selfdrive/carrot/web/css/pages/settings/device.css index 1b8de5b42e..59d9201c56 100644 --- a/selfdrive/carrot/web/css/pages/settings/device.css +++ b/selfdrive/carrot/web/css/pages/settings/device.css @@ -102,10 +102,8 @@ margin-top: 6px; color: var(--md-on-surface-var); font-size: var(--fs-body-sm); - display: -webkit-box; - overflow: hidden; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; + display: block; + overflow: visible; } .setting-search-result__mark { @@ -134,7 +132,7 @@ .setting-search-panel { --setting-search-form-width: clamp(360px, 34vw, 540px); --setting-search-results-width: min(64vw, 820px); - --setting-search-results-max-height: min(60dvh, 560px); + --setting-search-results-max-height: min(72dvh, 680px); gap: 14px; padding-top: calc(22px + env(safe-area-inset-top, 0px)); padding-bottom: calc(var(--nav-bar-height-desktop) + 18px + env(safe-area-inset-bottom, 0px)); @@ -219,7 +217,7 @@ .setting-search-panel { --setting-search-form-width: calc(100vw - 28px); --setting-search-results-width: calc(100vw - 28px); - --setting-search-results-max-height: min(56dvh, 420px); + --setting-search-results-max-height: min(68dvh, 560px); gap: 12px; padding: calc(14px + env(safe-area-inset-top, 0px)) 12px calc(var(--nav-bar-height) + 14px + env(safe-area-inset-bottom, 0px)); } @@ -232,7 +230,7 @@ @media (max-width: 640px) and (orientation: portrait) { .page--setting #settingScreenItems { - padding-top: calc(var(--setting-fixed-subnav-height, 174px) + 10px); + padding-top: 0; } .page--setting #settingScreenItems.setting-screen-items--profile { @@ -286,21 +284,7 @@ } .page--setting #settingSubnavWrap { - position: fixed !important; - top: 0 !important; - right: 0 !important; - left: 0 !important; - z-index: 90; - width: 100dvw; - margin: 0 !important; - padding: calc(8px + env(safe-area-inset-top, 0px)) var(--sp-lg) 12px; - background: color-mix(in srgb, var(--md-surface) 98%, #000); - border-bottom: 1px solid color-mix(in srgb, var(--md-primary) 42%, var(--md-outline-var)); - box-shadow: - 0 10px 22px rgba(0, 0, 0, 0.34), - inset 0 -1px 0 rgba(255, 255, 255, 0.04); - transform: none !important; - will-change: auto !important; + display: none !important; } .page--setting #settingSubnavWrap::after { @@ -310,7 +294,7 @@ @media (max-height: 760px) { .setting-search-panel { - --setting-search-results-max-height: min(48dvh, 300px); + --setting-search-results-max-height: min(64dvh, 520px); gap: 10px; padding-top: calc(12px + env(safe-area-inset-top, 0px)); } @@ -364,6 +348,9 @@ box-sizing: border-box; scrollbar-gutter: stable; transition: none; + /* One UI 스타일 스크롤 가장자리 페이드 (상 14px / 하 24px) */ + -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 14px, #000 calc(100% - 24px), transparent 100%); + mask-image: linear-gradient(to bottom, transparent 0, #000 14px, #000 calc(100% - 24px), transparent 100%); } .page.page--setting > #settingScreenHost > #settingScreenGroups { @@ -547,7 +534,7 @@ .setting-search-panel { --setting-search-form-width: min(76vw, 540px); --setting-search-results-width: min(82vw, 760px); - --setting-search-results-max-height: min(48dvh, 260px); + --setting-search-results-max-height: min(68dvh, 520px); gap: 10px; padding: calc(12px + env(safe-area-inset-top, 0px)) 12px calc(var(--nav-bar-height) + 12px + env(safe-area-inset-bottom, 0px)); } @@ -615,6 +602,275 @@ max-width: var(--setting-control-value); } +.page--setting #settingScreenItems .ctrl--toggle, +.page--setting #settingScreenItems .ctrl--value, +.page--setting #settingScreenItems .ctrl--segmented, +.page--setting #settingScreenItems .ctrl--select { + display: flex; + justify-content: flex-end; + align-items: center; + width: auto; +} + +.page--setting #settingScreenItems .ctrl--value { + display: inline-grid; + grid-template-columns: 38px minmax(66px, auto) 38px; + gap: 0; + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--md-outline-var) 54%, transparent); + border-radius: var(--r-md); + background: var(--md-surface-cont-h); +} + +.page--setting #settingScreenItems .ctrl--value > :nth-child(1), +.page--setting #settingScreenItems .ctrl--value > :nth-child(2), +.page--setting #settingScreenItems .ctrl--value > :nth-child(3) { + grid-row: 1; +} + +.page--setting #settingScreenItems .ctrl--value .val { + position: relative; + width: auto; + min-width: 66px; + max-width: min(120px, 34vw); + min-height: 32px; + padding: 0 10px; + border: 0; + border-left: 1px solid color-mix(in srgb, var(--md-outline-var) 34%, transparent); + border-right: 1px solid color-mix(in srgb, var(--md-outline-var) 34%, transparent); + border-radius: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: center; +} + +.page--setting #settingScreenItems .setting-value-compact { + border-color: transparent; + background: color-mix(in srgb, var(--md-surface-cont-hh) 72%, var(--md-surface-cont-h)); + color: var(--md-on-surface); + box-shadow: none; + font-weight: 900; +} + +.page--setting #settingScreenItems .setting-value-arrow { + display: inline-flex; + align-items: center; + justify-content: center; + width: 38px; + min-width: 38px; + min-height: 32px; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; + color: var(--md-on-surface); + font-size: var(--fs-label-sm); + font-weight: 900; + line-height: 1; + touch-action: manipulation; +} + +.page--setting #settingScreenItems .setting-value-arrow:active, +.page--setting #settingScreenItems .setting-value-arrow.is-holding, +.page--setting #settingScreenItems .setting-step:active, +.page--setting #settingScreenItems .setting-step.is-holding { + background: color-mix(in srgb, var(--md-surface-cont-hh) 88%, var(--md-on-surface)); +} + +.page--setting #settingScreenItems .setting-icon { + display: block; + width: 18px; + height: 18px; +} + +.page--setting #settingScreenItems .setting-icon path { + fill: none; + stroke: currentColor; + stroke-width: 2.7; + stroke-linecap: round; + stroke-linejoin: round; +} + +.page--setting #settingScreenItems .ctrl--toggle .val, +.page--setting #settingScreenItems .ctrl--segmented .val, +.page--setting #settingScreenItems .ctrl--select .val { + display: none; +} + +.page--setting #settingScreenItems .setting-switch { + position: relative; + display: inline-flex; + align-items: center; + width: 54px; + height: 32px; + cursor: pointer; + -webkit-tap-highlight-color: transparent; +} + +.page--setting #settingScreenItems .setting-switch__input { + position: absolute; + opacity: 0; + pointer-events: none; +} + +.page--setting #settingScreenItems .setting-switch__track { + position: relative; + display: block; + width: 100%; + height: 100%; + border: 1px solid color-mix(in srgb, var(--md-outline-var) 50%, transparent); + border-radius: 999px; + background: var(--md-surface-cont-h); + transition: background 0.16s ease, border-color 0.16s ease; +} + +.page--setting #settingScreenItems .setting-switch__track::after { + content: ""; + position: absolute; + top: 3px; + left: 3px; + width: 24px; + height: 24px; + border-radius: 50%; + background: var(--md-on-surface-var); + box-shadow: var(--shadow-1); + transition: transform 0.18s cubic-bezier(.2, 0, 0, 1), background 0.16s ease; +} + +.page--setting #settingScreenItems .setting-switch__input:checked + .setting-switch__track { + border-color: color-mix(in srgb, var(--md-primary) 72%, var(--md-outline-var)); + background: color-mix(in srgb, var(--md-primary) 70%, var(--md-surface-cont-h)); +} + +.page--setting #settingScreenItems .setting-switch__input:checked + .setting-switch__track::after { + transform: translateX(22px); + background: var(--md-on-primary); +} + +.page--setting #settingScreenItems .setting-switch:focus-within .setting-switch__track, +.page--setting #settingScreenItems .setting-segment:focus-visible, +.page--setting #settingScreenItems .setting-select:focus-visible, +.page--setting #settingScreenItems .setting-slider__input:focus-visible { + outline: var(--focus-ring-width, 2px) solid var(--focus-ring-color, var(--md-primary)); + outline-offset: 2px; +} + +.page--setting #settingScreenItems .setting-segments { + display: inline-flex; + max-width: min(216px, 100%); + overflow: hidden; + border: 1px solid color-mix(in srgb, var(--md-outline-var) 54%, transparent); + border-radius: var(--r-md); + background: var(--md-surface-cont-h); +} + +.page--setting #settingScreenItems .setting-segment { + min-width: 38px; + min-height: 32px; + padding: 0 10px; + border: 0; + border-right: 1px solid color-mix(in srgb, var(--md-outline-var) 34%, transparent); + background: transparent; + color: var(--md-on-surface-var); + font-size: var(--fs-label-sm); + font-weight: 800; +} + +.page--setting #settingScreenItems .setting-segment:last-child { + border-right: 0; +} + +.page--setting #settingScreenItems .setting-segment.is-active { + background: var(--md-primary-state); + color: var(--md-primary); +} + +.page--setting #settingScreenItems .setting-select { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 92px; + min-height: 32px; + padding: 0 14px; + border: 1px solid color-mix(in srgb, var(--md-outline-var) 54%, transparent); + border-radius: var(--r-md); + background: var(--md-surface-cont-h); + color: var(--md-on-surface); + font-family: inherit; + font-size: var(--fs-label-sm); + font-weight: 850; + line-height: 1; + text-align: center; + cursor: pointer; + touch-action: manipulation; + -webkit-tap-highlight-color: transparent; + appearance: none; +} + +.page--setting #settingScreenItems .ctrl--slider { + grid-template-columns: var(--setting-control-side) var(--setting-control-value) var(--setting-control-side); +} + +.page--setting #settingScreenItems .ctrl--slider .setting-slider { + grid-column: 1 / -1; + grid-row: 1; + width: 100%; +} + +.page--setting #settingScreenItems .ctrl--slider .setting-step--minus { + grid-column: 1; + grid-row: 2; +} + +.page--setting #settingScreenItems .ctrl--slider .val { + grid-column: 2; + grid-row: 2; +} + +.page--setting #settingScreenItems .ctrl--slider .setting-step--plus { + grid-column: 3; + grid-row: 2; +} + +.page--setting #settingScreenItems .setting-slider__input { + width: 100%; + accent-color: var(--md-primary); +} + +.page--setting #settingScreenItems .setting { + position: relative; +} + +.page--setting #settingScreenItems .setting--has-unit-cycle { + padding-bottom: var(--sp-md); +} + +.page--setting #settingScreenItems .setting-unit-cycle { + display: flex; + align-items: center; + justify-content: center; + margin: 8px 0 0 auto; + min-width: 54px; + min-height: 32px; + padding: 0 12px; + border: 1px solid color-mix(in srgb, var(--md-outline-var) 54%, transparent); + border-radius: var(--r-md); + background: color-mix(in srgb, var(--md-surface-cont-hh) 72%, var(--md-surface-cont-h)); + color: var(--md-on-surface); + font-size: var(--fs-label-sm); + font-weight: 900; + line-height: 1; + letter-spacing: 0; + box-shadow: none; +} + +.page--setting #settingScreenItems .setting-unit-cycle:active { + border-color: color-mix(in srgb, var(--md-outline-var) 68%, transparent); + background: color-mix(in srgb, var(--md-surface-cont-hh) 88%, var(--md-on-surface)); + color: var(--md-on-surface); +} + .page--setting #settingScreenItems .setting-copy { min-width: 0; max-width: 100%; @@ -655,10 +911,84 @@ fill: currentColor; } +.page--setting #settingScreenItems > .row-between.mb-sm { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px; + align-items: end; + min-height: 56px; + margin: 0 0 14px; + padding: 2px 2px 10px; +} + +.page--setting #settingScreenItems #itemsTitle { + display: inline-flex; + align-items: center; + gap: 8px; + min-width: 0; + color: var(--md-on-surface); + font-size: 34px; + font-weight: 900; + line-height: 1; + letter-spacing: 0; +} + +.page--setting #settingScreenItems .setting-title-backIcon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 42px; + height: 42px; + flex: 0 0 42px; + margin-left: -10px; + border: 0; + border-radius: 999px; + background: transparent; + color: var(--md-on-surface); + transition: background 0.14s ease, color 0.14s ease, transform 0.14s ease; +} + +.page--setting #settingScreenItems #itemsTitle:active .setting-title-backIcon { + background: color-mix(in srgb, var(--md-on-surface) 10%, transparent); + color: var(--md-on-surface); + transform: translateX(-1px); +} + +.page--setting #settingScreenItems .setting-title-backIcon .setting-icon { + width: 24px; + height: 24px; +} + +.page--setting #settingScreenItems .setting-title-backIcon .setting-icon path { + stroke-width: 3.05; +} + +.page--setting #settingScreenItems .setting-title-text { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.page--setting.setting-layout-split #settingScreenItems .setting-title-backIcon { + display: none; +} + +.page--setting #settingScreenItems #groupMeta { + padding-bottom: 3px; + color: var(--md-on-surface-var); + font-size: var(--fs-label-sm); + font-weight: 800; +} + .page--setting #settingScreenItems .setting.is-longpressing { background: color-mix(in srgb, #7dd3fc 7%, transparent); } +.page--setting #settingScreenItems.setting-screen-items--detail #items::after { + display: none; +} + .page--setting #settingScreenItems .setting-favorites-empty { max-width: min(460px, 100%); margin: 0 auto; @@ -740,16 +1070,24 @@ @media (max-width: 640px) and (orientation: portrait) { .page--setting #settingScreenItems > .row-between.mb-sm { - min-height: 30px; - margin-bottom: 8px; + min-height: 48px; + margin-bottom: 10px; padding-bottom: 8px; - border-bottom: 1px solid color-mix(in srgb, var(--md-outline-var) 30%, transparent); + } + + .page--setting #settingScreenItems #itemsTitle { + font-size: 28px; } .page--setting #settingScreenItems .setting { padding: 18px 0 20px; } + .page--setting #settingScreenItems .setting-group-card__body > .setting { + padding-left: var(--sp-lg); + padding-right: var(--sp-lg); + } + .page--setting #settingScreenItems #items::after { content: ""; display: block; @@ -773,12 +1111,30 @@ max-width: 100%; } + .page--setting #settingScreenItems .ctrl--toggle, + .page--setting #settingScreenItems .ctrl--value, + .page--setting #settingScreenItems .ctrl--segmented, + .page--setting #settingScreenItems .ctrl--select { + width: auto; + } + .page--setting #settingScreenItems .smallBtn, .page--setting #settingScreenItems .val { min-height: 34px; font-size: 12px; } + .page--setting #settingScreenItems .ctrl--value .val, + .page--setting #settingScreenItems .setting-value-arrow { + min-height: 36px; + font-size: var(--fs-label-sm); + } + + .page--setting #settingScreenItems .setting-unit-cycle { + min-height: 32px; + font-size: var(--fs-label-sm); + } + .page--setting #settingScreenItems .title, .page--setting #settingScreenItems .name { text-align: left; diff --git a/selfdrive/carrot/web/css/pages/settings/panels.css b/selfdrive/carrot/web/css/pages/settings/panels.css index 1c32188c57..7fcc29fe5e 100644 --- a/selfdrive/carrot/web/css/pages/settings/panels.css +++ b/selfdrive/carrot/web/css/pages/settings/panels.css @@ -2,7 +2,7 @@ .setting-search-panel { --setting-search-form-width: clamp(320px, 42vw, 520px); --setting-search-results-width: min(72vw, 760px); - --setting-search-results-max-height: min(58dvh, 520px); + --setting-search-results-max-height: min(72dvh, 680px); position: fixed; inset: 0; z-index: 171; @@ -525,6 +525,8 @@ @media (prefers-reduced-motion: reduce) { .setting-profile-panel, .page--setting #items > .setting.ui-stagger-item, + .page--setting #items > .setting-section-block.ui-stagger-item, + .page--setting #items > .setting-group-card.ui-stagger-item, .page--setting #items > .setting-profile-section.ui-stagger-item, .page--setting #deviceItems > .setting.ui-stagger-item, .setting-profile-section.is-collapsing .setting-profile-section__body, @@ -539,3 +541,65 @@ } } +/* ── 대(category) 구분 헤더 — carrot_settings.json menu 기반 그룹목록 ── */ +.setting-category-divider { + padding-top: var(--sp-lg); +} + +.setting-category-divider strong { + color: var(--md-primary); + font-size: var(--fs-label-md); + font-weight: 900; + letter-spacing: 0.01em; +} + +.setting-category-divider span { + background: color-mix(in srgb, var(--md-primary) 30%, transparent); +} + +/* ── 소(subgroup) 그룹박스 카드 — Material 3 / One UI ── + 항목 화면에서 소-섹션을 둥근 카드로 묶는다. 카드 배경은 페이지(--md-surface) + 보다 한 단계 밝은 surface-cont. 행(.setting)은 카드 안에서 hairline 으로 구분되고 + :last-child 는 자동으로 선이 사라진다. */ +.setting-section-block { + display: grid; + gap: 8px; + margin: 0 0 var(--sp-lg); +} + +.setting-group-card { + background: var(--md-surface-cont); + border: 1px solid color-mix(in srgb, var(--md-outline-var) 22%, transparent); + border-radius: var(--r-xl); + padding: 0 var(--sp-lg); + margin: 0; + box-shadow: var(--shadow-1); +} + +.setting-group-card__title { + padding: 0 2px; + color: var(--md-primary); + font-size: var(--fs-title-sm); + font-weight: 900; + letter-spacing: 0; +} + +.setting-group-card__body > .setting { + margin-left: calc(-1 * var(--sp-lg)); + margin-right: calc(-1 * var(--sp-lg)); + border-radius: 0; + padding-left: var(--sp-lg); + padding-right: var(--sp-lg); + border-bottom: 1px solid color-mix(in srgb, var(--md-stroke-soft) 32%, transparent); +} + +.setting-group-card__body > .setting:first-child { + border-top-left-radius: calc(var(--r-xl) - 1px); + border-top-right-radius: calc(var(--r-xl) - 1px); +} + +.setting-group-card__body > .setting:last-child { + border-bottom-left-radius: calc(var(--r-xl) - 1px); + border-bottom-right-radius: calc(var(--r-xl) - 1px); + border-bottom: none; +} diff --git a/selfdrive/carrot/web/css/tokens.css b/selfdrive/carrot/web/css/tokens.css index 3717d4a14a..6d0d809208 100644 --- a/selfdrive/carrot/web/css/tokens.css +++ b/selfdrive/carrot/web/css/tokens.css @@ -84,6 +84,22 @@ --r-2xl: 32px; --r-pill: 999px; + /* Popup UX primitives + - Compact sheets for alert / confirm / prompt. + - Bottom sheets on narrow portrait screens. + - Value grids for short numeric or code choices. + - Action lists for commands and destructive choices. + - Pill radius is reserved for FABs, switches, and small chips. */ + --dialog-sheet-radius: 16px; + --dialog-sheet-radius-mobile: 18px; + --dialog-control-radius: 8px; + --dialog-choice-radius: 8px; + --dialog-choice-grid-radius: 8px; + --dialog-sheet-width: 420px; + --dialog-sheet-width-wide: 520px; + --dialog-sheet-max-height: calc(100dvh - var(--sp-lg) * 2); + --dialog-sheet-mobile-max-height: min(88dvh, 720px); + /* ── Elevation ──────────────────────────────────────────────── Material 3 dark elevation. Pick the smallest level that reads as "raised". Larger = more separation from background. diff --git a/selfdrive/carrot/web/index.html b/selfdrive/carrot/web/index.html index c9af3cb7c5..535b6f152e 100644 --- a/selfdrive/carrot/web/index.html +++ b/selfdrive/carrot/web/index.html @@ -148,8 +148,11 @@

Mode @@ -566,11 +569,12 @@

Home

(--)
-
- -
GEAR
-
P
-
+ + +
+ +
GEAR
+
P
diff --git a/selfdrive/carrot/web/js/app.js b/selfdrive/carrot/web/js/app.js index 20c383cd5c..72ea86689f 100644 --- a/selfdrive/carrot/web/js/app.js +++ b/selfdrive/carrot/web/js/app.js @@ -28,7 +28,16 @@ window.addEventListener("popstate", async (ev) => { const targetGroup = CURRENT_GROUP || getLandscapeDefaultSettingGroup(); if (targetGroup) { CURRENT_GROUP = targetGroup; - await activateSettingGroup(targetGroup, false, { scrollMode: "restore", animateGroups: false, animateItems: false }); + if (screen === "detail" && st.settingName) { + showSettingScreen("items", false); + await renderItems(targetGroup, { + detailName: st.settingName, + scrollMode: "restore", + animateItems: false, + }); + } else { + await activateSettingGroup(targetGroup, false, { scrollMode: "restore", animateGroups: false, animateItems: false }); + } } else { showSettingScreen("groups", false); } @@ -38,7 +47,14 @@ window.addEventListener("popstate", async (ev) => { return; } - if (screen === "items" && CURRENT_GROUP) { + if (screen === "detail" && CURRENT_GROUP && st.settingName) { + showSettingScreen("items", false); + renderItems(CURRENT_GROUP, { + detailName: st.settingName, + scrollMode: "restore", + animateItems: false, + }); + } else if (screen === "items" && CURRENT_GROUP) { showSettingScreen("items", false); renderItems(CURRENT_GROUP, { scrollMode: "restore", animateItems: false }); } else { diff --git a/selfdrive/carrot/web/js/pages/logs/dashcam.js b/selfdrive/carrot/web/js/pages/logs/dashcam.js index 74d4782717..decb933208 100644 --- a/selfdrive/carrot/web/js/pages/logs/dashcam.js +++ b/selfdrive/carrot/web/js/pages/logs/dashcam.js @@ -1268,6 +1268,7 @@ async function showDashcamSegmentMenu(route, segment) { mode: "choice", title: `SEG ${dashcamSegmentIndex(segment)}`, message: segment, + choiceLayout: "list", choices: [ { label: getUIText("play", "Play"), value: "play" }, { label: getUIText("log_upload", "Upload Logs"), value: "upload" }, @@ -1497,10 +1498,11 @@ async function showDashcamRouteMenu(route) { mode: "choice", title: getUIText("group_menu", "Group menu"), message: dashcamRouteTitle(route), + choiceLayout: "list", choices: [ { label: `${getUIText("select_range", "Select range")}…`, value: "range" }, - { label: `${getUIText("sort_ascending", "Sort: ascending")}${sort === "asc" ? " ✓" : ""}`, value: "sort_asc" }, - { label: `${getUIText("sort_descending", "Sort: descending")}${sort === "desc" ? " ✓" : ""}`, value: "sort_desc" }, + { label: getUIText("sort_ascending", "Sort: ascending"), value: "sort_asc", selected: sort === "asc" }, + { label: getUIText("sort_descending", "Sort: descending"), value: "sort_desc", selected: sort === "desc" }, ], }); if (selected === "range") await showDashcamRangeSelect(route); diff --git a/selfdrive/carrot/web/js/pages/setting.js b/selfdrive/carrot/web/js/pages/setting.js index 54ef9e4edd..8ad65e9616 100644 --- a/selfdrive/carrot/web/js/pages/setting.js +++ b/selfdrive/carrot/web/js/pages/setting.js @@ -18,6 +18,7 @@ let settingSubnavFocusTimer = null; const SETTING_FAVORITES_GROUP = "__setting_favorites__"; const SETTING_PROFILES_DIVIDER = "__setting_profiles_divider__"; const SETTING_PROFILE_GROUP_PREFIX = "__setting_profile__:"; +const SETTING_CATEGORY_DIVIDER_PREFIX = "__setting_category__:"; const SETTING_FAVORITES_LONG_PRESS_MS = 620; const SETTING_FAVORITES_MOVE_TOLERANCE = 10; const settingFavoritesState = { @@ -40,6 +41,52 @@ function isSettingProfilesDivider(entry) { return entry?.group === SETTING_PROFILES_DIVIDER || entry === SETTING_PROFILES_DIVIDER; } +function isSettingCategoryDivider(entry) { + return String(entry?.group || "").startsWith(SETTING_CATEGORY_DIVIDER_PREFIX); +} + +function isSettingAnyDivider(entry) { + return isSettingProfilesDivider(entry) || isSettingCategoryDivider(entry); +} + +// 대>중>소 노드(카테고리/그룹/섹션)의 현재 언어 라벨. ko/en/zh 직접 보유 노드용. +function settingNodeLabel(node) { + if (!node) return ""; + if (LANG === "zh") return node.zh || node.en || node.ko || ""; + if (LANG === "ko") return node.ko || node.en || node.zh || ""; + return node.en || node.ko || node.zh || ""; +} + +// /api/settings 의 categories(대>중>소)가 있으면 groups/items_by_group 를 +// 중-group id 키 기준으로 정규화한다. 각 항목엔 소-섹션 라벨을 __section 으로 부착. +// categories 가 없으면(구버전) 아무것도 바꾸지 않아 기존 평면 UI 로 폴백. +function normalizeSettingCategories(j) { + if (!j || !Array.isArray(j.categories) || !j.categories.length) return; + const idx = {}; + Object.values(j.items_by_group || {}).forEach((list) => { + (list || []).forEach((it) => { if (it && it.name) idx[it.name] = it; }); + }); + const flatGroups = []; + const newItemsByGroup = {}; + j.categories.forEach((cat) => { + (cat.groups || []).forEach((g) => { + flatGroups.push({ group: g.id, ko: g.ko, en: g.en, zh: g.zh, count: g.count, category: cat.id }); + const items = []; + (g.sections || []).forEach((sec) => { + // 라벨이 없어도(단일 직속 섹션) 카드는 만들도록 항상 객체로 둔다. + const secLabel = { id: sec.id, ko: sec.ko, en: sec.en, zh: sec.zh }; + (sec.items || []).forEach((name) => { + const def = idx[name]; + if (def) items.push(Object.assign({}, def, { __section: secLabel })); + }); + }); + newItemsByGroup[g.id] = items; + }); + }); + j.groups = flatGroups; + j.items_by_group = newItemsByGroup; +} + function settingProfileGroup(profileId) { return SETTING_PROFILE_GROUP_PREFIX + String(profileId || ""); } @@ -133,15 +180,29 @@ function getSettingProfilesLabel() { } function getSettingGroupsForDisplay() { - const groups = SETTINGS?.groups || []; const out = [ { group: SETTING_FAVORITES_GROUP, count: getFavoriteSettingEntries().length, virtual: true, }, - ...groups, ]; + const cats = SETTINGS?.categories; + if (Array.isArray(cats) && cats.length) { + cats.forEach((cat) => { + out.push({ + group: SETTING_CATEGORY_DIVIDER_PREFIX + (cat.id || ""), + label: settingNodeLabel(cat), + divider: true, + virtual: true, + }); + (cat.groups || []).forEach((g) => { + out.push({ group: g.id, ko: g.ko, en: g.en, zh: g.zh, count: g.count, category: cat.id }); + }); + }); + } else { + out.push(...(SETTINGS?.groups || [])); + } const profiles = settingProfilesState.profiles || []; if (profiles.length) { out.push({ @@ -317,7 +378,7 @@ function applyRestoredSettingValuesToRenderedItems(values) { if (!name || !(name in values)) return; const valueButton = row.querySelector(".val"); if (!valueButton) return; - valueButton.textContent = String(values[name]); + syncSettingControlState(row, values[name]); row.classList.add("is-restored-live"); window.setTimeout(() => row.classList.remove("is-restored-live"), 900); updated = true; @@ -496,6 +557,7 @@ async function loadSettings(options = {}) { settingsLoadPromise = (async () => { const j = await getJson("/api/settings"); + normalizeSettingCategories(j); SETTINGS = j; UNIT_CYCLE = j.unit_cycle || UNIT_CYCLE; settingValueCache.clear(); @@ -626,7 +688,7 @@ function renderGroups(options = {}) { const box = document.getElementById("groupList"); const animateGroups = options.animateGroups !== false; const groups = getSettingGroupsForDisplay(); - const signature = groups.map((g) => isSettingProfilesDivider(g) ? SETTING_PROFILES_DIVIDER : `${g.group}:${g.count ?? ""}:${g.label || ""}`).join("|"); + const signature = groups.map((g) => isSettingAnyDivider(g) ? `div:${g.label || ""}` : `${g.group}:${g.count ?? ""}:${g.label || ""}`).join("|"); function setGroupButtonLabel(button, label, count) { const text = Number.isFinite(Number(count)) ? `${label} (${count})` : label; @@ -638,8 +700,8 @@ function renderGroups(options = {}) { if (!animateGroups && box.dataset.groupsSignature === signature && box.children.length === groups.length) { Array.from(box.children).forEach((button, index) => { const g = groups[index]; - if (isSettingProfilesDivider(g)) { - button.className = "setting-profile-divider"; + if (isSettingAnyDivider(g)) { + button.className = isSettingCategoryDivider(g) ? "setting-profile-divider setting-category-divider" : "setting-profile-divider"; button.innerHTML = `${escapeHtml(g.label || getSettingProfilesLabel())}`; button.removeAttribute("data-group"); button.onclick = null; @@ -662,9 +724,10 @@ function renderGroups(options = {}) { box.dataset.groupsSignature = signature; groups.forEach(g => { - if (isSettingProfilesDivider(g)) { + if (isSettingAnyDivider(g)) { const divider = document.createElement("div"); - divider.className = animateGroups ? "setting-profile-divider ui-stagger-item" : "setting-profile-divider"; + const base = animateGroups ? "setting-profile-divider ui-stagger-item" : "setting-profile-divider"; + divider.className = isSettingCategoryDivider(g) ? base + " setting-category-divider" : base; if (animateGroups) divider.style.setProperty("--i", String(box.children.length)); divider.innerHTML = `${escapeHtml(g.label || getSettingProfilesLabel())}`; box.appendChild(divider); @@ -716,11 +779,178 @@ function getSettingGroupLabel(group) { if (profile) return profile.name; const meta = getSettingGroupMeta(group); if (!meta) return group; + if (meta.ko || meta.en || meta.zh) return settingNodeLabel(meta); if (LANG === "zh") return meta.cgroup || meta.egroup || meta.group; if (LANG === "ko") return meta.group || meta.egroup || group; return meta.egroup || meta.group || group; } +function getSettingItemContextLabel(group, item) { + const groupLabel = getSettingGroupLabel(group); + const sectionLabel = item?.__section ? settingNodeLabel(item.__section) : ""; + if (!sectionLabel || sectionLabel === groupLabel) return groupLabel; + return `${groupLabel} > ${sectionLabel}`; +} + +const SETTING_CONTROL_OVERRIDES = { + ShowPathMode: { kind: "select" }, + ShowPathColor: { kind: "select" }, + ShowPathColorCruiseOff: { kind: "select" }, + ShowPathModeLane: { kind: "select" }, + ShowPathColorLane: { kind: "select" }, + ShowPlotMode: { kind: "select" }, + ClusterHudScreenMode: { kind: "select" }, + ClusterHudRadarInfo: { kind: "select" }, +}; + +function getSettingControlConfig(p) { + const override = SETTING_CONTROL_OVERRIDES[p?.name] || {}; + const min = Number(p?.min); + const max = Number(p?.max); + const unit = Math.max(1, Number(p?.unit) || 1); + const optionCount = Number.isFinite(min) && Number.isFinite(max) ? Math.floor(max - min + 1) : 0; + let kind = override.kind || "slider"; + + if (!override.kind) { + if (min === 0 && max === 1) { + kind = "toggle"; + } else if (Number.isInteger(min) && Number.isInteger(max) && optionCount >= 2 && optionCount <= 4) { + kind = "segmented"; + } else if (Number.isInteger(min) && Number.isInteger(max) && optionCount > 4 && optionCount <= 8) { + kind = "select"; + } else { + kind = "slider"; + } + } + + return { kind, min, max, unit, optionCount }; +} + +function getSettingDisplayUnit(name) { + if (!name) return ""; + if (/Brightness|Volume/i.test(name)) return "%"; + if (/Speed/i.test(name)) return "km/h"; + if (/Dist|Distance|Offset/i.test(name)) return "cm"; + return ""; +} + +function formatSettingDisplayValue(p, value) { + const text = String(value); + const unit = getSettingDisplayUnit(p?.name); + return unit ? `${text}${unit}` : text; +} + +const SETTING_UNIT_STORAGE_KEY = "carrot.settingUnitIndex.v1"; +let settingUnitIndexStore = null; + +function getSettingUnitIndexStore() { + if (settingUnitIndexStore) return settingUnitIndexStore; + try { + settingUnitIndexStore = JSON.parse(localStorage.getItem(SETTING_UNIT_STORAGE_KEY) || "{}") || {}; + } catch (_) { + settingUnitIndexStore = {}; + } + return settingUnitIndexStore; +} + +function saveSettingUnitIndex(name, index) { + const key = String(name || "").trim(); + if (!key) return; + try { + const store = getSettingUnitIndexStore(); + store[key] = index; + localStorage.setItem(SETTING_UNIT_STORAGE_KEY, JSON.stringify(store)); + } catch (_) {} +} + +function getSettingUnitIndex(name) { + const key = String(name || "").trim(); + if (!key) return 0; + if (!(key in UNIT_INDEX)) { + const saved = Number(getSettingUnitIndexStore()[key]); + UNIT_INDEX[key] = Number.isInteger(saved) && saved >= 0 && saved < UNIT_CYCLE.length ? saved : 0; + } + if (!Number.isInteger(UNIT_INDEX[key]) || UNIT_INDEX[key] < 0 || UNIT_INDEX[key] >= UNIT_CYCLE.length) { + UNIT_INDEX[key] = 0; + } + return UNIT_INDEX[key]; +} + +function getSettingUnitValue(name) { + return UNIT_CYCLE[getSettingUnitIndex(name)] || UNIT_CYCLE[0] || 1; +} + +function setSettingUnitButtonLabel(button, name) { + if (button) button.textContent = "x" + getSettingUnitValue(name); +} + +function cycleSettingUnitValue(name) { + const key = String(name || "").trim(); + if (!key) return; + UNIT_INDEX[key] = (getSettingUnitIndex(key) + 1) % UNIT_CYCLE.length; + saveSettingUnitIndex(key, UNIT_INDEX[key]); +} + +function settingChevronSvg(direction = "left") { + const path = direction === "right" ? "M8.75 4.75 16 12l-7.25 7.25" : "M15.25 4.75 8 12l7.25 7.25"; + return ` + + `; +} + +function setSettingItemsTitle(label) { + if (!itemsTitle) return; + const safeLabel = escapeHtml(label || ""); + itemsTitle.innerHTML = ` + + ${safeLabel} + `; +} + +function getSettingOptionValues(config) { + if (!config || !Number.isInteger(config.min) || !Number.isInteger(config.max)) return []; + const out = []; + for (let value = config.min; value <= config.max; value += 1) out.push(value); + return out; +} + +function getSettingOptionLabel(name, value) { + return String(value); +} + +function syncSettingControlState(row, value) { + if (!row) return; + const text = String(value); + const valueButton = row.querySelector(".val"); + if (valueButton) { + const displayText = formatSettingDisplayValue({ name: row.dataset.settingName || "" }, value); + valueButton.textContent = displayText; + valueButton.dataset.rawValue = text; + } + + const toggle = row.querySelector(".setting-switch__input"); + if (toggle) toggle.checked = Number(value) === 1; + + const slider = row.querySelector(".setting-slider__input"); + if (slider) slider.value = text; + + row.querySelectorAll(".setting-segment").forEach((button) => { + button.classList.toggle("is-active", String(button.dataset.value) === text); + button.setAttribute("aria-pressed", String(button.dataset.value) === text ? "true" : "false"); + }); + + const select = row.querySelector(".setting-select"); + if (select) { + select.value = text; + if (select.tagName === "BUTTON") { + select.dataset.value = text; + select.textContent = getSettingOptionLabel(row.dataset.settingName || "", value); + } + } +} + const SETTING_SUBNAV_PAGE_STEP = 1; let settingGroupTransitionLock = false; let settingRenderToken = 0; @@ -731,6 +961,7 @@ let settingSearchEntries = []; let settingSearchScope = { type: "all", profileId: "" }; const settingPageRoot = document.getElementById("pageSetting"); let settingFabMenuOpen = false; +let CURRENT_SETTING_DETAIL = null; function isCompactLandscapeMode() { return window.matchMedia("(orientation: landscape)").matches; @@ -1191,6 +1422,7 @@ function mountSettingSearchOverlay() { function makeSettingSearchEntry({ source, profile = null, group, item }) { const groupLabel = getSettingGroupLabel(group); + const contextGroupLabel = getSettingItemContextLabel(group, item); const title = formatItemText(item, "title", "etitle", ""); const descr = formatItemText(item, "descr", "edescr", ""); const isProfile = source === "profile" && profile?.id; @@ -1199,8 +1431,8 @@ function makeSettingSearchEntry({ source, profile = null, group, item }) { ? getUIText("setting_search_source_profile", "Profile") : getUIText("setting_search_source_carrot", "CarrotPilot"); const contextLabel = isProfile - ? `${profileName} / ${groupLabel}` - : groupLabel; + ? `${profileName} / ${contextGroupLabel}` + : contextGroupLabel; return { source: isProfile ? "profile" : "carrot", @@ -1210,11 +1442,12 @@ function makeSettingSearchEntry({ source, profile = null, group, item }) { group: isProfile ? settingProfileGroup(profile.id) : group, originalGroup: group, groupLabel, + contextGroupLabel, contextLabel, name: item.name, title, descr, - haystack: [sourceLabel, profileName, groupLabel, item.name, title, descr].join("\n").toLowerCase(), + haystack: [sourceLabel, profileName, groupLabel, contextGroupLabel, item.name, title, descr].join("\n").toLowerCase(), }; } @@ -1363,7 +1596,7 @@ function resetSettingItemsViewport() { function hasRenderedSettingItems(group = CURRENT_GROUP) { const itemsBox = document.getElementById("items"); if (!itemsBox || !group) return false; - return itemsBox.dataset.renderedGroup === group && itemsBox.childElementCount > 0; + return itemsBox.dataset.renderedGroup === group && !itemsBox.dataset.renderedDetail && itemsBox.childElementCount > 0; } function isCarrotSettingTabActive() { @@ -1373,12 +1606,62 @@ function isCarrotSettingTabActive() { function syncSettingGroupChrome(group = CURRENT_GROUP) { const meta = document.getElementById("groupMeta"); const list = getSettingItemEntriesForGroup(group); - if (meta && group) meta.textContent = `${group} / ${list.length}`; const groupLabel = group ? getSettingGroupLabel(group) : ""; + if (CURRENT_SETTING_DETAIL) { + const detailEntry = getSettingDetailEntry(group, CURRENT_SETTING_DETAIL); + const detailTitle = detailEntry?.item ? getSettingDetailTitle(detailEntry.item) : CURRENT_SETTING_DETAIL; + if (meta && group) meta.textContent = `${group} / 1`; + if (group) { + settingTitle.textContent = (UI_STRINGS[LANG].setting || "Setting") + " - " + detailTitle; + setSettingItemsTitle(detailTitle); + } + return; + } + if (meta && group) meta.textContent = `${group} / ${list.length}`; if (group) { settingTitle.textContent = (UI_STRINGS[LANG].setting || "Setting") + " - " + groupLabel; - if (itemsTitle) itemsTitle.textContent = groupLabel; + setSettingItemsTitle(groupLabel); + } +} + +function getSettingDetailEntry(group, name) { + const target = String(name || "").trim(); + if (!target) return null; + const entries = getSettingItemEntriesForGroup(group); + return entries.find((entry) => entry?.item?.name === target) || null; +} + +function getSettingDetailTitle(item) { + return formatItemText(item, "title", "etitle", item?.name || ""); +} + +function isSettingInlineControlTarget(target) { + return Boolean(target?.closest?.(".ctrl, button, input, select, textarea, a")); +} + +async function selectSettingDetail(group, name, pushHistory = true) { + const targetGroup = group || CURRENT_GROUP; + const targetName = String(name || "").trim(); + if (!targetGroup || !targetName) return; + + saveCurrentSettingScrollPosition(targetGroup); + CURRENT_GROUP = targetGroup; + CURRENT_SETTING_DETAIL = targetName; + showSettingScreen("items", false); + if (pushHistory) { + history.pushState({ + page: "setting", + screen: "detail", + group: targetGroup, + settingName: targetName, + }, ""); } + await renderItems(targetGroup, { + detailName: targetName, + scrollMode: "top", + animateItems: true, + forceValues: true, + }); } function settingMarqueeHtml(text, className) { @@ -1545,8 +1828,8 @@ function renderSettingSearchResults(query = "") { button.type = "button"; button.className = "setting-search-result"; const metaLabel = entry.source === "profile" - ? `${entry.profileName} / ${entry.groupLabel}` - : entry.groupLabel; + ? `${entry.profileName} / ${entry.contextGroupLabel || entry.groupLabel}` + : entry.contextGroupLabel || entry.groupLabel; button.innerHTML = `
${highlightSettingSearchText(metaLabel, trimmed)}
${highlightSettingSearchText(entry.title || entry.name, trimmed)}
@@ -1560,7 +1843,7 @@ function renderSettingSearchResults(query = "") { settingProfileSectionExpandedState.set(`${entry.profileId}:${entry.originalGroup}`, true); } closeSettingSearchPanel({ syncHistory: false }); - if (CURRENT_GROUP === entry.group && screenItems && screenItems.style.display !== "none") { + if (CURRENT_GROUP === entry.group && !CURRENT_SETTING_DETAIL && screenItems && screenItems.style.display !== "none") { focusSettingItem(entry.name); return; } @@ -1603,8 +1886,9 @@ async function openSettingSearchPanel(options = {}) { if (pushHistory && !(state.page === "setting" && state.search)) { history.pushState({ page: "setting", - screen: (screenItems && screenItems.style.display !== "none") ? "items" : "groups", + screen: CURRENT_SETTING_DETAIL ? "detail" : ((screenItems && screenItems.style.display !== "none") ? "items" : "groups"), group: CURRENT_GROUP || null, + settingName: CURRENT_SETTING_DETAIL || null, search: true, searchScope: settingSearchScope.type, profileId: settingSearchScope.profileId || null, @@ -1624,14 +1908,6 @@ async function openSettingSearchPanel(options = {}) { }); } -function toggleSettingSearchPanel() { - if (!settingSearchPanel) return; - if (settingSearchPanel.hidden) { - openSettingSearchPanel().catch(() => {}); - } - else closeSettingSearchPanel({ syncHistory: true }); -} - if (btnSettingSearch) { btnSettingSearch.onclick = () => toggleSettingFabMenu(); } @@ -1752,12 +2028,27 @@ function updateSettingSubnavLayoutState() { syncSettingSubnavFixedOffset(); } +function getCurrentSettingCategoryId() { + if (!Array.isArray(SETTINGS?.categories) || !SETTINGS.categories.length) return null; + const meta = (SETTINGS?.groups || []).find((g) => g.group === CURRENT_GROUP); + return meta?.category || null; +} + function getSettingSubnavGroups() { - return getSettingGroupsForDisplay().filter((entry) => + const all = getSettingGroupsForDisplay().filter((entry) => !isSettingProfilesDivider(entry) && + !isSettingCategoryDivider(entry) && !isSettingFavoritesGroup(entry.group) && !isSettingProfileGroup(entry.group) ); + // 카테고리 모드: 서브내비(퀵링크)를 현재 카테고리의 그룹만으로 한정 → footprint 축소. + // 다른 카테고리는 뒤로가기(그룹목록)로 전환. CURRENT_GROUP 이 가상/없으면 전체 유지. + const catId = getCurrentSettingCategoryId(); + if (catId) { + const scoped = all.filter((entry) => entry.category === catId); + if (scoped.length) return scoped; + } + return all; } function getSettingSubnavGroupIndex(group = CURRENT_GROUP) { @@ -1791,6 +2082,7 @@ async function activateSettingGroup(group, pushHistory = true, options = {}) { if (!isCarrotSettingTabActive()) return; const nextGroup = group || CURRENT_GROUP; const previousGroup = CURRENT_GROUP; + CURRENT_SETTING_DETAIL = null; const scrollMode = options.scrollMode || "top"; const animateItems = options.animateItems !== false; const animateGroups = options.animateGroups !== false; @@ -2167,21 +2459,29 @@ async function renderItems(group, options = {}) { const meta = document.getElementById("groupMeta"); const itemsBox = document.getElementById("items"); const renderToken = ++settingRenderToken; + const detailName = String(options.detailName || "").trim(); + const detailMode = Boolean(detailName); const scrollMode = options.scrollMode || "top"; const animateItems = options.animateItems !== false; const requestedScrollTop = Number.isFinite(options.scrollTop) ? options.scrollTop : null; itemsBox.innerHTML = ""; delete itemsBox.dataset.renderedGroup; + delete itemsBox.dataset.renderedDetail; renderSettingSubnav(); - const entries = getSettingItemEntriesForGroup(group); + const allEntries = getSettingItemEntriesForGroup(group); + const detailEntry = detailMode ? getSettingDetailEntry(group, detailName) : null; + const entries = detailMode ? (detailEntry ? [detailEntry] : []) : allEntries; const list = entries.map((entry) => entry.item); const profile = getSettingProfileByGroup(group); if (screenItems) screenItems.classList.toggle("setting-screen-items--profile", Boolean(profile)); - if (meta) meta.textContent = `${group} / ${list.length}`; + if (screenItems) screenItems.classList.toggle("setting-screen-items--detail", detailMode); + CURRENT_SETTING_DETAIL = detailMode ? detailName : null; + if (meta) meta.textContent = `${group} / ${detailMode ? "1" : list.length}`; const groupLabel = getSettingGroupLabel(group); - settingTitle.textContent = (UI_STRINGS[LANG].setting || "Setting") + " - " + groupLabel; - if (itemsTitle) itemsTitle.textContent = groupLabel; + const detailTitle = detailMode && list[0] ? getSettingDetailTitle(list[0]) : ""; + settingTitle.textContent = (UI_STRINGS[LANG].setting || "Setting") + " - " + (detailTitle || groupLabel); + setSettingItemsTitle(detailTitle || groupLabel); let values = {}; try { @@ -2197,6 +2497,24 @@ async function renderItems(group, options = {}) { return; } + if (!list.length && detailMode) { + const empty = document.createElement("div"); + empty.className = "setting-favorites-empty"; + const emptyTitle = document.createElement("div"); + emptyTitle.className = "setting-favorites-empty__title"; + emptyTitle.textContent = getUIText("setting_not_found", "Setting not found"); + const emptyDesc = document.createElement("div"); + emptyDesc.className = "setting-favorites-empty__desc"; + emptyDesc.textContent = detailName; + empty.appendChild(emptyTitle); + empty.appendChild(emptyDesc); + itemsBox.appendChild(empty); + itemsBox.dataset.renderedGroup = group; + itemsBox.dataset.renderedDetail = detailName; + requestAnimationFrame(resetSettingItemsViewport); + return; + } + if (!list.length && isSettingFavoritesGroup(group)) { const empty = document.createElement("div"); empty.className = "setting-favorites-empty"; @@ -2217,7 +2535,7 @@ async function renderItems(group, options = {}) { return; } - if (profile) appendSettingProfileHeader(profile, itemsBox); + if (profile && !detailMode) appendSettingProfileHeader(profile, itemsBox); const profileSectionCounts = new Map(); if (profile) { @@ -2227,12 +2545,58 @@ async function renderItems(group, options = {}) { } let lastProfileGroup = ""; let currentProfileSectionBody = null; + let lastCategorySectionKey = null; + let currentCategoryCardBody = null; + + // 즐겨찾기도 다른 하위메뉴와 같은 카드 박스(공통분모: setting-section-block + + // setting-group-card)에 담는다. 즐겨찾기는 소-섹션이 섞여 있으므로 단일 카드 1개로. + if (!detailMode && isSettingFavoritesGroup(group) && list.length) { + const favBlock = document.createElement("section"); + favBlock.className = animateItems ? "setting-section-block ui-stagger-item" : "setting-section-block"; + if (animateItems) favBlock.style.setProperty("--i", "1"); + const favCard = document.createElement("div"); + favCard.className = "setting-group-card"; + const favBody = document.createElement("div"); + favBody.className = "setting-group-card__body"; + favCard.appendChild(favBody); + favBlock.appendChild(favCard); + itemsBox.appendChild(favBlock); + currentCategoryCardBody = favBody; + } + list.forEach((p, index) => { const name = p.name; const originGroup = entries[index]?.group || group; - if (!(name in UNIT_INDEX)) UNIT_INDEX[name] = 0; + getSettingUnitIndex(name); + + // 카테고리 모드: 소-섹션마다 카드(그룹박스) 생성 (프로필/즐겨찾기 뷰 제외). + // 라벨이 있으면 카드 제목으로, 없으면(단일 직속 섹션) 제목 없는 카드. + if (!detailMode && !profile && !isSettingFavoritesGroup(group) && p.__section) { + const secKey = p.__section.id || ""; + if (secKey !== lastCategorySectionKey) { + lastCategorySectionKey = secKey; + const sectionBlock = document.createElement("section"); + sectionBlock.className = animateItems ? "setting-section-block ui-stagger-item" : "setting-section-block"; + if (animateItems) sectionBlock.style.setProperty("--i", String(Math.min(index + 1, 14))); + const cardLabel = settingNodeLabel(p.__section); + if (cardLabel) { + const cardTitle = document.createElement("div"); + cardTitle.className = "setting-group-card__title"; + cardTitle.textContent = cardLabel; + sectionBlock.appendChild(cardTitle); + } + const card = document.createElement("div"); + card.className = "setting-group-card"; + const cardBody = document.createElement("div"); + cardBody.className = "setting-group-card__body"; + card.appendChild(cardBody); + sectionBlock.appendChild(card); + itemsBox.appendChild(sectionBlock); + currentCategoryCardBody = cardBody; + } + } - if (profile && originGroup !== lastProfileGroup) { + if (!detailMode && profile && originGroup !== lastProfileGroup) { lastProfileGroup = originGroup; const section = document.createElement("div"); section.className = animateItems ? "setting-profile-section ui-stagger-item" : "setting-profile-section"; @@ -2310,38 +2674,115 @@ async function renderItems(group, options = {}) { `; + const controlConfig = getSettingControlConfig(p); + const compactNumeric = !detailMode && controlConfig.kind === "slider"; const ctrl = document.createElement("div"); - ctrl.className = "ctrl"; - - const btnMinus = document.createElement("button"); - btnMinus.type = "button"; - btnMinus.className = "smallBtn"; - btnMinus.textContent = "-"; + ctrl.className = `ctrl ctrl--${compactNumeric ? "value" : controlConfig.kind}`; const val = document.createElement("button"); val.type = "button"; - val.className = "pill val"; - val.setAttribute("aria-label", getUIText("setting_value_edit", "Edit value")); - - const btnPlus = document.createElement("button"); - btnPlus.type = "button"; - btnPlus.className = "smallBtn"; - btnPlus.textContent = "+"; - - const unitBtn = document.createElement("button"); - unitBtn.type = "button"; - unitBtn.className = "smallBtn"; - unitBtn.textContent = "x" + UNIT_CYCLE[UNIT_INDEX[name]]; - - unitBtn.onclick = () => { - UNIT_INDEX[name] = (UNIT_INDEX[name] + 1) % UNIT_CYCLE.length; - unitBtn.textContent = "x" + UNIT_CYCLE[UNIT_INDEX[name]]; - }; - - ctrl.appendChild(btnMinus); - ctrl.appendChild(val); - ctrl.appendChild(btnPlus); - ctrl.appendChild(unitBtn); + val.className = compactNumeric ? "pill val setting-value-compact" : "pill val"; + val.setAttribute("aria-label", compactNumeric + ? getUIText("setting_value_detail", "Open detail") + : getUIText("setting_value_edit", "Edit value")); + + let btnMinus = null; + let btnPlus = null; + let unitBtn = null; + let sliderInput = null; + let toggleInput = null; + let selectInput = null; + const segmentButtons = []; + + if (controlConfig.kind === "toggle") { + const switchLabel = document.createElement("label"); + switchLabel.className = "setting-switch"; + toggleInput = document.createElement("input"); + toggleInput.type = "checkbox"; + toggleInput.className = "setting-switch__input"; + toggleInput.setAttribute("aria-label", title || name); + const switchTrack = document.createElement("span"); + switchTrack.className = "setting-switch__track"; + switchLabel.appendChild(toggleInput); + switchLabel.appendChild(switchTrack); + ctrl.appendChild(switchLabel); + ctrl.appendChild(val); + } else if (controlConfig.kind === "segmented") { + const segmentWrap = document.createElement("div"); + segmentWrap.className = "setting-segments"; + getSettingOptionValues(controlConfig).forEach((optionValue) => { + const button = document.createElement("button"); + button.type = "button"; + button.className = "setting-segment"; + button.dataset.value = String(optionValue); + button.textContent = getSettingOptionLabel(name, optionValue); + button.setAttribute("aria-pressed", "false"); + segmentButtons.push(button); + segmentWrap.appendChild(button); + }); + ctrl.appendChild(segmentWrap); + ctrl.appendChild(val); + } else if (controlConfig.kind === "select") { + selectInput = document.createElement("button"); + selectInput.type = "button"; + selectInput.className = "setting-select setting-select--button"; + selectInput.setAttribute("aria-label", title || name); + selectInput.setAttribute("aria-haspopup", "dialog"); + ctrl.appendChild(selectInput); + ctrl.appendChild(val); + } else if (compactNumeric) { + btnMinus = document.createElement("button"); + btnMinus.type = "button"; + btnMinus.className = "smallBtn setting-value-arrow setting-value-arrow--prev"; + btnMinus.textContent = "-"; + btnMinus.setAttribute("aria-label", getUIText("setting_value_previous", "Previous value")); + + btnPlus = document.createElement("button"); + btnPlus.type = "button"; + btnPlus.className = "smallBtn setting-value-arrow setting-value-arrow--next"; + btnPlus.textContent = "+"; + btnPlus.setAttribute("aria-label", getUIText("setting_value_next", "Next value")); + + unitBtn = document.createElement("button"); + unitBtn.type = "button"; + unitBtn.className = "setting-unit-cycle"; + setSettingUnitButtonLabel(unitBtn, name); + + ctrl.appendChild(btnMinus); + ctrl.appendChild(val); + ctrl.appendChild(btnPlus); + } else { + const sliderWrap = document.createElement("div"); + sliderWrap.className = "setting-slider"; + sliderInput = document.createElement("input"); + sliderInput.type = "range"; + sliderInput.className = "setting-slider__input"; + sliderInput.min = String(controlConfig.min); + sliderInput.max = String(controlConfig.max); + sliderInput.step = String(controlConfig.unit); + sliderInput.setAttribute("aria-label", title || name); + sliderWrap.appendChild(sliderInput); + + btnMinus = document.createElement("button"); + btnMinus.type = "button"; + btnMinus.className = "smallBtn setting-step setting-step--minus"; + btnMinus.textContent = "-"; + + btnPlus = document.createElement("button"); + btnPlus.type = "button"; + btnPlus.className = "smallBtn setting-step setting-step--plus"; + btnPlus.textContent = "+"; + + unitBtn = document.createElement("button"); + unitBtn.type = "button"; + unitBtn.className = "setting-unit-cycle"; + setSettingUnitButtonLabel(unitBtn, name); + + ctrl.appendChild(sliderWrap); + ctrl.appendChild(btnMinus); + ctrl.appendChild(val); + ctrl.appendChild(btnPlus); + } top.appendChild(left); top.appendChild(ctrl); @@ -2352,10 +2793,15 @@ async function renderItems(group, options = {}) { el.appendChild(top); el.appendChild(d); - (currentProfileSectionBody || itemsBox).appendChild(el); + if (unitBtn) { + el.classList.add("setting--has-unit-cycle"); + el.appendChild(unitBtn); + } + (currentProfileSectionBody || currentCategoryCardBody || itemsBox).appendChild(el); const cur = (name in values) ? values[name] : p.default; - val.textContent = String(cur); + syncSettingControlState(el, cur); + val.dataset.committedValue = String(cur); function normalizeSettingValue(raw) { const text = String(raw).trim(); @@ -2386,7 +2832,8 @@ async function renderItems(group, options = {}) { } else { await setParam(name, next); } - val.textContent = String(next); + syncSettingControlState(el, next); + val.dataset.committedValue = String(next); if (!profile) { cacheSettingValue(name, next, group); if (originGroup !== group) cacheSettingValue(name, next, originGroup); @@ -2396,77 +2843,214 @@ async function renderItems(group, options = {}) { } } - async function editValueDirect() { - const input = await appPrompt( - getUIText("setting_value_prompt", "Enter value for {name}\nRange: {min} - {max}", { - name, - min: p.min, - max: p.max, - }), - { - title: getUIText("setting_value_title", "Edit value"), - defaultValue: val.textContent, - placeholder: String(p.default), - confirmLabel: getUIText("ok", "OK"), - showCancel: false, - defaultActionLabel: getUIText("default_value", "Default"), - defaultActionValue: { settingDefaultAction: true, value: String(p.default) }, + async function applyDelta(sign) { + const step = getSettingUnitValue(name); + let curv = Number(val.dataset.rawValue); + if (Number.isNaN(curv)) curv = Number(p.default); + + let next = curv + sign * step; + next = clamp(next, Number(p.min), Number(p.max)); + + if (Number.isInteger(Number(p.min)) && Number.isInteger(Number(p.max)) && Number.isInteger(step)) { + next = Math.round(next); + } + + await commitSettingValue(next); + } + + let deltaBusy = false; + async function requestDelta(sign) { + if (deltaBusy) return; + deltaBusy = true; + try { + await applyDelta(sign); + } finally { + deltaBusy = false; + } + } + + function bindDeltaButton(button, sign) { + if (!button) return; + + let holdTimer = null; + let repeatTimer = null; + let pointerActive = false; + let activePointerId = null; + let ignoreNextClick = false; + + function clearTimers() { + if (holdTimer) { + clearTimeout(holdTimer); + holdTimer = null; } - ); - if (input === null) return; + if (repeatTimer) { + clearTimeout(repeatTimer); + repeatTimer = null; + } + } - if (input?.settingDefaultAction) { - const defaultValue = input.value; - const ok = await appConfirm(getUIText( - "default_value_confirm", - "Restore {name} to default value ({value})?", - { name, value: defaultValue } - ), { - title: getUIText("default_value", "Default"), - confirmLabel: getUIText("ok", "OK"), - }); - if (!ok) return; + function stopHold() { + clearTimers(); + pointerActive = false; + button.classList.remove("is-holding"); + if (activePointerId !== null && typeof button.releasePointerCapture === "function") { + try { + button.releasePointerCapture(activePointerId); + } catch (_) { + /* pointer capture may already be released by the browser */ + } + } + activePointerId = null; + window.setTimeout(() => { + ignoreNextClick = false; + }, 0); + } + + function repeatDelta() { + if (!pointerActive) return; + requestDelta(sign); + repeatTimer = window.setTimeout(repeatDelta, 120); + } + + button.addEventListener("pointerdown", (event) => { + if (event.button !== undefined && event.button !== 0) return; + event.stopPropagation(); + event.preventDefault(); + stopHold(); + pointerActive = true; + activePointerId = event.pointerId; + ignoreNextClick = true; + button.classList.add("is-holding"); + if (typeof button.setPointerCapture === "function") { + try { + button.setPointerCapture(activePointerId); + } catch (_) { + /* pointer capture is best-effort for repeated input */ + } + } + requestDelta(sign); + holdTimer = window.setTimeout(repeatDelta, 420); + }); - const nextDefault = normalizeSettingValue(defaultValue); - if (nextDefault === null) { - showAppToast(getUIText("setting_value_invalid", "Enter a valid number."), { tone: "error" }); + button.addEventListener("pointerup", (event) => { + event.stopPropagation(); + stopHold(); + }); + button.addEventListener("pointercancel", stopHold); + button.addEventListener("lostpointercapture", stopHold); + button.addEventListener("click", (event) => { + event.stopPropagation(); + if (ignoreNextClick) { + event.preventDefault(); return; } - if (String(nextDefault) === String(val.textContent)) return; - await commitSettingValue(nextDefault); - return; - } + requestDelta(sign); + }); + } + async function promptSettingValue() { + const input = await appPrompt( + `min=${p.min}, max=${p.max}, default=${p.default}`, + { + title: title || name, + defaultValue: val.dataset.rawValue ?? String(p.default), + placeholder: String(p.default), + confirmLabel: getUIText("ok", "OK"), + cancelLabel: getUIText("cancel", "Cancel"), + showCancel: true, + }, + ); + if (input === null) return; const next = normalizeSettingValue(input); if (next === null) { showAppToast(getUIText("setting_value_invalid", "Enter a valid number."), { tone: "error" }); return; } - if (String(next) === String(val.textContent)) return; + if (String(next) === String(val.dataset.rawValue)) return; await commitSettingValue(next); } - async function applyDelta(sign) { - const step = UNIT_CYCLE[UNIT_INDEX[name]]; - let curv = Number(val.textContent); - if (Number.isNaN(curv)) curv = Number(p.default); + async function promptSettingChoice() { + const current = String(val.dataset.rawValue ?? p.default); + const choices = getSettingOptionValues(controlConfig).map((optionValue) => { + const optionText = String(optionValue); + const isCurrent = optionText === current; + return { + label: getSettingOptionLabel(name, optionValue), + value: optionText, + selected: isCurrent, + className: "setting-choice-option", + }; + }); + const selected = await openAppDialog({ + mode: "choice", + choiceLayout: "value-grid", + title: title || name, + html: true, + messageHtml: `
${escapeHtml(name)}
min=${escapeHtml(p.min)}, max=${escapeHtml(p.max)}, default=${escapeHtml(p.default)}
`, + choices, + cancelLabel: getUIText("cancel", "Cancel"), + showCancel: true, + }); + if (selected === null) return; + const next = normalizeSettingValue(selected); + if (next === null || String(next) === String(val.dataset.rawValue)) return; + await commitSettingValue(next); + } - let next = curv + sign * step; - next = clamp(next, Number(p.min), Number(p.max)); + if (toggleInput) { + toggleInput.onchange = () => { + commitSettingValue(toggleInput.checked ? 1 : 0); + }; + } - if (Number.isInteger(p.min) && Number.isInteger(p.max) && Number.isInteger(step)) { - next = Math.round(next); - } + if (unitBtn) { + unitBtn.onclick = (event) => { + event.stopPropagation(); + cycleSettingUnitValue(name); + setSettingUnitButtonLabel(unitBtn, name); + }; + } - await commitSettingValue(next); + bindDeltaButton(btnMinus, -1); + bindDeltaButton(btnPlus, +1); + + val.onclick = (event) => { + event.stopPropagation(); + if (controlConfig.kind === "slider") promptSettingValue(); + }; + + segmentButtons.forEach((button) => { + button.onclick = (event) => { + event.stopPropagation(); + const next = normalizeSettingValue(button.dataset.value); + if (next === null || String(next) === String(val.dataset.rawValue)) return; + commitSettingValue(next); + }; + }); + + if (selectInput) { + selectInput.onclick = (event) => { + event.stopPropagation(); + promptSettingChoice(); + }; } - btnMinus.onclick = () => applyDelta(-1); - val.onclick = editValueDirect; - btnPlus.onclick = () => applyDelta(+1); + if (sliderInput) { + sliderInput.oninput = () => { + const next = normalizeSettingValue(sliderInput.value); + if (next !== null) syncSettingControlState(el, next); + }; + sliderInput.onchange = () => { + const next = normalizeSettingValue(sliderInput.value); + if (next === null || String(next) === String(val.dataset.committedValue ?? val.dataset.rawValue)) return; + commitSettingValue(next); + }; + } }); itemsBox.dataset.renderedGroup = group; + if (detailMode) itemsBox.dataset.renderedDetail = detailName; scheduleSettingOverflowSync(itemsBox); if (pendingSettingFocus?.group === group) { @@ -2498,7 +3082,7 @@ function bindSettingFavoriteLongPress() { } function isIgnoredFavoritePressTarget(target) { - return Boolean(target?.closest?.(".ctrl, button, input, select, textarea, a")); + return isSettingInlineControlTarget(target); } itemsBox.addEventListener("pointerdown", (event) => { @@ -2519,6 +3103,10 @@ function bindSettingFavoriteLongPress() { if (!press || press.row !== row) return; press.fired = true; row.classList.remove("is-longpressing"); + row.dataset.settingSuppressClick = "1"; + window.setTimeout(() => { + if (row.dataset.settingSuppressClick === "1") delete row.dataset.settingSuppressClick; + }, 420); toggleSettingFavorite(row.dataset.settingName).catch(() => {}); }, SETTING_FAVORITES_LONG_PRESS_MS), }; @@ -2584,7 +3172,11 @@ async function syncSettingViewportLayout(options = {}) { syncSettingGroupChrome(targetGroup); if (typeof centerActiveSettingSubnavTab === "function") centerActiveSettingSubnavTab("auto"); if (!hasRenderedSettingItems(targetGroup)) { - await renderItems(targetGroup, { scrollMode: "restore", animateItems }); + await renderItems(targetGroup, { + detailName: CURRENT_SETTING_DETAIL || "", + scrollMode: "restore", + animateItems, + }); } return; } @@ -2594,7 +3186,11 @@ async function syncSettingViewportLayout(options = {}) { showSettingScreen("items", false); if (typeof centerActiveSettingSubnavTab === "function") centerActiveSettingSubnavTab("auto"); if (!hasRenderedSettingItems(CURRENT_GROUP)) { - await renderItems(CURRENT_GROUP, { scrollMode: "restore", animateItems }); + await renderItems(CURRENT_GROUP, { + detailName: CURRENT_SETTING_DETAIL || "", + scrollMode: "restore", + animateItems, + }); } } else { showSettingScreen("groups", false); @@ -2638,6 +3234,7 @@ window.addEventListener("carrot:paramsrestored", (event) => { settingRestoreRefreshTimer = window.setTimeout(() => { settingRestoreRefreshTimer = null; renderItems(CURRENT_GROUP, { + detailName: CURRENT_SETTING_DETAIL || "", forceValues: true, scrollMode: "restore", scrollTop: currentTop, diff --git a/selfdrive/carrot/web/js/shared/i18n.js b/selfdrive/carrot/web/js/shared/i18n.js index 8b6e7fe194..faf3042065 100644 --- a/selfdrive/carrot/web/js/shared/i18n.js +++ b/selfdrive/carrot/web/js/shared/i18n.js @@ -363,7 +363,12 @@ function setWebLanguage(lang, options = {}) { const currentTop = typeof getSettingItemsScrollTop === "function" ? getSettingItemsScrollTop() : 0; - renderItems(CURRENT_GROUP, { scrollMode: "restore", scrollTop: currentTop, animateItems: false }); + renderItems(CURRENT_GROUP, { + detailName: (typeof CURRENT_SETTING_DETAIL !== "undefined" && CURRENT_SETTING_DETAIL) ? CURRENT_SETTING_DETAIL : "", + scrollMode: "restore", + scrollTop: currentTop, + animateItems: false, + }); } } if (dispatch) { diff --git a/selfdrive/carrot/web/js/shared/ui/dialog.js b/selfdrive/carrot/web/js/shared/ui/dialog.js index 1594639806..9f5f5c685c 100644 --- a/selfdrive/carrot/web/js/shared/ui/dialog.js +++ b/selfdrive/carrot/web/js/shared/ui/dialog.js @@ -7,6 +7,13 @@ let appToastRemoveTimer = null; let activeAppDialog = null; let appDialogSerial = 0; +const APP_DIALOG_VARIANT_CLASSES = [ + "app-dialog--choice", + "app-dialog--choice-list", + "app-dialog--choice-grid", + "app-dialog--choice-value-grid", +]; + function syncModalBodyLock() { const hasOpenDialog = @@ -65,6 +72,39 @@ function showAppToast(message, opts = {}) { /* ── Dialog (alert / confirm / prompt / choice) ─────────── */ +function resetAppDialogPresentation() { + if (appDialog) appDialog.classList.remove(...APP_DIALOG_VARIANT_CLASSES); + if (appDialogChoices) { + appDialogChoices.className = "app-dialog__choices"; + appDialogChoices.style.removeProperty("--app-dialog-choice-columns"); + } +} + +function appDialogChoiceText(choice) { + if (!choice || choice.labelHtml) return ""; + return String(choice.label ?? "").trim(); +} + +function inferAppDialogChoiceLayout(choices, options = {}) { + const explicit = String(options.choiceLayout || options.choiceKind || "").trim(); + if (explicit === "grid" || explicit === "value-grid" || explicit === "values") return "value-grid"; + if (explicit === "list" || explicit === "action-list" || explicit === "actions") return "list"; + + const shortValueChoices = choices.length > 4 && choices.every((choice) => { + const text = appDialogChoiceText(choice); + return text && text.length <= 5 && !choice.danger && /^[+-]?(?:\d+|\d+\.\d+|[A-Za-z]{1,4})$/.test(text); + }); + return shortValueChoices ? "value-grid" : "list"; +} + +function appDialogChoiceColumns(count, options = {}) { + const explicit = Number(options.choiceColumns || options.columns); + if (Number.isInteger(explicit) && explicit >= 2 && explicit <= 6) return explicit; + if (count <= 4) return Math.max(2, count); + if (count <= 25) return 5; + return 4; +} + function resolveAppDialog(result) { if (!activeAppDialog || !appDialog) return; @@ -80,6 +120,7 @@ function resolveAppDialog(result) { } appDialog.hidden = true; syncModalBodyLock(); + resetAppDialogPresentation(); if (appDialogChoices) { appDialogChoices.hidden = true; appDialogChoices.innerHTML = ""; @@ -140,12 +181,20 @@ function openAppDialog(options = {}) { const defaultActionLabel = options.defaultActionLabel || ""; const hasDefaultAction = mode === "prompt" && Boolean(defaultActionLabel); const choices = Array.isArray(options.choices) - ? options.choices.filter((choice) => choice && (choice.label || choice.labelHtml)) + ? options.choices.filter((choice) => choice && (choice.label != null || choice.labelHtml)) : []; const hasChoices = choices.length > 0; const isChoice = mode === "choice" || hasChoices; + const choiceLayout = hasChoices ? inferAppDialogChoiceLayout(choices, options) : ""; const showCancel = mode !== "alert" && options.showCancel !== false; + resetAppDialogPresentation(); + if (appDialog && hasChoices) { + appDialog.classList.add("app-dialog--choice"); + appDialog.classList.add(choiceLayout === "value-grid" ? "app-dialog--choice-grid" : "app-dialog--choice-list"); + if (choiceLayout === "value-grid") appDialog.classList.add("app-dialog--choice-value-grid"); + } + appDialogTitle.textContent = title; if (useHtml) appDialogBody.innerHTML = String(messageHtml || message); else appDialogBody.textContent = String(message); @@ -180,12 +229,22 @@ function openAppDialog(options = {}) { if (appDialogChoices) { appDialogChoices.innerHTML = ""; appDialogChoices.hidden = !hasChoices; + appDialogChoices.className = `app-dialog__choices app-dialog__choices--${choiceLayout || "list"}`; + if (choiceLayout === "value-grid") { + appDialogChoices.style.setProperty("--app-dialog-choice-columns", String(appDialogChoiceColumns(choices.length, options))); + } else { + appDialogChoices.style.removeProperty("--app-dialog-choice-columns"); + } for (const choice of choices) { const button = document.createElement("button"); button.type = "button"; let btnClass = choice.danger ? "btn btn--danger app-dialog__choiceBtn" : "btn app-dialog__choiceBtn"; + btnClass += choiceLayout === "value-grid" + ? " app-dialog__choiceBtn--value" + : " app-dialog__choiceBtn--action"; + if (choice.current || choice.selected) btnClass += " is-current"; if (choice.className) btnClass += " " + choice.className; button.className = btnClass; if (choice.labelHtml) { @@ -193,7 +252,6 @@ function openAppDialog(options = {}) { } else { button.textContent = String(choice.label); } - button.style.cssText = "text-align:left; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;"; button.addEventListener("click", () => resolveAppDialog(choice.value)); appDialogChoices.appendChild(button); } @@ -224,8 +282,14 @@ function openAppDialog(options = {}) { appDialogInput.focus(); appDialogInput.select(); } else if (hasChoices && appDialogChoices) { - const firstChoice = appDialogChoices.querySelector("button"); - if (firstChoice && typeof firstChoice.focus === "function") firstChoice.focus(); + const currentChoice = appDialogChoices.querySelector(".is-current"); + const firstChoice = currentChoice || appDialogChoices.querySelector("button"); + if (firstChoice && typeof firstChoice.focus === "function") { + firstChoice.focus({ preventScroll: Boolean(currentChoice) }); + if (currentChoice && typeof currentChoice.scrollIntoView === "function") { + currentChoice.scrollIntoView({ block: "center", inline: "nearest" }); + } + } } else if (mode === "choice" && appDialogCancel) { appDialogCancel.focus(); } else { diff --git a/selfdrive/carrot/web/js/shared/ui/navigation.js b/selfdrive/carrot/web/js/shared/ui/navigation.js index ab4811c2bb..4bf0ed7b4b 100644 --- a/selfdrive/carrot/web/js/shared/ui/navigation.js +++ b/selfdrive/carrot/web/js/shared/ui/navigation.js @@ -698,9 +698,20 @@ btnBackCar.onclick = () => history.back(); carTitle.onclick = () => history.back(); modelTitle.onclick = () => showCarScreen("makers"); -if (btnBackGroups) btnBackGroups.onclick = () => history.back(); -settingTitle.onclick = () => history.back(); -if (itemsTitle) itemsTitle.onclick = () => history.back(); +function goBackUnlessSettingSplit() { + if ( + CURRENT_PAGE === "setting" && + typeof isCompactLandscapeMode === "function" && + isCompactLandscapeMode() + ) { + return; + } + history.back(); +} + +if (btnBackGroups) btnBackGroups.onclick = goBackUnlessSettingSplit; +settingTitle.onclick = goBackUnlessSettingSplit; +if (itemsTitle) itemsTitle.onclick = goBackUnlessSettingSplit; btnBackBranch.onclick = () => history.back(); branchTitle.onclick = () => history.back(); diff --git a/selfdrive/carrot_settings.json b/selfdrive/carrot_settings.json index ce33edc1dd..cc9bb022f2 100644 --- a/selfdrive/carrot_settings.json +++ b/selfdrive/carrot_settings.json @@ -1,5 +1,581 @@ { "apilot": 20220111, + "menu": [ + { + "id": "DRIVING", + "ko": "주행 제어", + "en": "Driving", + "zh": "驾驶控制", + "groups": [ + { + "id": "START_AUTO", + "ko": "시작·오토", + "en": "Start & Auto", + "zh": "启动·自动", + "groups": [ + { + "id": "BASIC_START", + "ko": "시작 동작", + "en": "Engage", + "zh": "起步动作", + "params": [ + "AlwaysLateral", + "AutoEngage", + "DisableMinSteerSpeed" + ] + }, + { + "id": "BASIC_AUTOCRUISE", + "ko": "오토크루즈", + "en": "Auto Cruise", + "zh": "自动巡航", + "params": [ + "AutoCruiseControl", + "AutoGasTokSpeed", + "AutoGasCancelSpeed", + "AutoGasSyncSpeed", + "CruiseOnDist" + ] + } + ] + }, + { + "id": "BUTTON_PRESET", + "ko": "버튼·프리셋", + "en": "Buttons & Presets", + "zh": "按钮·预设", + "groups": [ + { + "id": "BUTTON_MODE", + "ko": "버튼 모드", + "en": "Button Modes", + "zh": "按钮模式", + "params": [ + "CruiseButtonMode", + "CancelButtonMode", + "LfaButtonMode", + "PaddleMode" + ] + }, + { + "id": "BUTTON_UNIT", + "ko": "속도 단위", + "en": "Speed Units", + "zh": "速度单位", + "params": [ + "CruiseSpeedUnit", + "CruiseSpeedUnitBasic", + "CruiseButtonLongDelay" + ] + }, + { + "id": "BUTTON_TEST", + "ko": "버튼 테스트", + "en": "Button Test", + "zh": "按钮测试", + "params": [ + "CruiseButtonTest1", + "CruiseButtonTest2", + "CruiseButtonTest3" + ] + }, + { + "id": "BASIC_SPEEDTABLE", + "ko": "속도 프리셋", + "en": "Speed Presets", + "zh": "速度预设", + "params": [ + "CruiseSpeed1", + "CruiseSpeed2", + "CruiseSpeed3", + "CruiseSpeed4", + "CruiseSpeed5" + ] + } + ] + }, + { + "id": "STEER", + "ko": "차량 조향", + "en": "Steering", + "zh": "转向", + "groups": [ + { + "id": "STEER_CENTER", + "ko": "중앙 보정", + "en": "Centering", + "zh": "居中校正", + "params": [ + "PathOffset", + "CameraYawTrimDeg" + ] + }, + { + "id": "STEER_FEEL", + "ko": "조향감", + "en": "Steering Feel", + "zh": "转向手感", + "params": [ + "SteerActuatorDelay", + "LatSmoothSec", + "LatSuspendAngleDeg", + "CustomSR", + "SteerRatioRate" + ] + }, + { + "id": "STEER_LANECHANGE", + "ko": "차로 변경 (자동 턴)", + "en": "Lane Change (Auto Turn)", + "zh": "变道 (自动转向)", + "params": [ + "LaneChangeNeedTorque", + "LaneChangeDelay", + "LaneChangeBsd", + "LaneLineCheck", + "AutoTurnControl", + "AutoTurnControlSpeedTurn", + "AutoTurnControlTurnEnd", + "AutoTurnMapChange" + ] + }, + { + "id": "STEER_LANEMODE", + "ko": "레인모드", + "en": "Lane Mode", + "zh": "车道模式", + "params": [ + "LatMpcPathCost", + "LatMpcMotionCost", + "LatMpcAccelCost", + "LatMpcJerkCost", + "LatMpcSteeringRateCost", + "LatMpcInputOffset", + "UseLaneLineSpeed", + "UseLaneLineCurveSpeed", + "AdjustLaneOffset" + ] + }, + { + "id": "STEER_TORQUE", + "ko": "고급 토크", + "en": "Advanced Torque", + "zh": "高级扭矩", + "groups": [ + { + "id": "STEER_TORQUE_COEFF", + "ko": "토크 계수", + "en": "Torque Coefficients", + "zh": "扭矩系数", + "params": [ + "LateralTorqueCustom", + "LateralTorqueAccelFactor", + "LateralTorqueFriction", + "LateralTorqueKpV", + "LateralTorqueKiV", + "LateralTorqueKf", + "LateralTorqueKd" + ] + }, + { + "id": "STEER_TORQUE_LIMIT", + "ko": "조향 제한", + "en": "Steering Limits", + "zh": "转向限制", + "params": [ + "CustomSteerMax", + "CustomSteerDeltaUp", + "CustomSteerDeltaDown", + "CustomSteerDeltaUpLC", + "CustomSteerDeltaDownLC" + ] + } + ] + } + ] + }, + { + "id": "SPEED", + "ko": "속도·감속", + "en": "Speed & Decel", + "zh": "速度与减速", + "groups": [ + { + "id": "SPEED_CAMERA", + "ko": "과속카메라", + "en": "Speed Camera", + "zh": "测速摄像头", + "params": [ + "AutoNaviSpeedCtrlMode", + "AutoNaviSpeedCtrlEnd", + "AutoNaviSpeedDecelRate", + "AutoNaviSpeedSafetyFactor", + "AutoNaviCountDownMode" + ] + }, + { + "id": "SPEED_LIMIT", + "ko": "제한속도", + "en": "Speed Limit", + "zh": "限速", + "params": [ + "AutoRoadSpeedLimitOffset", + "AutoRoadSpeedAdjust", + "AutoSpeedUptoRoadSpeedLimit" + ] + }, + { + "id": "SPEED_BUMP", + "ko": "방지턱", + "en": "Speed Bump", + "zh": "减速带", + "params": [ + "AutoNaviSpeedBumpTime", + "AutoNaviSpeedBumpSpeed" + ] + }, + { + "id": "SPEED_CURVE", + "ko": "커브·턴", + "en": "Curve & Turn", + "zh": "弯道与转弯", + "params": [ + "AutoCurveSpeedFactor", + "AutoCurveSpeedLowerLimit", + "TurnSpeedControlMode", + "MapTurnSpeedFactor", + "ModelTurnSpeedFactor", + "ApplyModelSpeed" + ] + }, + { + "id": "SPEED_TRAFFIC", + "ko": "신호감지", + "en": "Traffic Light", + "zh": "信号灯", + "params": [ + "TrafficLightDetectMode", + "TrafficStopDistanceAdjust" + ] + } + ] + }, + { + "id": "CRUISE", + "ko": "크루즈·차간", + "en": "Cruise & Gap", + "zh": "巡航与车距", + "groups": [ + { + "id": "CRUISE_ACCEL", + "ko": "가속 성향", + "en": "Accel Profile", + "zh": "加速特性", + "groups": [ + { + "id": "CRUISE_DRIVE_MODE", + "ko": "드라이브 모드", + "en": "Drive Mode", + "zh": "驾驶模式", + "params": [ + "MyDrivingMode", + "MyDrivingModeAuto" + ] + }, + { + "id": "CRUISE_ACCEL_TABLE", + "ko": "속도별 가속값", + "en": "Speed Accel Table", + "zh": "速度加速表", + "params": [ + "CruiseMaxVals0", + "CruiseMaxVals1", + "CruiseMaxVals2", + "CruiseMaxVals3", + "CruiseMaxVals4", + "CruiseMaxVals5", + "CruiseMaxVals6" + ] + } + ] + }, + { + "id": "CRUISE_STOPGO", + "ko": "정차·재출발", + "en": "Stop & Resume", + "zh": "停车与再起步", + "params": [ + "StopDistanceCarrot", + "StoppingAccel", + "VEgoStopping", + "AChangeCostStarting" + ] + }, + { + "id": "CRUISE_LONGTUNE", + "ko": "가감속 튜닝", + "en": "Accel/Brake Tuning", + "zh": "加减速调校", + "params": [ + "LongTuningKpV", + "LongTuningKiV", + "LongTuningKf", + "LongActuatorDelay" + ] + }, + { + "id": "CRUISE_GAP", + "ko": "차간거리", + "en": "Follow Gap", + "zh": "车距", + "params": [ + "TFollowGap1", + "TFollowGap2", + "TFollowGap3", + "TFollowGap4", + "DynamicTFollow", + "DynamicTFollowLC", + "EnableSpeedTF", + "TFollowDecelBoost" + ] + }, + { + "id": "CRUISE_LEAD", + "ko": "선행차 반응", + "en": "Lead Response", + "zh": "前车响应", + "params": [ + "JLeadFactor3", + "RadarReactionFactor" + ] + }, + { + "id": "CRUISE_CARROT", + "ko": "당근 크루즈", + "en": "Carrot Cruise", + "zh": "Carrot巡航", + "params": [ + "CruiseEcoControl", + "CarrotCruiseDecel", + "CarrotCruiseAtcDecel" + ] + } + ] + } + ] + }, + { + "id": "VEHICLE", + "ko": "차량·하드웨어", + "en": "Vehicle & Hardware", + "zh": "车辆与硬件", + "groups": [ + { + "id": "VEH_HYUNDAI", + "ko": "현대·기아", + "en": "Hyundai·Kia", + "zh": "现代·起亚", + "params": [ + "HyundaiCameraSCC", + "IsLdwsCar", + "HapticFeedbackWhenSpeedCamera" + ] + }, + { + "id": "VEH_CANFD", + "ko": "CANFD·HDA", + "en": "CANFD·HDA", + "zh": "CANFD·HDA", + "params": [ + "CanfdHDA2", + "CanfdDebug", + "HDPuse" + ] + }, + { + "id": "VEH_RADAR", + "ko": "레이더", + "en": "Radar", + "zh": "雷达", + "params": [ + "EnableRadarTracks", + "RadarLatFactor", + "EnableCornerRadar" + ] + }, + { + "id": "VEH_DM", + "ko": "운전자 모니터링", + "en": "Driver Monitor", + "zh": "驾驶员监控", + "params": [ + "DisableDM", + "MuteDoor", + "MuteSeatbelt" + ] + }, + { + "id": "VEH_AUX", + "ko": "차량 보조", + "en": "Vehicle Aux", + "zh": "车辆辅助", + "params": [ + "MaxAngleFrames", + "SpeedFromPCM" + ] + } + ] + }, + { + "id": "DISPLAY", + "ko": "화면 표시", + "en": "Display", + "zh": "显示", + "groups": [ + { + "id": "DISP_SCREEN", + "ko": "정보 표시", + "en": "On-screen Info", + "zh": "信息显示", + "params": [ + "ShowDebugUI", + "ShowTpms", + "ShowDateTime", + "ShowPathEnd", + "ShowDeviceState", + "ShowLaneInfo", + "ShowRadarInfo", + "ShowRouteInfo", + "ShowPlotMode" + ] + }, + { + "id": "DISP_PATH", + "ko": "경로 표시", + "en": "Path Display", + "zh": "路径显示", + "params": [ + "CarrotTireTrajectory", + "ShowPathMode", + "ShowPathColor", + "ShowPathColorCruiseOff", + "ShowPathModeLane", + "ShowPathColorLane" + ] + }, + { + "id": "DISP_BRIGHT", + "ko": "밝기·주행화면", + "en": "Brightness & View", + "zh": "亮度与视图", + "params": [ + "ShowCustomBrightness", + "ShowModelView" + ] + }, + { + "id": "DISP_HUD", + "ko": "외부 HUD", + "en": "External HUD", + "zh": "外部HUD", + "groups": [ + { + "id": "HUD_BASIC", + "ko": "기본", + "en": "Basic", + "zh": "基本", + "params": [ + "ClusterHud", + "ClusterHudBrightness", + "ClusterHudTheme" + ] + }, + { + "id": "HUD_SCREEN", + "ko": "화면·카메라", + "en": "Screen & Camera", + "zh": "画面·摄像头", + "params": [ + "ClusterHudEncoder", + "ClusterHudLiveFps", + "ClusterHudScreenMode", + "ClusterHudCameraViewMode" + ] + }, + { + "id": "HUD_RADAR", + "ko": "레이더 표시", + "en": "Radar Display", + "zh": "雷达显示", + "params": [ + "ClusterHudRadarInfo", + "ClusterHudRadarDisplay", + "ClusterHudRadarSourceColor" + ] + }, + { + "id": "HUD_PERF", + "ko": "성능·디버그", + "en": "Performance & Debug", + "zh": "性能·调试", + "params": [ + "ClusterHudCoreMode", + "ClusterHudPriority", + "ClusterHudDebug" + ] + } + ] + } + ] + }, + { + "id": "SYSTEM", + "ko": "시스템", + "en": "System", + "zh": "系统", + "groups": [ + { + "id": "SYS_RECORD", + "ko": "녹화·전원", + "en": "Record & Power", + "zh": "录制与电源", + "params": [ + "RecordRoadCam", + "MaxTimeOffroadMin" + ] + }, + { + "id": "SYS_NET", + "ko": "네트워크·지도", + "en": "Network & Map", + "zh": "网络与地图", + "params": [ + "HotspotOnBoot", + "MapboxStyle" + ] + }, + { + "id": "SYS_SOUND", + "ko": "사운드", + "en": "Sound", + "zh": "声音", + "params": [ + "SoundVolumeAdjust", + "SoundVolumeAdjustEngage" + ] + }, + { + "id": "SYS_SW", + "ko": "소프트웨어", + "en": "Software", + "zh": "软件", + "params": [ + "SoftwareMenu" + ] + } + ] + } + ], "params": [ { "group": "조향일반", @@ -9,13 +585,13 @@ "egroup": "LAT", "etitle": "Lane Deviation Left/Right Correction(0)", "edescr": "LaneMode only — (-)Left / (+)Right", + "cgroup": "转向", + "ctitle": "车道偏移左右修正(0)", + "cdescr": "仅限车道模式 — (-)左侧 / (+)右侧", "min": -150, "max": 150, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "车道偏移左右修正(0)", - "cdescr": "仅限车道模式 — (-)左侧 / (+)右侧" + "unit": 1 }, { "group": "조향일반", @@ -25,13 +601,13 @@ "egroup": "LAT", "etitle": "Camera YAW Correction(0)", "edescr": "Fine tunes camera yaw alignment.(+: right)", + "cgroup": "转向", + "ctitle": "摄像头YAW校正(0)", + "cdescr": "微调摄像头左右(YAW)值(+: right)", "min": -200, "max": 200, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "摄像头YAW校正(0)", - "cdescr": "微调摄像头左右(YAW)值(+: right)" + "unit": 1 }, { "group": "조향토크", @@ -41,13 +617,13 @@ "egroup": "LAT_TQ", "etitle": "_LateralTorqueCustom(0)", "edescr": "Use Custom Torque(TorqueAccelFactor, FrictionFactor)", + "cgroup": "转向", + "ctitle": "自定义转向力矩(0)", + "cdescr": "使用自定义力矩 (TorqueAccelFactor, FrictionFactor)", "min": 0, "max": 1, "default": 1, - "unit": 1, - "cgroup": "转向", - "ctitle": "自定义转向力矩(0)", - "cdescr": "使用自定义力矩 (TorqueAccelFactor, FrictionFactor)" + "unit": 1 }, { "group": "조향토크", @@ -57,13 +633,13 @@ "egroup": "LAT_TQ", "etitle": "_LateralTorqueAccelFactor*0.001(2500)", "edescr": "As the value decreases, the torque becomes stronger up to a point, then weakens — Uses the value at the strongest point.", + "cgroup": "转向", + "ctitle": "转向力矩加速系数*0.001(2500)", + "cdescr": "随着值减小,力矩会增强到某一点然后变弱 — 请使用力矩最强点的值。", "min": 1000, "max": 6000, "default": 2500, - "unit": 100, - "cgroup": "转向", - "ctitle": "转向力矩加速系数*0.001(2500)", - "cdescr": "随着值减小,力矩会增强到某一点然后变弱 — 请使用力矩最强点的值。" + "unit": 100 }, { "group": "조향토크", @@ -73,13 +649,13 @@ "egroup": "LAT_TQ", "etitle": "_LateralTorqueFriction*0.001(100)", "edescr": "More fine steering at higher values — 0-50 recommended", + "cgroup": "转向", + "ctitle": "转向力矩摩擦系数*0.001(100)", + "cdescr": "值越高微操越多 — 推荐 0-50", "min": 0, "max": 1000, "default": 100, - "unit": 10, - "cgroup": "转向", - "ctitle": "转向力矩摩擦系数*0.001(100)", - "cdescr": "值越高微操越多 — 推荐 0-50" + "unit": 10 }, { "group": "조향토크", @@ -89,13 +665,13 @@ "egroup": "LAT_TQ", "etitle": "_LateralTorqueKp*0.01(100)", "edescr": "Steering Response Speed (Instant Reaction) — 140 Recommended", + "cgroup": "转向", + "ctitle": "转向力矩 Kp*0.01(100)", + "cdescr": "转向响应速度 (瞬时反应) — 推荐 140", "min": 0, "max": 200, "default": 100, - "unit": 1, - "cgroup": "转向", - "ctitle": "转向力矩 Kp*0.01(100)", - "cdescr": "转向响应速度 (瞬时反应) — 推荐 140" + "unit": 1 }, { "group": "조향토크", @@ -105,13 +681,13 @@ "egroup": "LAT_TQ", "etitle": "_LateralTorqueKi*0.01(10)", "edescr": "Steering Accumulated Error Correction — 20 Recommended", + "cgroup": "转向", + "ctitle": "转向力矩 Ki*0.01(10)", + "cdescr": "转向累积误差修正 — 推荐 20", "min": 0, "max": 200, "default": 10, - "unit": 1, - "cgroup": "转向", - "ctitle": "转向力矩 Ki*0.01(10)", - "cdescr": "转向累积误差修正 — 推荐 20" + "unit": 1 }, { "group": "조향토크", @@ -121,13 +697,13 @@ "egroup": "LAT_TQ", "etitle": "_LateralTorqueKf*0.001(100)", "edescr": "Steering Feedforward Correction — 85 Recommended", + "cgroup": "转向", + "ctitle": "转向力矩 Kf*0.001(100)", + "cdescr": "转向前馈修正 — 推荐 85", "min": 0, "max": 200, "default": 100, - "unit": 1, - "cgroup": "转向", - "ctitle": "转向力矩 Kf*0.001(100)", - "cdescr": "转向前馈修正 — 推荐 85" + "unit": 1 }, { "group": "조향토크", @@ -137,13 +713,13 @@ "egroup": "LAT_TQ", "etitle": "_LateralTorqueKd*0.01(0)", "edescr": "Steering Derivative Gain, 0 Recommended", + "cgroup": "转向", + "ctitle": "转向力矩 Kd*0.01(0)", + "cdescr": "转向微分增益,推荐 0", "min": 0, "max": 10000, "default": 0, - "unit": 10, - "cgroup": "转向", - "ctitle": "转向力矩 Kd*0.01(0)", - "cdescr": "转向微分增益,推荐 0" + "unit": 10 }, { "group": "레인모드", @@ -153,13 +729,13 @@ "egroup": "LANE", "etitle": "*LATMPC.PathCost(200)", "edescr": "(LaneMode) Path Following Weight — Higher values prioritize the path", + "cgroup": "转向", + "ctitle": "横向MPC路径代价(200)", + "cdescr": "(车道模式) 路径跟随权重 — 值越高越优先考虑路径", "min": 0, "max": 500, "default": 200, - "unit": 10, - "cgroup": "转向", - "ctitle": "横向MPC路径代价(200)", - "cdescr": "(车道模式) 路径跟随权重 — 值越高越优先考虑路径" + "unit": 10 }, { "group": "레인모드", @@ -169,13 +745,13 @@ "egroup": "LANE", "etitle": "*LATMPC.MotionCost(7)", "edescr": "(LaneMode) Heading Weight", + "cgroup": "转向", + "ctitle": "横向MPC运动代价(7)", + "cdescr": "(车道模式) 航向权重", "min": 0, "max": 500, "default": 7, - "unit": 1, - "cgroup": "转向", - "ctitle": "横向MPC运动代价(7)", - "cdescr": "(车道模式) 航向权重" + "unit": 1 }, { "group": "레인모드", @@ -185,13 +761,13 @@ "egroup": "LANE", "etitle": "*LATMPC.AccelCost(120)", "edescr": "(LaneMode) Lateral Acceleration Limit — Lower values increase agility but reduce stability", + "cgroup": "转向", + "ctitle": "横向MPC加速代价(120)", + "cdescr": "(车道模式) 侧向加速度限制 — 值越低灵活性越高但稳定性降低", "min": 0, "max": 500, "default": 100, - "unit": 10, - "cgroup": "转向", - "ctitle": "横向MPC加速代价(120)", - "cdescr": "(车道模式) 侧向加速度限制 — 值越低灵活性越高但稳定性降低" + "unit": 10 }, { "group": "레인모드", @@ -201,13 +777,13 @@ "egroup": "LANE", "etitle": "*LATMPC.JerkCost(4)", "edescr": "(LaneMode) Lower values allow quicker steering — reduces smoothness", + "cgroup": "转向", + "ctitle": "横向MPC冲击代价(4)", + "cdescr": "(车道模式) 值越低允许越快的转向 — 会降低平滑度", "min": 0, "max": 200, "default": 1, - "unit": 1, - "cgroup": "转向", - "ctitle": "横向MPC冲击代价(4)", - "cdescr": "(车道模式) 值越低允许越快的转向 — 会降低平滑度" + "unit": 1 }, { "group": "레인모드", @@ -217,13 +793,13 @@ "egroup": "LANE", "etitle": "*LATMPC.SteerRateCost(7)", "edescr": "(LaneMode) Steering Rate Limit — Lower values make steering more aggressive", + "cgroup": "转向", + "ctitle": "横向MPC转向速率代价(7)", + "cdescr": "(车道模式) 转向速率限制 — 值越低转向越激进", "min": 0, "max": 1000, "default": 7, - "unit": 100, - "cgroup": "转向", - "ctitle": "横向MPC转向速率代价(7)", - "cdescr": "(车道模式) 转向速率限制 — 值越低转向越激进" + "unit": 100 }, { "group": "레인모드", @@ -233,13 +809,13 @@ "egroup": "LANE", "etitle": "*LATMPC.InputOffset(4)", "edescr": "(LaneMode) MPC Input Compensation — Higher values enter curves earlier", + "cgroup": "转向", + "ctitle": "横向MPC输入偏移(4)", + "cdescr": "(车道模式) MPC 输入补偿 — 值越高进入弯道越早", "min": 0, "max": 20, "default": 4, - "unit": 1, - "cgroup": "转向", - "ctitle": "横向MPC输入偏移(4)", - "cdescr": "(车道模式) MPC 输入补偿 — 值越高进入弯道越早" + "unit": 1 }, { "group": "조향토크", @@ -249,13 +825,13 @@ "egroup": "LAT_TQ", "etitle": "_CustomSteerMax(0)", "edescr": "Maximum Steering Torque — Use the largest value among 270/384/409 that does not cause errors", + "cgroup": "转向", + "ctitle": "自定义最大转向力矩(0)", + "cdescr": "最大转向力矩 — 在 270/384/409 中选择不报错的最大值", "min": 0, "max": 30000, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "自定义最大转向力矩(0)", - "cdescr": "最大转向力矩 — 在 270/384/409 中选择不报错的最大值" + "unit": 1 }, { "group": "조향토크", @@ -265,13 +841,13 @@ "egroup": "LAT_TQ", "etitle": "_CustomSteerDeltaUp(0)", "edescr": "Torque Vehicle — Higher values increase steering wheel turn speed", + "cgroup": "转向", + "ctitle": "自定义转向增大斜率(0)", + "cdescr": "力矩控制车辆 — 值越高方向盘转动速度越快", "min": 0, "max": 50, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "自定义转向增大斜率(0)", - "cdescr": "力矩控制车辆 — 值越高方向盘转动速度越快" + "unit": 1 }, { "group": "조향토크", @@ -281,13 +857,13 @@ "egroup": "LAT_TQ", "etitle": "_CustomSteerDeltaDown(0)", "edescr": "Torque Vehicle — Higher values increase steering wheel release speed", + "cgroup": "转向", + "ctitle": "自定义转向减小斜率(0)", + "cdescr": "力矩控制车辆 — 值越高方向盘回正速度越快", "min": 0, "max": 50, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "自定义转向减小斜率(0)", - "cdescr": "力矩控制车辆 — 值越高方向盘回正速度越快" + "unit": 1 }, { "group": "조향토크", @@ -297,13 +873,13 @@ "egroup": "LAT_TQ", "etitle": "_CustomSteerDeltaUpLC(0)", "edescr": "Torque Vehicle — Temporarily applies SteerDeltaUp during lane changes", + "cgroup": "转向", + "ctitle": "变道自定义转向增大斜率(0)", + "cdescr": "力矩控制车辆 — 变道期间临时应用转向增大斜率", "min": 0, "max": 50, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "变道自定义转向增大斜率(0)", - "cdescr": "力矩控制车辆 — 变道期间临时应用转向增大斜率" + "unit": 1 }, { "group": "조향토크", @@ -313,13 +889,13 @@ "egroup": "LAT_TQ", "etitle": "_CustomSteerDeltaDownLC(0)", "edescr": "Torque Vehicle — Temporarily applies SteerDeltaDown during lane changes", + "cgroup": "转向", + "ctitle": "变道自定义转向减小斜率(0)", + "cdescr": "力矩控制车辆 — 变道期间临时应用转向减小斜率", "min": 0, "max": 50, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "变道自定义转向减小斜率(0)", - "cdescr": "力矩控制车辆 — 变道期间临时应用转向减小斜率" + "unit": 1 }, { "group": "조향튜닝", @@ -329,13 +905,13 @@ "egroup": "LAT", "etitle": "SteerActuatorDelay(30)", "edescr": "Steering Delay Value — Higher values steer earlier. 0 means Live Delay", + "cgroup": "转向", + "ctitle": "转向执行器延迟(30)", + "cdescr": "转向延迟值 — 值越高转向越早。0 表示实时延迟", "min": 0, "max": 300, "default": 30, - "unit": 1, - "cgroup": "转向", - "ctitle": "转向执行器延迟(30)", - "cdescr": "转向延迟值 — 值越高转向越早。0 表示实时延迟" + "unit": 1 }, { "group": "조향튜닝", @@ -345,13 +921,13 @@ "egroup": "LAT", "etitle": "LatSmoothSec(13)", "edescr": "Lat smoothing values — Higher values smoother", + "cgroup": "转向", + "ctitle": "转向平滑时间(30)", + "cdescr": "转向平滑值 — 值越高越平滑", "min": 1, "max": 30, "default": 13, - "unit": 1, - "cgroup": "转向", - "ctitle": "转向平滑时间(30)", - "cdescr": "转向平滑值 — 值越高越平滑" + "unit": 1 }, { "group": "조향튜닝", @@ -361,13 +937,13 @@ "egroup": "LAT", "etitle": "AutoSteering Suspend Angle(300)", "edescr": "Auto steering pauses at large steering angles and resumes when it returns within 15.", + "cgroup": "转向", + "ctitle": "AutoSteering Suspend Angle(300)", + "cdescr": "Auto steering pauses at large steering angles and resumes when it returns within 15.", "min": 45, "max": 300, "default": 300, - "unit": 10, - "cgroup": "转向", - "ctitle": "AutoSteering Suspend Angle(300)", - "cdescr": "Auto steering pauses at large steering angles and resumes when it returns within 15." + "unit": 10 }, { "group": "크루즈", @@ -377,13 +953,13 @@ "egroup": "CRUISE", "etitle": "CruiseOnDist(0cm)", "edescr": "Cruise ON when releasing the pedal and the front car gets close", + "cgroup": "巡航设置", + "ctitle": "巡航开启距离(0cm)", + "cdescr": "松开油门且前车靠近时开启巡航", "min": 0, "max": 2500, "default": 0, - "unit": 50, - "cgroup": "巡航设置", - "ctitle": "巡航开启距离(0cm)", - "cdescr": "松开油门且前车靠近时开启巡航" + "unit": 50 }, { "group": "크루즈", @@ -393,13 +969,13 @@ "egroup": "CRUISE", "etitle": "CruiseEcoControl(2km/h)", "edescr": "Temporarily increases the target speed for fuel efficiency", + "cgroup": "巡航设置", + "ctitle": "巡航节能控制(2km/h)", + "cdescr": "为提高燃油效率临时提高目标速度", "min": 0, "max": 10, "default": 2, - "unit": 1, - "cgroup": "巡航设置", - "ctitle": "巡航节能控制(2km/h)", - "cdescr": "为提高燃油效率临时提高目标速度" + "unit": 1 }, { "group": "크루즈", @@ -409,13 +985,13 @@ "egroup": "CRUISE", "etitle": "CarrotCruiseDecel(-1)", "edescr": "Carrot Cruise Deceleration Setting, -1: Cruise OFF", + "cgroup": "巡航设置", + "ctitle": "胡萝卜巡航减速度(-1)", + "cdescr": "胡萝卜巡航减速度设置,-1: 巡航关闭", "min": -1, "max": 200, "default": -1, - "unit": 10, - "cgroup": "巡航设置", - "ctitle": "胡萝卜巡航减速度(-1)", - "cdescr": "胡萝卜巡航减速度设置,-1: 巡航关闭" + "unit": 10 }, { "group": "크루즈", @@ -425,13 +1001,13 @@ "egroup": "CRUISE", "etitle": "CarrotCruiseDecelATCx0.01(-1)", "edescr": "Carrot Cruise ATC Deceleration Setting, -1: Not Change", + "cgroup": "巡航设置", + "ctitle": "胡萝卜巡航 ATC 减速度(-1)", + "cdescr": "胡萝卜巡航 ATC 减速度设置,-1: 不改变", "min": -1, "max": 200, "default": -1, - "unit": 10, - "cgroup": "巡航设置", - "ctitle": "胡萝卜巡航 ATC 减速度(-1)", - "cdescr": "胡萝卜巡航 ATC 减速度设置,-1: 不改变" + "unit": 10 }, { "group": "조향튜닝", @@ -441,13 +1017,13 @@ "egroup": "LAT", "etitle": "CustomSteerRatiox0.1(0)", "edescr": "Steering Ratio (Refer to Vehicle Specs), 0: Uses LiveSteerRatio", + "cgroup": "转向", + "ctitle": "自定义转向比x0.1(0)", + "cdescr": "转向比 (参考车辆参数),0: 使用实时转向比", "min": 0, "max": 300, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "自定义转向比x0.1(0)", - "cdescr": "转向比 (参考车辆参数),0: 使用实时转向比" + "unit": 1 }, { "group": "조향튜닝", @@ -457,13 +1033,13 @@ "egroup": "LAT", "etitle": "CustomSteerRatioRatex0.01(100)", "edescr": "LiveSteerRatio Application Ratio (Requires CustomSR set to 0)", + "cgroup": "转向", + "ctitle": "自定义转向比率x0.01(100)", + "cdescr": "实时转向比应用比例 (需要 CustomSR 设置为 0)", "min": 30, "max": 200, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "自定义转向比率x0.01(100)", - "cdescr": "实时转向比应用比例 (需要 CustomSR 设置为 0)" + "unit": 1 }, { "group": "가속설정", @@ -473,13 +1049,13 @@ "egroup": "ACCEL", "etitle": "AccelSet0:0km/h(160)", "edescr": "Acceleration from 0 km/h to 10 km/h", + "cgroup": "加速设置", + "ctitle": "加速度设置 0: 0km/h(160)", + "cdescr": "0 km/h 到 10 km/h 的加速度", "min": 1, "max": 250, "default": 160, - "unit": 5, - "cgroup": "加速设置", - "ctitle": "加速度设置 0: 0km/h(160)", - "cdescr": "0 km/h 到 10 km/h 的加速度" + "unit": 5 }, { "group": "가속설정", @@ -489,13 +1065,13 @@ "egroup": "ACCEL", "etitle": "AccelSet1:10km/h(160)", "edescr": "Acceleration from 10 km/h to 40 km/h", + "cgroup": "加速设置", + "ctitle": "加速度设置 1: 10km/h(160)", + "cdescr": "10 km/h 到 40 km/h 的加速度", "min": 1, "max": 250, "default": 160, - "unit": 5, - "cgroup": "加速设置", - "ctitle": "加速度设置 1: 10km/h(160)", - "cdescr": "10 km/h 到 40 km/h 的加速度" + "unit": 5 }, { "group": "가속설정", @@ -505,13 +1081,13 @@ "egroup": "ACCEL", "etitle": "AccelSet2:40km/h(120)", "edescr": "Acceleration from 40 km/h to 60 km/h", + "cgroup": "加速设置", + "ctitle": "加速度设置 2: 40km/h(120)", + "cdescr": "40 km/h 到 60 km/h 的加速度", "min": 1, "max": 250, "default": 120, - "unit": 5, - "cgroup": "加速设置", - "ctitle": "加速度设置 2: 40km/h(120)", - "cdescr": "40 km/h 到 60 km/h 的加速度" + "unit": 5 }, { "group": "가속설정", @@ -521,13 +1097,13 @@ "egroup": "ACCEL", "etitle": "AccelSet3:60km/h(100)", "edescr": "Acceleration from 60 km/h to 80 km/h", + "cgroup": "加速设置", + "ctitle": "加速度设置 3: 60km/h(100)", + "cdescr": "60 km/h 到 80 km/h 的加速度", "min": 1, "max": 250, "default": 100, - "unit": 5, - "cgroup": "加速设置", - "ctitle": "加速度设置 3: 60km/h(100)", - "cdescr": "60 km/h 到 80 km/h 的加速度" + "unit": 5 }, { "group": "가속설정", @@ -537,13 +1113,13 @@ "egroup": "ACCEL", "etitle": "AccelSet4:80km/h(80)", "edescr": "Acceleration from 80 km/h to 110 km/h", + "cgroup": "加速设置", + "ctitle": "加速度设置 4: 80km/h(80)", + "cdescr": "80 km/h 到 110 km/h 的加速度", "min": 1, "max": 250, "default": 80, - "unit": 5, - "cgroup": "加速设置", - "ctitle": "加速度设置 4: 80km/h(80)", - "cdescr": "80 km/h 到 110 km/h 的加速度" + "unit": 5 }, { "group": "가속설정", @@ -553,13 +1129,13 @@ "egroup": "ACCEL", "etitle": "AccelSet5:110km/h(70)", "edescr": "Acceleration from 110 km/h to 140 km/h", + "cgroup": "加速设置", + "ctitle": "加速度设置 5: 110km/h(70)", + "cdescr": "110 km/h 到 140 km/h 的加速度", "min": 1, "max": 250, "default": 70, - "unit": 5, - "cgroup": "加速设置", - "ctitle": "加速度设置 5: 110km/h(70)", - "cdescr": "110 km/h 到 140 km/h 的加速度" + "unit": 5 }, { "group": "가속설정", @@ -569,13 +1145,13 @@ "egroup": "ACCEL", "etitle": "AccelSet6:140km/h(60)", "edescr": "Acceleration from 140 km/h", + "cgroup": "加速设置", + "ctitle": "加速度设置 6: 140km/h(60)", + "cdescr": "140 km/h 以上的加速度", "min": 1, "max": 250, "default": 60, - "unit": 5, - "cgroup": "加速设置", - "ctitle": "加速度设置 6: 140km/h(60)", - "cdescr": "140 km/h 以上的加速度" + "unit": 5 }, { "group": "주행튜닝", @@ -585,13 +1161,13 @@ "egroup": "LONG", "etitle": "StopDistance(600)cm", "edescr": "Stop Distance * 0.8", + "cgroup": "纵向控制", + "ctitle": "停止距离(600)cm", + "cdescr": "停止距离 * 0.8", "min": 400, "max": 1000, "default": 600, - "unit": 10, - "cgroup": "纵向控制", - "ctitle": "停止距离(600)cm", - "cdescr": "停止距离 * 0.8" + "unit": 10 }, { "group": "주행튜닝", @@ -601,13 +1177,13 @@ "egroup": "LONG", "etitle": "AChangeCostStarting", "edescr": "AccelCost Starting, 0:rapid starting", + "cgroup": "纵向控制", + "ctitle": "启动加速度限制", + "cdescr": "启动加速代价,0: 快速启动", "min": 0, "max": 200, "default": 10, - "unit": 10, - "cgroup": "纵向控制", - "ctitle": "启动加速度限制", - "cdescr": "启动加速代价,0: 快速启动" + "unit": 10 }, { "group": "주행튜닝", @@ -617,13 +1193,13 @@ "egroup": "LONG", "etitle": "TrafficStopDistanceAdjust", "edescr": "", + "cgroup": "纵向控制", + "ctitle": "交通信号停止距离调整", + "cdescr": "", "min": -600, "max": 600, "default": -150, - "unit": 100, - "cgroup": "纵向控制", - "ctitle": "交通信号停止距离调整", - "cdescr": "" + "unit": 100 }, { "group": "주행튜닝", @@ -633,13 +1209,13 @@ "egroup": "LONG", "etitle": "JerkLeadFactor(0)", "edescr": "Higher values respond more sensitively to acceleration changes of the lead vehicle", + "cgroup": "纵向控制", + "ctitle": "前车冲击因子(0)", + "cdescr": "值越高对前车加速度变化的反应越敏感", "min": 0, "max": 100, "default": 0, - "unit": 10, - "cgroup": "纵向控制", - "ctitle": "前车冲击因子(0)", - "cdescr": "值越高对前车加速度变化的反应越敏感" + "unit": 10 }, { "group": "주행튜닝", @@ -649,13 +1225,13 @@ "egroup": "LONG", "etitle": "Long KpV(100)x0.01", "edescr": "Longitudinal Control Proportional Gain (Immediate Error Response) — HKG: 100 Recommended", + "cgroup": "纵向控制", + "ctitle": "纵向 KpV(100)x0.01", + "cdescr": "纵向控制比例增益 (瞬时误差响应) — HKG: 推荐 100", "min": 0, "max": 200, "default": 100, - "unit": 5, - "cgroup": "纵向控制", - "ctitle": "纵向 KpV(100)x0.01", - "cdescr": "纵向控制比例增益 (瞬时误差响应) — HKG: 推荐 100" + "unit": 5 }, { "group": "주행튜닝", @@ -665,13 +1241,13 @@ "egroup": "LONG", "etitle": "Long Kf(100)x0.01", "edescr": "Longitudinal Feedforward Correction — HKG: 100 Recommended", + "cgroup": "纵向控制", + "ctitle": "纵向 Kf(100)x0.01", + "cdescr": "纵向前馈修正 — HKG: 推荐 100", "min": 0, "max": 200, "default": 0, - "unit": 5, - "cgroup": "纵向控制", - "ctitle": "纵向 Kf(100)x0.01", - "cdescr": "纵向前馈修正 — HKG: 推荐 100" + "unit": 5 }, { "group": "주행튜닝", @@ -681,13 +1257,13 @@ "egroup": "LONG", "etitle": "Long KiV(0)x0.01", "edescr": "Longitudinal Integral Gain (Cumulative Error Correction) — HKG: 0 Recommended", + "cgroup": "纵向控制", + "ctitle": "纵向 KiV(0)x0.01", + "cdescr": "纵向积分增益 (累积误差修正) — HKG: 推荐 0", "min": 0, "max": 2000, "default": 0, - "unit": 1, - "cgroup": "纵向控制", - "ctitle": "纵向 KiV(0)x0.01", - "cdescr": "纵向积分增益 (累积误差修正) — HKG: 推荐 0" + "unit": 1 }, { "group": "주행튜닝", @@ -697,13 +1273,13 @@ "egroup": "LONG", "etitle": "LongActuatorDelay(20)x0.01", "edescr": "Longitudinal Delay Value (Higher values lead to earlier acceleration/deceleration) — HKG: 20 Recommeded", + "cgroup": "纵向控制", + "ctitle": "纵向执行器延迟(20)x0.01", + "cdescr": "纵向延迟值 (值越高越早开始加减速) — HKG: 推荐 20", "min": 0, "max": 200, "default": 20, - "unit": 5, - "cgroup": "纵向控制", - "ctitle": "纵向执行器延迟(20)x0.01", - "cdescr": "纵向延迟值 (值越高越早开始加减速) — HKG: 推荐 20" + "unit": 5 }, { "group": "주행튜닝", @@ -713,13 +1289,13 @@ "egroup": "LONG", "etitle": "VEgoStopping(50)x0.01", "edescr": "Lower values result in quicker starts; setting too low may increase collision risk. (5 recommended)", + "cgroup": "纵向控制", + "ctitle": "停止/启动灵敏度(50)x0.01", + "cdescr": "值越低起步越快;设置过低可能会增加碰撞风险 (推荐 5)", "min": 1, "max": 100, "default": 50, - "unit": 5, - "cgroup": "纵向控制", - "ctitle": "停止/启动灵敏度(50)x0.01", - "cdescr": "值越低起步越快;设置过低可能会增加碰撞风险 (推荐 5)" + "unit": 5 }, { "group": "주행튜닝", @@ -729,13 +1305,13 @@ "egroup": "LONG", "etitle": "Radar reaction factor (100)%", "edescr": "Lower value: faster response to lead car", + "cgroup": "纵向控制", + "ctitle": "雷达反应因子 (100)%", + "cdescr": "值越低:对前车的反应越快", "min": 0, "max": 200, "default": 100, - "unit": 1, - "cgroup": "纵向控制", - "ctitle": "雷达反应因子 (100)%", - "cdescr": "值越低:对前车的反应越快" + "unit": 1 }, { "group": "감속제어", @@ -745,13 +1321,13 @@ "egroup": "DECEL", "etitle": "CurveSpeedFactor(100%)", "edescr": "Higher value: slow curve speed", + "cgroup": "减速控制", + "ctitle": "弯道速度调节比例(100%)", + "cdescr": "值越高:弯道速度越慢", "min": 50, "max": 300, "default": 100, - "unit": 5, - "cgroup": "减速控制", - "ctitle": "弯道速度调节比例(100%)", - "cdescr": "值越高:弯道速度越慢" + "unit": 5 }, { "group": "조향일반", @@ -761,13 +1337,13 @@ "egroup": "LATSET", "etitle": "ATC: AutoTurnControl", "edescr": "(APN)0: not use, 1:laneChange, 2:laneChange+speed, 3:speed", + "cgroup": "转向设置", + "ctitle": "ATC: 自动转向控制", + "cdescr": "(APN) 0: 不使用, 1: 变道, 2: 变道+速度, 3: 速度", "min": 0, "max": 3, "default": 0, - "unit": 1, - "cgroup": "转向设置", - "ctitle": "ATC: 自动转向控制", - "cdescr": "(APN) 0: 不使用, 1: 变道, 2: 变道+速度, 3: 速度" + "unit": 1 }, { "group": "조향일반", @@ -777,13 +1353,13 @@ "egroup": "LATSET", "etitle": "ATC: Turn Speed(20)", "edescr": "(APN)0: not use, Turn Speed", + "cgroup": "转向设置", + "ctitle": "ATC: 转向速度(20)", + "cdescr": "(APN) 0: 不使用, 转向速度", "min": 0, "max": 100, "default": 20, - "unit": 5, - "cgroup": "转向设置", - "ctitle": "ATC: 转向速度(20)", - "cdescr": "(APN) 0: 不使用, 转向速度" + "unit": 5 }, { "group": "조향일반", @@ -793,13 +1369,13 @@ "egroup": "LATSET", "etitle": "ATC: Speed Ctrl left Time(3)", "edescr": "(APN) NOO Completion Time Distance (Speed × Time)", + "cgroup": "转向设置", + "ctitle": "ATC: 速度控制剩余时间(3)", + "cdescr": "(APN) NOO 完成时间距离 (速度 × 时间)", "min": 0, "max": 30, "default": 6, - "unit": 1, - "cgroup": "转向设置", - "ctitle": "ATC: 速度控制剩余时间(3)", - "cdescr": "(APN) NOO 完成时间距离 (速度 × 时间)" + "unit": 1 }, { "group": "조향일반", @@ -809,13 +1385,13 @@ "egroup": "LATSET", "etitle": "ATC Helper Map display(0)", "edescr": "(APN) Turn On Map When ATC", + "cgroup": "转向设置", + "ctitle": "ATC 辅助地图显示(0)", + "cdescr": "(APN) ATC 激活时开启地图", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "转向设置", - "ctitle": "ATC 辅助地图显示(0)", - "cdescr": "(APN) ATC 激活时开启地图" + "unit": 1 }, { "group": "버튼설정", @@ -825,13 +1401,13 @@ "egroup": "BUTN", "etitle": "Cruise Button Mode(0)", "edescr": "0:General, 1:User1, 2:User2, 3:User3(Use Speed Tables)", + "cgroup": "按键设置", + "ctitle": "巡航按钮模式(0)", + "cdescr": "0: 常规, 1: 用户1, 2: 用户2, 3: 用户3 (使用速度表)", "min": 0, "max": 3, "default": 0, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "巡航按钮模式(0)", - "cdescr": "0: 常规, 1: 用户1, 2: 用户2, 3: 用户3 (使用速度表)" + "unit": 1 }, { "group": "버튼설정", @@ -841,13 +1417,13 @@ "egroup": "BUTN", "etitle": "Cancel Button Mode(0)", "edescr": "0:Long control, 1: Long + Lateral", + "cgroup": "按键设置", + "ctitle": "取消按钮模式(0)", + "cdescr": "0: 纵向控制, 1: 纵向 + 横向", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "取消按钮模式(0)", - "cdescr": "0: 纵向控制, 1: 纵向 + 横向" + "unit": 1 }, { "group": "버튼설정", @@ -857,13 +1433,13 @@ "egroup": "BUTN", "etitle": "LFA Button Mode(0)", "edescr": "0:General, 1:Decel&Stop, leadCar ready, 2:CarrotCruise", + "cgroup": "按键设置", + "ctitle": "LFA 按钮模式(0)", + "cdescr": "0: 常规, 1: 减速并停止/前车就绪, 2: 胡萝卜巡航", "min": 0, "max": 2, "default": 0, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "LFA 按钮模式(0)", - "cdescr": "0: 常规, 1: 减速并停止/前车就绪, 2: 胡萝卜巡航" + "unit": 1 }, { "group": "버튼설정", @@ -873,13 +1449,13 @@ "egroup": "BUTN", "etitle": "Cruise Button spam1(8)", "edescr": "For Non-Long Vehicle Only, Speed Button Signal Send Restriction Time", + "cgroup": "按键设置", + "ctitle": "巡航按钮连发1(8)", + "cdescr": "仅限非纵向控制车辆,速度按钮信号发送限制时间", "min": 1, "max": 20, "default": 0, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "巡航按钮连发1(8)", - "cdescr": "仅限非纵向控制车辆,速度按钮信号发送限制时间" + "unit": 1 }, { "group": "버튼설정", @@ -889,13 +1465,13 @@ "egroup": "BUTN", "etitle": "Cruise Button spam2(30)", "edescr": "For Non-Long Vehicle Only — Speed Button Signal Send Pause Time", + "cgroup": "按键设置", + "ctitle": "巡航按钮连发2(30)", + "cdescr": "仅限非纵向控制车辆 — 速度按钮信号发送暂停时间", "min": 1, "max": 200, "default": 0, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "巡航按钮连发2(30)", - "cdescr": "仅限非纵向控制车辆 — 速度按钮信号发送暂停时间" + "unit": 1 }, { "group": "버튼설정", @@ -905,13 +1481,13 @@ "egroup": "BUTN", "etitle": "Cruise Button spam3(1)", "edescr": "For Non-Long Vehicle Only — Consecutive Speed Button Signal Count", + "cgroup": "按键设置", + "ctitle": "巡航按钮连发3(1)", + "cdescr": "仅限非纵向控制车辆 — 连续速度按钮信号计数", "min": 1, "max": 20, "default": 0, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "巡航按钮连发3(1)", - "cdescr": "仅限非纵向控制车辆 — 连续速度按钮信号计数" + "unit": 1 }, { "group": "버튼설정", @@ -921,13 +1497,13 @@ "egroup": "BUTN", "etitle": "Cruise Speed Unit Extra(10)", "edescr": "SET/DECEL up/down speed unit(mode1,2,3, GasTok)", + "cgroup": "按键设置", + "ctitle": "巡航速度单位扩展(10)", + "cdescr": "SET/DECEL 加减速单位 (模式 1,2,3, GasTok)", "min": 1, "max": 100, "default": 10, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "巡航速度单位扩展(10)", - "cdescr": "SET/DECEL 加减速单位 (模式 1,2,3, GasTok)" + "unit": 1 }, { "group": "버튼설정", @@ -937,13 +1513,13 @@ "egroup": "Button Settings", "etitle": "Cruise Button Long Delay (40)", "edescr": "Delay before the long-press action is triggered.", + "cgroup": "按键设置", + "ctitle": "巡航按键长按延迟(40)", + "cdescr": "长按按钮后,功能触发前的延迟时间。", "min": 30, "max": 100, "default": 40, - "unit": 10, - "cgroup": "按键设置", - "ctitle": "巡航按键长按延迟(40)", - "cdescr": "长按按钮后,功能触发前的延迟时间。" + "unit": 10 }, { "group": "버튼설정", @@ -953,13 +1529,13 @@ "egroup": "BUTN", "etitle": "Cruise Speed Unit Baic(10)", "edescr": "SET/DECEL up/down speed unit(Basic unit)", + "cgroup": "按键设置", + "ctitle": "巡航速度单位基础(10)", + "cdescr": "SET/DECEL 加减速单位 (基础单位)", "min": 1, "max": 100, "default": 10, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "巡航速度单位基础(10)", - "cdescr": "SET/DECEL 加减速单位 (基础单位)" + "unit": 1 }, { "group": "버튼설정", @@ -969,13 +1545,13 @@ "egroup": "BUTN", "etitle": "Cruise Speed1(30)", "edescr": "Button Mode User 3, Stepwise Speed, 0: Road Speed Limit + Auto Speed Increase Offset", + "cgroup": "按键设置", + "ctitle": "巡航速度1(30)", + "cdescr": "按钮模式用户 3, 分段速度, 0: 道路限速 + 自动加速偏移", "min": 0, "max": 160, "default": 10, - "unit": 10, - "cgroup": "按键设置", - "ctitle": "巡航速度1(30)", - "cdescr": "按钮模式用户 3, 分段速度, 0: 道路限速 + 自动加速偏移" + "unit": 10 }, { "group": "버튼설정", @@ -985,13 +1561,13 @@ "egroup": "BUTN", "etitle": "Cruise Speed2(50)", "edescr": "Button Mode User 3, Second Speed", + "cgroup": "按键设置", + "ctitle": "巡航速度2(50)", + "cdescr": "按钮模式用户 3, 第二段速度", "min": 1, "max": 160, "default": 10, - "unit": 10, - "cgroup": "按键设置", - "ctitle": "巡航速度2(50)", - "cdescr": "按钮模式用户 3, 第二段速度" + "unit": 10 }, { "group": "버튼설정", @@ -1001,13 +1577,13 @@ "egroup": "BUTN", "etitle": "Cruise Speed3(80)", "edescr": "Button Mode User 3, Third Speed", + "cgroup": "按键设置", + "ctitle": "巡航速度3(80)", + "cdescr": "按钮模式用户 3, 第三段速度", "min": 1, "max": 160, "default": 10, - "unit": 10, - "cgroup": "按键设置", - "ctitle": "巡航速度3(80)", - "cdescr": "按钮模式用户 3, 第三段速度" + "unit": 10 }, { "group": "버튼설정", @@ -1017,13 +1593,13 @@ "egroup": "BUTN", "etitle": "Cruise Speed4(110)", "edescr": "Button Mode User 3, Fourth Speed", + "cgroup": "按键设置", + "ctitle": "巡航速度4(110)", + "cdescr": "按钮模式用户 3, 第四段速度", "min": 1, "max": 160, "default": 10, - "unit": 10, - "cgroup": "按键设置", - "ctitle": "巡航速度4(110)", - "cdescr": "按钮模式用户 3, 第四段速度" + "unit": 10 }, { "group": "버튼설정", @@ -1033,13 +1609,13 @@ "egroup": "BUTN", "etitle": "Cruise Speed5(130)", "edescr": "Button Mode User 3, Fifth Speed", + "cgroup": "按键设置", + "ctitle": "巡航速度5(130)", + "cdescr": "按钮模式用户 3, 第五段速度", "min": 1, "max": 160, "default": 10, - "unit": 10, - "cgroup": "按键设置", - "ctitle": "巡航速度5(130)", - "cdescr": "按钮模式用户 3, 第五段速度" + "unit": 10 }, { "group": "버튼설정", @@ -1049,13 +1625,13 @@ "egroup": "BUTN", "etitle": "PaddleShift Mode(0)", "edescr": "After Regen paddle, 0:Cruise ON, 1:Cruise Ready, 2: decel & cruise ready, 3: CarrotCruise", + "cgroup": "按键设置", + "ctitle": "换挡拨片模式(0)", + "cdescr": "动能回收拨片后, 0: 巡航开启, 1: 巡航就绪, 2: 减速并巡航就绪, 3: 胡萝卜巡航", "min": 0, "max": 3, "default": 1, - "unit": 1, - "cgroup": "按键设置", - "ctitle": "换挡拨片模式(0)", - "cdescr": "动能回收拨片后, 0: 巡航开启, 1: 巡航就绪, 2: 减速并巡航就绪, 3: 胡萝卜巡航" + "unit": 1 }, { "group": "오토크루즈", @@ -1065,13 +1641,13 @@ "egroup": "AUTOCRUISE", "etitle": "Auto Cruise control(HKG only)", "edescr": "1:Softhold + Auto Cruise, 2:if softhold error", + "cgroup": "自动巡航", + "ctitle": "自动巡航控制 (仅限现代/起亚)", + "cdescr": "1: 软驻车 + 自动巡航, 2: 如果软驻车报错", "min": 0, "max": 3, "default": 0, - "unit": 1, - "cgroup": "自动巡航", - "ctitle": "自动巡航控制 (仅限现代/起亚)", - "cdescr": "1: 软驻车 + 自动巡航, 2: 如果软驻车报错" + "unit": 1 }, { "group": "오토크루즈", @@ -1081,13 +1657,13 @@ "egroup": "AUTOCRUISE", "etitle": "Auto Cruise: Accelerator Tap Start Speed", "edescr": "Operates only above the set speed — GasTab : Tap the accelerator pedal for 0.6 seconds", + "cgroup": "自动巡航", + "ctitle": "自动巡航: 踩油门起步速度", + "cdescr": "仅在设定速度以上运行 — GasTab: 踩油门 0.6 秒", "min": 0, "max": 200, "default": 0, - "unit": 5, - "cgroup": "自动巡航", - "ctitle": "自动巡航: 踩油门起步速度", - "cdescr": "仅在设定速度以上运行 — GasTab: 踩油门 0.6 秒" + "unit": 5 }, { "group": "오토크루즈", @@ -1097,13 +1673,13 @@ "egroup": "AUTOCRUISE", "etitle": "Auto Cruise: Gas Release Cancel Speed", "edescr": "When releasing the accelerator pedal, cruise is canceled below this speed.", + "cgroup": "自动巡航", + "ctitle": "自动巡航: 松开油门取消速度", + "cdescr": "松开油门踏板时,当前速度低于设定值则取消巡航。", "min": 0, "max": 200, "default": 30, - "unit": 5, - "cgroup": "自动巡航", - "ctitle": "自动巡航: 松开油门取消速度", - "cdescr": "松开油门踏板时,当前速度低于设定值则取消巡航。" + "unit": 5 }, { "group": "오토크루즈", @@ -1113,13 +1689,13 @@ "egroup": "AUTOCRUISE", "etitle": "Auto Engage on onroad", "edescr": "1: Steering Only, 2: Steering ON + Cruise Standby", + "cgroup": "自动巡航", + "ctitle": "上路自动激活", + "cdescr": "1: 仅转向, 2: 转向开启 + 巡航待机", "min": 0, "max": 2, "default": 0, - "unit": 1, - "cgroup": "自动巡航", - "ctitle": "上路自动激活", - "cdescr": "1: 仅转向, 2: 转向开启 + 巡航待机" + "unit": 1 }, { "group": "시작", @@ -1129,13 +1705,13 @@ "egroup": "START", "etitle": "Always Lateral", "edescr": "Allow lateral control even when cruise is not active.\n0: OFF, 1: ON", + "cgroup": "启动设置", + "ctitle": "全时横向控制", + "cdescr": "即使巡航未激活,也允许横向控制。\n0: 关闭, 1: 开启", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "全时横向控制", - "cdescr": "即使巡航未激活,也允许横向控制。\n0: 关闭, 1: 开启" + "unit": 1 }, { "group": "시작", @@ -1145,13 +1721,13 @@ "egroup": "START", "etitle": "Record RoadCam", "edescr": "1: Standard Camera, 2: Standard Camera + Wide Camera", + "cgroup": "启动设置", + "ctitle": "记录路面摄像头", + "cdescr": "1: 标准摄像头, 2: 标准摄像头 + 广角摄像头", "min": 0, "max": 2, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "记录路面摄像头", - "cdescr": "1: 标准摄像头, 2: 标准摄像头 + 广角摄像头" + "unit": 1 }, { "group": "시작", @@ -1161,13 +1737,13 @@ "egroup": "START", "etitle": "Use HDP(CCNC)", "edescr": "1:While using APN, 2:Always", + "cgroup": "启动设置", + "ctitle": "使用 HDP (CCNC)", + "cdescr": "1: 使用 APN 时, 2: 始终", "min": 0, "max": 2, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "使用 HDP (CCNC)", - "cdescr": "1: 使用 APN 时, 2: 始终" + "unit": 1 }, { "group": "시작", @@ -1177,13 +1753,13 @@ "egroup": "START", "etitle": "MapboxStyle", "edescr": "0: Default Background, 1: Dark Background, 2: Satellite Map", + "cgroup": "启动设置", + "ctitle": "Mapbox 样式", + "cdescr": "0: 默认背景, 1: 深色背景, 2: 卫星地图", "min": 0, "max": 2, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "Mapbox 样式", - "cdescr": "0: 默认背景, 1: 深色背景, 2: 卫星地图" + "unit": 1 }, { "group": "시작", @@ -1193,13 +1769,13 @@ "egroup": "START", "etitle": "DisableMinSteerSpeed", "edescr": "smdps equiped: 1", + "cgroup": "启动设置", + "ctitle": "解除最小转向速度限制", + "cdescr": "配备 smdps: 1", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "解除最小转向速度限制", - "cdescr": "配备 smdps: 1" + "unit": 1 }, { "group": "시작", @@ -1209,13 +1785,13 @@ "egroup": "START", "etitle": "Speed from PCM(StockSCC)", "edescr": "0: Curve/Camera Deceleration + Long, 1: No Button Spamming (Stock SCC), 2: Curve/Camera Decel, 3: Honda/Toyota", + "cgroup": "启动设置", + "ctitle": "来自 PCM 的速度 (原厂 SCC)", + "cdescr": "0: 弯道/摄像头减速 + 纵向控制, 1: 无按钮连发 (原厂 SCC), 2: 弯道/摄像头减速, 3: 本田/丰田", "min": 0, "max": 3, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "来自 PCM 的速度 (原厂 SCC)", - "cdescr": "0: 弯道/摄像头减速 + 纵向控制, 1: 无按钮连发 (原厂 SCC), 2: 弯道/摄像头减速, 3: 本田/丰田" + "unit": 1 }, { "group": "시작", @@ -1225,13 +1801,13 @@ "egroup": "START", "etitle": "PowerOffTime(min)", "edescr": "When the engine is turned off and the specified time elapses, the comma device will shut down.", + "cgroup": "启动设置", + "ctitle": "关机时间 (分钟)", + "cdescr": "发动机熄火且经过指定时间后,comma 设备将关机。", "min": 0, "max": 3000, "default": 60, - "unit": 10, - "cgroup": "启动设置", - "ctitle": "关机时间 (分钟)", - "cdescr": "发动机熄火且经过指定时间后,comma 设备将关机。" + "unit": 10 }, { "group": "시작", @@ -1241,13 +1817,13 @@ "egroup": "START", "etitle": "DisableDM", "edescr": "1.DisableDM, 2: +EnableWebRTC, Reboot required", + "cgroup": "启动设置", + "ctitle": "禁用驾驶员监控", + "cdescr": "1. 禁用 DM, 2: + 开启 WebRTC, 需要重启", "min": 0, "max": 2, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "禁用驾驶员监控", - "cdescr": "1. 禁用 DM, 2: + 开启 WebRTC, 需要重启" + "unit": 1 }, { "group": "시작", @@ -1257,13 +1833,13 @@ "egroup": "START", "etitle": "MuteDoor", "edescr": "", + "cgroup": "启动设置", + "ctitle": "屏蔽车门感应", + "cdescr": "", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "屏蔽车门感应", - "cdescr": "" + "unit": 1 }, { "group": "시작", @@ -1273,13 +1849,13 @@ "egroup": "START", "etitle": "MuteSeatbelt", "edescr": "", + "cgroup": "启动设置", + "ctitle": "屏蔽安全带感应", + "cdescr": "", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "屏蔽安全带感应", - "cdescr": "" + "unit": 1 }, { "group": "크루즈", @@ -1289,13 +1865,13 @@ "egroup": "CRUISE", "etitle": "AutoSpeedUpx:(100)%", "edescr": "Automatically increases speed on lead car, up to (road speed x ratio)", + "cgroup": "巡航设置", + "ctitle": "自动加速至: (100)%", + "cdescr": "当前方有车时自动加速,最高至 (道路限速 x 比例)", "min": 0, "max": 200, "default": 0, - "unit": 10, - "cgroup": "巡航设置", - "ctitle": "自动加速至: (100)%", - "cdescr": "当前方有车时自动加速,最高至 (道路限速 x 比例)" + "unit": 10 }, { "group": "감속제어", @@ -1305,13 +1881,13 @@ "egroup": "CRUISE", "etitle": "AutoRoadLimitSpeedAdjust (50)%", "edescr": "-1: set roadlimitspeed, 100: new road speed, 50: median, 0: not change", + "cgroup": "巡航设置", + "ctitle": "自动道路限速调整 (50)%", + "cdescr": "-1: 设置道路限速, 100: 新道路速度, 50: 中值, 0: 不改变", "min": -1, "max": 100, "default": 0, - "unit": 10, - "cgroup": "巡航设置", - "ctitle": "自动道路限速调整 (50)%", - "cdescr": "-1: 设置道路限速, 100: 新道路速度, 50: 中值, 0: 不改变" + "unit": 10 }, { "group": "크루즈", @@ -1321,13 +1897,13 @@ "egroup": "CRUISE", "etitle": "Auto update Cruise speed", "edescr": "If accelerator is pressed and speed exceeds set speed, update set speed", + "cgroup": "巡航设置", + "ctitle": "自动更新巡航速度", + "cdescr": "如果踩下油门且速度超过设定速度,则更新设定速度", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "巡航设置", - "ctitle": "自动更新巡航速度", - "cdescr": "如果踩下油门且速度超过设定速度,则更新设定速度" + "unit": 1 }, { "group": "크루즈", @@ -1337,13 +1913,13 @@ "egroup": "CRUISE", "etitle": "Model Speed Apply Ratio", "edescr": "Applies the model-calculated driving speed by the set ratio.", + "cgroup": "巡航设置", + "ctitle": "模型车速应用比例", + "cdescr": "按设定比例应用模型计算的行驶速度。", "min": -120, "max": 120, "default": 0, - "unit": 1, - "cgroup": "巡航设置", - "ctitle": "模型车速应用比例", - "cdescr": "按设定比例应用模型计算的行驶速度。" + "unit": 1 }, { "group": "화면", @@ -1353,13 +1929,13 @@ "egroup": "DISP", "etitle": "Show Debug Info", "edescr": "", + "cgroup": "显示设置", + "ctitle": "显示调试信息", + "cdescr": "", "min": 0, "max": 2, "default": 1, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示调试信息", - "cdescr": "" + "unit": 1 }, { "group": "화면", @@ -1369,13 +1945,13 @@ "egroup": "DISP", "etitle": "Show TPMS Info", "edescr": "0:None, 1: Upper, 2:Lower, 3: Both", + "cgroup": "显示设置", + "ctitle": "显示胎压 (TPMS) 信息", + "cdescr": "0: 无, 1: 上方, 2: 下方, 3: 两侧", "min": 0, "max": 3, "default": 1, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示胎压 (TPMS) 信息", - "cdescr": "0: 无, 1: 上方, 2: 下方, 3: 两侧" + "unit": 1 }, { "group": "화면", @@ -1385,13 +1961,13 @@ "egroup": "DISP", "etitle": "Show Time Info", "edescr": "0:None, 1:Time/Date, 2:Time, 3:Date", + "cgroup": "显示设置", + "ctitle": "显示时间信息", + "cdescr": "0: 无, 1: 时间/日期, 2: 时间, 3: 日期", "min": 0, "max": 3, "default": 1, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示时间信息", - "cdescr": "0: 无, 1: 时间/日期, 2: 时间, 3: 日期" + "unit": 1 }, { "group": "화면", @@ -1401,13 +1977,13 @@ "egroup": "DISP", "etitle": "Show Path End", "edescr": "", + "cgroup": "显示设置", + "ctitle": "显示路径终点", + "cdescr": "", "min": 0, "max": 1, "default": 1, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示路径终点", - "cdescr": "" + "unit": 1 }, { "group": "화면", @@ -1417,29 +1993,13 @@ "egroup": "DISP", "etitle": "Show Device Info", "edescr": "", - "min": 0, - "max": 1, - "default": 1, - "unit": 1, "cgroup": "显示设置", "ctitle": "显示设备信息", - "cdescr": "" - }, - { - "group": "화면", - "name": "ShowCustomBrightness", - "title": "화면밝기비율", - "descr": "화면 밝기를 조절합니다.\n터치나 이벤트 발생 시 일시적으로 밝아집니다.", - "egroup": "DISP", - "etitle": "ScreenBrightness ratio", - "edescr": "Adjusts screen brightness. Temporarily brightens on touch or event.", + "cdescr": "", "min": 0, - "max": 100, - "default": 100, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "屏幕亮度比例", - "cdescr": "调整屏幕亮度。触摸或发生事件时临时变亮。" + "max": 1, + "default": 1, + "unit": 1 }, { "group": "화면", @@ -1449,13 +2009,13 @@ "egroup": "DISP", "etitle": "Show Lane Info", "edescr": "-1:None, 0:Path, 1:Path+Lane, 2:Path+Lane+RoadEdge", + "cgroup": "显示设置", + "ctitle": "显示车道信息", + "cdescr": "-1: 无, 0: 路径, 1: 路径+车道, 2: 路径+车道+路缘", "min": -1, "max": 2, "default": 1, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示车道信息", - "cdescr": "-1: 无, 0: 路径, 1: 路径+车道, 2: 路径+车道+路缘" + "unit": 1 }, { "group": "화면", @@ -1465,13 +2025,13 @@ "egroup": "DISP", "etitle": "Show Radar Info", "edescr": "0:None,1:display,2:+relative pos, 3:stopped obstacle", + "cgroup": "显示设置", + "ctitle": "显示雷达信息", + "cdescr": "0: 无, 1: 显示, 2: + 相对位置, 3: 停止的障碍物", "min": 0, "max": 3, "default": 0, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示雷达信息", - "cdescr": "0: 无, 1: 显示, 2: + 相对位置, 3: 停止的障碍物" + "unit": 1 }, { "group": "화면", @@ -1481,13 +2041,13 @@ "egroup": "DISP", "etitle": "Show Route Info", "edescr": "Operates only when connected to APN. 0: Off, 1: On", + "cgroup": "显示设置", + "ctitle": "显示路线信息", + "cdescr": "仅在连接 APN 时运行。0: 关闭, 1: 开启", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示路线信息", - "cdescr": "仅在连接 APN 时运行。0: 关闭, 1: 开启" + "unit": 1 }, { "group": "화면패스", @@ -1497,13 +2057,13 @@ "egroup": "DISP", "etitle": "Tire Trajectory", "edescr": "Shows the tire trajectory gradient on the screen.\n 0: Off, 1: On", + "cgroup": "显示设置", + "ctitle": "显示轮胎轨迹", + "cdescr": "显示轮胎轨迹。0: 关闭, 1: 开启", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示轮胎轨迹", - "cdescr": "显示轮胎轨迹。0: 关闭, 1: 开启" + "unit": 1 }, { "group": "화면패스", @@ -1513,13 +2073,13 @@ "egroup": "DISP", "etitle": "Show Path Mode:LaneLess(9)", "edescr": "0:stock,1,2:REC,3,4:^^,5,6:REC,7,8:^^,9,10,11:smooth^^,12:smooth2^^,13:3lines,14:2lines,15:1lines", + "cgroup": "显示设置", + "ctitle": "显示路径模式: 无车道 (9)", + "cdescr": "0: 原厂, 1,2: 矩形, 3,4: ^^, 5,6: 矩形, 7,8: ^^, 9,10,11: 平滑^^, 12: 平滑2^^, 13: 3线, 14: 2线, 15: 1线", "min": 0, "max": 15, "default": 9, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示路径模式: 无车道 (9)", - "cdescr": "0: 原厂, 1,2: 矩形, 3,4: ^^, 5,6: 矩形, 7,8: ^^, 9,10,11: 平滑^^, 12: 平滑2^^, 13: 3线, 14: 2线, 15: 1线" + "unit": 1 }, { "group": "화면패스", @@ -1529,13 +2089,13 @@ "egroup": "DISP", "etitle": "Show Path Color:LaneLess(12)", "edescr": "(+10:stroke)0(R),1(O),2(Y),3(B),5(N),6(V),7(K),8(W),9(B),20:Auto", + "cgroup": "显示设置", + "ctitle": "显示路径颜色: 无车道 (12)", + "cdescr": "(+10: 描边) 0(红), 1(橙), 2(黄), 3(蓝), 5(深蓝), 6(紫), 7(黑), 8(白), 9(蓝), 20: 自动", "min": 0, "max": 20, "default": 12, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示路径颜色: 无车道 (12)", - "cdescr": "(+10: 描边) 0(红), 1(橙), 2(黄), 3(蓝), 5(深蓝), 6(紫), 7(黑), 8(白), 9(蓝), 20: 自动" + "unit": 1 }, { "group": "화면패스", @@ -1545,13 +2105,13 @@ "egroup": "DISP", "etitle": "Show Path Color:CruiseOFF(1)", "edescr": "(+10:stroke)0(R),1(O),2(Y),3(B),5(N),6(V),7(K),8(W),9(B)", + "cgroup": "显示设置", + "ctitle": "显示路径颜色: 巡航关闭 (1)", + "cdescr": "(+10:stroke)0(R),1(O),2(Y),3(B),5(N),6(V),7(K),8(W),9(B)", "min": 0, "max": 19, "default": 1, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示路径颜色: 巡航关闭 (1)", - "cdescr": "(+10:stroke)0(R),1(O),2(Y),3(B),5(N),6(V),7(K),8(W),9(B)" + "unit": 1 }, { "group": "화면패스", @@ -1561,13 +2121,13 @@ "egroup": "DISP", "etitle": "Show Path Mode:LaneMode(11)", "edescr": "0:stock,1,2:REC,3,4:^^,5,6:REC,7,8:^^,9,10,11:smooth^^,12:smooth2^^,13:3lines,14:2lines,15:1lines", + "cgroup": "显示设置", + "ctitle": "显示路径模式: 车道模式 (11)", + "cdescr": "0: 原厂, 1,2: 矩形, 3,4: ^^, 5,6: 矩形, 7,8: ^^, 9,10,11: 平滑^^, 12: 平滑2^^, 13: 3线, 14: 2线, 15: 1线", "min": 0, "max": 15, "default": 11, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示路径模式: 车道模式 (11)", - "cdescr": "0: 原厂, 1,2: 矩形, 3,4: ^^, 5,6: 矩形, 7,8: ^^, 9,10,11: 平滑^^, 12: 平滑2^^, 13: 3线, 14: 2线, 15: 1线" + "unit": 1 }, { "group": "화면패스", @@ -1577,13 +2137,13 @@ "egroup": "DISP", "etitle": "Show Path Color:LaneMode(3)", "edescr": "(+10:stroke)0(R),1(O),2(Y),3(B),5(N),6(V),7(K),8(W),9(B),20:Auto", + "cgroup": "显示设置", + "ctitle": "显示路径颜色: 车道模式 (3)", + "cdescr": "(+10: 描边) 0(红), 1(橙), 2(黄), 3(蓝), 5(深蓝), 6(紫), 7(黑), 8(白), 9(蓝), 20: 自动", "min": 0, "max": 20, "default": 3, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示路径颜色: 车道模式 (3)", - "cdescr": "(+10: 描边) 0(红), 1(橙), 2(黄), 3(蓝), 5(深蓝), 6(紫), 7(黑), 8(白), 9(蓝), 20: 自动" + "unit": 1 }, { "group": "화면", @@ -1593,13 +2153,13 @@ "egroup": "DISP", "etitle": "Show Debug Plot(0)", "edescr": "0:Off, 1:Accel, 2:Speed+Accel, 3:Model, 4:Lead, 5:Lead2, 6:Steer, 7:SteerA, 8:Curvature", + "cgroup": "显示设置", + "ctitle": "显示调试图表 (0)", + "cdescr": "0:关, 1:加速度, 2:速度+加速度, 3:模型, 4:前车, 5:前车2, 6:转向, 7:转角, 8:曲率", "min": 0, "max": 8, "default": 0, - "unit": 1, - "cgroup": "显示设置", - "ctitle": "显示调试图表 (0)", - "cdescr": "0:关, 1:加速度, 2:速度+加速度, 3:模型, 4:前车, 5:前车2, 6:转向, 7:转角, 8:曲率" + "unit": 1 }, { "group": "레인모드", @@ -1609,13 +2169,13 @@ "egroup": "LANE", "etitle": "Laneline: Speed(0)", "edescr": "Switches to LaneMode when speed exceeds the set speed", + "cgroup": "转向", + "ctitle": "车道线: 速度 (0)", + "cdescr": "当速度超过设定速度时切换到车道模式", "min": 0, "max": 200, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "车道线: 速度 (0)", - "cdescr": "当速度超过设定速度时切换到车道模式" + "unit": 1 }, { "group": "레인모드", @@ -1625,13 +2185,13 @@ "egroup": "LANE", "etitle": "Laneline: Curve Speed(0)", "edescr": "LaneMode is not used below the set speed", + "cgroup": "转向", + "ctitle": "车道线: 弯道速度 (0)", + "cdescr": "低于设定速度时不使用车道模式", "min": 0, "max": 200, "default": 0, - "unit": 10, - "cgroup": "转向", - "ctitle": "车道线: 弯道速度 (0)", - "cdescr": "低于设定速度时不使用车道模式" + "unit": 10 }, { "group": "레인모드", @@ -1641,13 +2201,13 @@ "egroup": "LANE", "etitle": "LaneLine: AdjustLaneOffset (0)cm", "edescr": "Corrects toward the road edge/inside of the curve", + "cgroup": "转向", + "ctitle": "车道线: 车道偏移调整 (0)cm", + "cdescr": "向路缘/弯道内侧修正", "min": 0, "max": 500, "default": 0, - "unit": 10, - "cgroup": "转向", - "ctitle": "车道线: 车道偏移调整 (0)cm", - "cdescr": "向路缘/弯道内侧修正" + "unit": 10 }, { "group": "조향일반", @@ -1657,13 +2217,13 @@ "egroup": "LAT", "etitle": "LaneChange use steering torque", "edescr": "0:immediate, 1: need torque(nudge), -1: auto lane change off", + "cgroup": "转向", + "ctitle": "变道使用转向力矩", + "cdescr": "0: 立即变道, 1: 需要力矩 (轻拨), -1: 自动变道关闭", "min": -1, "max": 1, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "变道使用转向力矩", - "cdescr": "0: 立即变道, 1: 需要力矩 (轻拨), -1: 自动变道关闭" + "unit": 1 }, { "group": "조향일반", @@ -1673,13 +2233,13 @@ "egroup": "LAT", "etitle": "LaneChange delayx0.1s(0)", "edescr": "Changes lanes after the set time once the turn signal is activated", + "cgroup": "转向", + "ctitle": "变道延迟 x0.1s(0)", + "cdescr": "开启转向灯后在设定时间后变道", "min": 0, "max": 100, "default": 0, - "unit": 10, - "cgroup": "转向", - "ctitle": "变道延迟 x0.1s(0)", - "cdescr": "开启转向灯后在设定时间后变道" + "unit": 10 }, { "group": "조향일반", @@ -1689,13 +2249,13 @@ "egroup": "LAT", "etitle": "LaneChange BSD", "edescr": "0:BSD detect(allow steer torque), 1: block steer torque, -1: ignore BSD", + "cgroup": "转向", + "ctitle": "变道盲点监测 (BSD)", + "cdescr": "0: BSD 检测 (允许转向力矩), 1: 阻止转向力矩, -1: 忽略 BSD", "min": -1, "max": 1, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "变道盲点监测 (BSD)", - "cdescr": "0: BSD 检测 (允许转向力矩), 1: 阻止转向力矩, -1: 忽略 BSD" + "unit": 1 }, { "group": "조향일반", @@ -1705,13 +2265,13 @@ "egroup": "LAT", "etitle": "LaneChange LineCheck", "edescr": "0: Color+Type(block yellow), 1: Type only, 2: Type+torque override solid", + "cgroup": "转向", + "ctitle": "变道车道判断", + "cdescr": "0: 颜色+类型(黄线阻止), 1: 仅类型(虚线/实线), 2: 仅类型+实线扭矩override", "min": 0, "max": 2, "default": 0, - "unit": 1, - "cgroup": "转向", - "ctitle": "变道车道判断", - "cdescr": "0: 颜色+类型(黄线阻止), 1: 仅类型(虚线/实线), 2: 仅类型+实线扭矩override" + "unit": 1 }, { "group": "시작", @@ -1721,13 +2281,13 @@ "egroup": "START", "etitle": "HapticFeedbackSpeedCamera(0)", "edescr": "0: Disabled, 1: Vibration, 2: Instrument Cluster, 3: HUD Display", + "cgroup": "启动设置", + "ctitle": "超速照相触感反馈 (0)", + "cdescr": "0: 禁用, 1: 振动, 2: 仪表盘, 3: HUD 显示", "min": 0, "max": 3, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "超速照相触感反馈 (0)", - "cdescr": "0: 禁用, 1: 振动, 2: 仪表盘, 3: HUD 显示" + "unit": 1 }, { "group": "시작", @@ -1737,13 +2297,13 @@ "egroup": "START", "etitle": "MaxAngleFrames(89)", "edescr": "89 Recommeded", + "cgroup": "启动设置", + "ctitle": "最大角度帧数 (89)", + "cdescr": "推荐 89", "min": 80, "max": 100, "default": 89, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "最大角度帧数 (89)", - "cdescr": "推荐 89" + "unit": 1 }, { "group": "시작", @@ -1753,13 +2313,13 @@ "egroup": "START", "etitle": "HYUNDAI: CAMERA SCC(0)", "edescr": "1: Long-con Vehicle, 2: Cruise State Sync (CAN-FD Vehicle), 3: Stock Cruise (CANFD Long harness)", + "cgroup": "启动设置", + "ctitle": "现代: 摄像头 SCC (0)", + "cdescr": "1: 纵向控制车辆, 2: 巡航状态同步 (CAN-FD 车辆), 3: 原厂巡航 (CANFD 纵向线束)", "min": 0, "max": 3, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "现代: 摄像头 SCC (0)", - "cdescr": "1: 纵向控制车辆, 2: 巡航状态同步 (CAN-FD 车辆), 3: 原厂巡航 (CANFD 纵向线束)" + "unit": 1 }, { "group": "시작", @@ -1769,13 +2329,13 @@ "egroup": "START", "etitle": "HYUNDAI: LDWS ONLY CAR(0)", "edescr": "LDWS ONLY CAR", + "cgroup": "启动设置", + "ctitle": "现代: 仅配备 LDWS 的车辆 (0)", + "cdescr": "仅配备 LDWS 的车辆", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "现代: 仅配备 LDWS 的车辆 (0)", - "cdescr": "仅配备 LDWS 的车辆" + "unit": 1 }, { "group": "시작", @@ -1785,13 +2345,13 @@ "egroup": "START", "etitle": "CANFD HDA2(0)", "edescr": "0:HDA1, 1: HDA2", + "cgroup": "启动设置", + "ctitle": "CANFD HDA2 (0)", + "cdescr": "0: HDA1, 1: HDA2", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "CANFD HDA2 (0)", - "cdescr": "0: HDA1, 1: HDA2" + "unit": 1 }, { "group": "시작", @@ -1801,13 +2361,13 @@ "egroup": "START", "etitle": "CANFD Debug(0)", "edescr": "", + "cgroup": "启动设置", + "ctitle": "CANFD 调试 (0)", + "cdescr": "", "min": -1, "max": 128, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "CANFD 调试 (0)", - "cdescr": "" + "unit": 1 }, { "group": "레이더설정", @@ -1817,13 +2377,13 @@ "egroup": "RADAR", "etitle": "Enable radar tracks(0)", "edescr": "-2: VOACC vision-only test\n-1: Always use SCC\n0: Use SCC radar\n1: Use radar tracks\n2: Radar tracks + low-speed SCC\n3: Radar tracks + low-speed SCC + cut-in vehicles + stealth vehicles", + "cgroup": "雷达设置", + "ctitle": "开启雷达轨迹 (0)", + "cdescr": "-1: 始终使用 SCC\n0: 使用 SCC 雷达\n1: 使用雷达轨迹\n2: 雷达轨迹 + 低速 SCC\n3: 雷达轨迹 + 低速 SCC + 切入车辆 + 隐形车辆", "min": -2, "max": 3, "default": 0, - "unit": 1, - "cgroup": "雷达设置", - "ctitle": "开启雷达轨迹 (0)", - "cdescr": "-1: 始终使用 SCC\n0: 使用 SCC 雷达\n1: 使用雷达轨迹\n2: 雷达轨迹 + 低速 SCC\n3: 雷达轨迹 + 低速 SCC + 切入车辆 + 隐形车辆" + "unit": 1 }, { "group": "레이더설정", @@ -1833,13 +2393,13 @@ "egroup": "RADAR", "etitle": "Cut-in detection sensitivity(0)", "edescr": "Higher values catch lane-entering vehicles earlier, but too high may mistake adjacent-lane vehicles for leads.", + "cgroup": "雷达设置", + "ctitle": "切入检测灵敏度 (0)", + "cdescr": "值越高越早发现从旁边车道驶入的车辆,但太高可能把旁边车道车辆当作前车", "min": 0, "max": 1000, "default": 0, - "unit": 10, - "cgroup": "雷达设置", - "ctitle": "切入检测灵敏度 (0)", - "cdescr": "值越高越早发现从旁边车道驶入的车辆,但太高可能把旁边车道车辆当作前车" + "unit": 10 }, { "group": "레이더설정", @@ -1849,13 +2409,13 @@ "egroup": "RADAR", "etitle": "Enable Corner radar(0)", "edescr": "1:Enable Corner radar, 2: Cutin Detection for CCNC car", + "cgroup": "雷达设置", + "ctitle": "开启角雷达 (0)", + "cdescr": "1: 开启角雷达, 2: CCNC 车辆的切入检测", "min": 0, "max": 2, "default": 0, - "unit": 1, - "cgroup": "雷达设置", - "ctitle": "开启角雷达 (0)", - "cdescr": "1: 开启角雷达, 2: CCNC 车辆的切入检测" + "unit": 1 }, { "group": "시작", @@ -1865,13 +2425,13 @@ "egroup": "START", "etitle": "Enable HotSpot(0)", "edescr": "Feature to automatically enable hotspot when a USIM is inserted in COMMA", + "cgroup": "启动设置", + "ctitle": "开启热点 (0)", + "cdescr": "在 COMMA 中插入 USIM 卡时自动开启热点的功能", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "开启热点 (0)", - "cdescr": "在 COMMA 中插入 USIM 卡时自动开启热点的功能" + "unit": 1 }, { "group": "시작", @@ -1881,13 +2441,13 @@ "egroup": "START", "etitle": "Enable Software menu", "edescr": "Turn off on memory error", + "cgroup": "启动设置", + "ctitle": "开启软件菜单", + "cdescr": "内存错误时关闭", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "启动设置", - "ctitle": "开启软件菜单", - "cdescr": "内存错误时关闭" + "unit": 1 }, { "group": "시작", @@ -1897,13 +2457,13 @@ "egroup": "START", "etitle": "Sound Volume adjust(100%)", "edescr": "", + "cgroup": "启动设置", + "ctitle": "音量调节 (100%)", + "cdescr": "", "min": 5, "max": 200, "default": 0, - "unit": 5, - "cgroup": "启动设置", - "ctitle": "音量调节 (100%)", - "cdescr": "" + "unit": 5 }, { "group": "시작", @@ -1913,13 +2473,13 @@ "egroup": "START", "etitle": "Sound Volume adjust,Engage(10%)", "edescr": "", + "cgroup": "启动设置", + "ctitle": "音量调节,激活音量 (10%)", + "cdescr": "", "min": 5, "max": 200, "default": 0, - "unit": 5, - "cgroup": "启动设置", - "ctitle": "音量调节,激活音量 (10%)", - "cdescr": "" + "unit": 5 }, { "group": "차량간격", @@ -1929,13 +2489,13 @@ "egroup": "FDIST", "etitle": "TF(1): TimeFollow1x0.01s(110)", "edescr": "Following Distance Level 1", + "cgroup": "跟车距离", + "ctitle": "跟车时间(1): TimeFollow1x0.01s(110)", + "cdescr": "跟车距离 1 级", "min": 40, "max": 300, "default": 110, - "unit": 5, - "cgroup": "跟车距离", - "ctitle": "跟车时间(1): TimeFollow1x0.01s(110)", - "cdescr": "跟车距离 1 级" + "unit": 5 }, { "group": "차량간격", @@ -1945,13 +2505,13 @@ "egroup": "FDIST", "etitle": "TF(2): TimeFollow2x0.01s(120)", "edescr": "Following Distance Level 2", + "cgroup": "跟车距离", + "ctitle": "跟车时间(2): TimeFollow2x0.01s(120)", + "cdescr": "跟车距离 2 级", "min": 40, "max": 300, "default": 120, - "unit": 5, - "cgroup": "跟车距离", - "ctitle": "跟车时间(2): TimeFollow2x0.01s(120)", - "cdescr": "跟车距离 2 级" + "unit": 5 }, { "group": "차량간격", @@ -1961,13 +2521,13 @@ "egroup": "FDIST", "etitle": "TF(3): TimeFollow3x0.01s(140)", "edescr": "Following Distance Level 3", + "cgroup": "跟车距离", + "ctitle": "跟车时间(3): TimeFollow3x0.01s(140)", + "cdescr": "跟车距离 3 级", "min": 40, "max": 300, "default": 140, - "unit": 5, - "cgroup": "跟车距离", - "ctitle": "跟车时间(3): TimeFollow3x0.01s(140)", - "cdescr": "跟车距离 3 级" + "unit": 5 }, { "group": "차량간격", @@ -1977,13 +2537,13 @@ "egroup": "FDIST", "etitle": "TF(4): TimeFollow4x0.01s(160)", "edescr": "Following Distance Level 4", + "cgroup": "跟车距离", + "ctitle": "跟车时间(4): TimeFollow4x0.01s(160)", + "cdescr": "跟车距离 4 级", "min": 40, "max": 300, "default": 160, - "unit": 5, - "cgroup": "跟车距离", - "ctitle": "跟车时间(4): TimeFollow4x0.01s(160)", - "cdescr": "跟车距离 4 级" + "unit": 5 }, { "group": "차량간격", @@ -1993,13 +2553,13 @@ "egroup": "FDIST", "etitle": "Dynamic TFollow", "edescr": "Dynamically adjusts following distance based on changes in lead vehicle motion", + "cgroup": "跟车距离", + "ctitle": "动态跟车时间", + "cdescr": "根据前车运动变化动态调整跟车距离", "min": 0, "max": 100, "default": 0, - "unit": 1, - "cgroup": "跟车距离", - "ctitle": "动态跟车时间", - "cdescr": "根据前车运动变化动态调整跟车距离" + "unit": 1 }, { "group": "차량간격", @@ -2009,13 +2569,13 @@ "egroup": "FDIST", "etitle": "EnableSpeedTF(0)", "edescr": "-1:0/30/60/90km/h steps\n-2:0/40/80/120km/h steps\n-3:0/50/100/150km/h steps\n1~50%:Reduce TF below 100km/h", + "cgroup": "跟车距离", + "ctitle": "开启基于速度的跟车时间 (0)", + "cdescr": "-1: 0/30/60/90km/h 分段\n-2: 0/40/80/120km/h 分段\n-3: 0/50/100/150km/h 分段\n1~50%: 100km/h 以下减小跟车时间", "min": -3, "max": 50, "default": 0, - "unit": 1, - "cgroup": "跟车距离", - "ctitle": "开启基于速度的跟车时间 (0)", - "cdescr": "-1: 0/30/60/90km/h 分段\n-2: 0/40/80/120km/h 分段\n-3: 0/50/100/150km/h 分段\n1~50%: 100km/h 以下减小跟车时间" + "unit": 1 }, { "group": "차량간격", @@ -2025,13 +2585,13 @@ "egroup": "FDIST", "etitle": "Dynamic TFollow LaneChange(100)%", "edescr": "Temporarily reduces the gap between vehicles when starting a lane change", + "cgroup": "跟车距离", + "ctitle": "变道动态跟车时间 (100)%", + "cdescr": "变道开始时临时缩小车距", "min": 20, "max": 100, "default": 0, - "unit": 5, - "cgroup": "跟车距离", - "ctitle": "变道动态跟车时间 (100)%", - "cdescr": "变道开始时临时缩小车距" + "unit": 5 }, { "group": "차량간격", @@ -2041,13 +2601,13 @@ "egroup": "FDIST", "etitle": "Decel TF Boost Ratio (30)", "edescr": "Extra t_follow boost ratio during deceleration. Higher values keep more following distance while slowing down.", + "cgroup": "跟车距离", + "ctitle": "减速TF增益比例(30)", + "cdescr": "减速时额外增加 t_follow 的比例。数值越大,减速时跟车距离越宽松。", "min": 0, "max": 100, "default": 10, - "unit": 10, - "cgroup": "跟车距离", - "ctitle": "减速TF增益比例(30)", - "cdescr": "减速时额外增加 t_follow 的比例。数值越大,减速时跟车距离越宽松。" + "unit": 10 }, { "group": "감속제어", @@ -2057,13 +2617,13 @@ "egroup": "FDIST", "etitle": "AutoCurveSpeed LowerLimit(30)", "edescr": "Curve Minimum Speed Setting", + "cgroup": "跟车距离", + "ctitle": "弯道自动减速下限 (30)", + "cdescr": "弯道最低速度设置", "min": 10, "max": 200, "default": 30, - "unit": 10, - "cgroup": "跟车距离", - "ctitle": "弯道自动减速下限 (30)", - "cdescr": "弯道最低速度设置" + "unit": 10 }, { "group": "주행튜닝", @@ -2073,13 +2633,13 @@ "egroup": "LONG", "etitle": "StoppingStartAccelx0.01(0)", "edescr": "stopping acceleration, 0: Traditional method", + "cgroup": "纵向控制", + "ctitle": "停止起步加速度 x0.01(0)", + "cdescr": "停止加速度,0: 传统方式", "min": -100, "max": 0, "default": 0, - "unit": 10, - "cgroup": "纵向控制", - "ctitle": "停止起步加速度 x0.01(0)", - "cdescr": "停止加速度,0: 传统方式" + "unit": 10 }, { "group": "가속설정", @@ -2089,13 +2649,13 @@ "egroup": "ACCEL", "etitle": "DrivingMode:Init(3)", "edescr": "1: Fuel Economy (ComfortBrake, 10% accel. reduction), 2: Safety (ComfortBrake, 20% accel. reduction), 3: Normal, 4: High Speed (Ignores signals, 20% accel. increase).", + "cgroup": "加速设置", + "ctitle": "驾驶模式: 初始 (3)", + "cdescr": "1: 经济 (舒适刹车, 加速度减少 10%), 2: 安全 (舒适刹车, 加速度减少 20%), 3: 普通, 4: 高速 (忽略信号, 加速度增加 20%)", "min": 1, "max": 4, "default": 3, - "unit": 1, - "cgroup": "加速设置", - "ctitle": "驾驶模式: 初始 (3)", - "cdescr": "1: 经济 (舒适刹车, 加速度减少 10%), 2: 安全 (舒适刹车, 加速度减少 20%), 3: 普通, 4: 高速 (忽略信号, 加速度增加 20%)" + "unit": 1 }, { "group": "가속설정", @@ -2105,13 +2665,13 @@ "egroup": "ACCEL", "etitle": "DrivingMode: Auto", "edescr": "Safety mode in Congested Conditions; Normal mode when lead vehicle accelerates quickly or 20+km/h.", + "cgroup": "加速设置", + "ctitle": "驾驶模式: 自动", + "cdescr": "拥堵路况进入安全模式;前车加速快或速度超过 20km/h 时进入普通模式", "min": 0, "max": 1, "default": 0, - "unit": 1, - "cgroup": "加速设置", - "ctitle": "驾驶模式: 自动", - "cdescr": "拥堵路况进入安全模式;前车加速快或速度超过 20km/h 时进入普通模式" + "unit": 1 }, { "group": "감속제어", @@ -2121,13 +2681,13 @@ "egroup": "SPEED", "etitle": "TrafficLightDetectMode", "edescr": "0:None,1:Stopping only, 2:Stop & Go", + "cgroup": "速度控制", + "ctitle": "红绿灯检测模式", + "cdescr": "0: 无, 1: 仅停止, 2: 停止和起步", "min": 0, "max": 2, "default": 2, - "unit": 1, - "cgroup": "速度控制", - "ctitle": "红绿灯检测模式", - "cdescr": "0: 无, 1: 仅停止, 2: 停止和起步" + "unit": 1 }, { "group": "감속제어", @@ -2137,13 +2697,13 @@ "egroup": "SPEED", "etitle": "NaviSpeedEndingPoint(6s)", "edescr": "Time until decel completes, based on speed x time", + "cgroup": "速度控制", + "ctitle": "导航减速结束点 (6秒)", + "cdescr": "基于 速度 x 时间 的减速完成时间", "min": 3, "max": 20, "default": 6, - "unit": 1, - "cgroup": "速度控制", - "ctitle": "导航减速结束点 (6秒)", - "cdescr": "基于 速度 x 时间 的减速完成时间" + "unit": 1 }, { "group": "감속제어", @@ -2153,13 +2713,13 @@ "egroup": "SPEED", "etitle": "NaviSpeedControlMode(2)", "edescr": "0: Not Use, 1: Speed cam, 2: cam + speed bump, 3: cam + bump + apn speed cam", + "cgroup": "速度控制", + "ctitle": "导航减速模式 (2)", + "cdescr": "0: 不使用, 1: 限速摄像头, 2: 摄像头 + 减速带, 3: 摄像头 + 减速带 + APN 限速摄像头", "min": 0, "max": 3, "default": 2, - "unit": 1, - "cgroup": "速度控制", - "ctitle": "导航减速模式 (2)", - "cdescr": "0: 不使用, 1: 限速摄像头, 2: 摄像头 + 减速带, 3: 摄像头 + 减速带 + APN 限速摄像头" + "unit": 1 }, { "group": "감속제어", @@ -2169,13 +2729,13 @@ "egroup": "SPEED", "etitle": "RoadSpeedLimitOffset(-1)", "edescr": "RoadLimitSpeed+Set Value, -1:Not Use", + "cgroup": "速度控制", + "ctitle": "道路限速偏移 (-1)", + "cdescr": "道路限速 + 设定值, -1: 不使用", "min": -1, "max": 100, "default": -1, - "unit": 1, - "cgroup": "速度控制", - "ctitle": "道路限速偏移 (-1)", - "cdescr": "道路限速 + 设定值, -1: 不使用" + "unit": 1 }, { "group": "감속제어", @@ -2185,13 +2745,13 @@ "egroup": "SPEED", "etitle": "SpeedBumpEndingPoint(1s)", "edescr": "", + "cgroup": "速度控制", + "ctitle": "减速带结束点 (1秒)", + "cdescr": "", "min": 1, "max": 50, "default": 1, - "unit": 1, - "cgroup": "速度控制", - "ctitle": "减速带结束点 (1秒)", - "cdescr": "" + "unit": 1 }, { "group": "감속제어", @@ -2201,13 +2761,13 @@ "egroup": "SPEED", "etitle": "SpeedBumpSpeed(35km/h)", "edescr": "", + "cgroup": "速度控制", + "ctitle": "减速带速度 (35km/h)", + "cdescr": "", "min": 10, "max": 100, "default": 35, - "unit": 5, - "cgroup": "速度控制", - "ctitle": "减速带速度 (35km/h)", - "cdescr": "" + "unit": 5 }, { "group": "감속제어", @@ -2217,13 +2777,13 @@ "egroup": "SPEED", "etitle": "NaviSpeedDecelRate 0.01m/s^2x(200)", "edescr": "The lower the value, the further away it decelerates.", + "cgroup": "速度控制", + "ctitle": "导航减速效率 0.01m/s^2x(200)", + "cdescr": "值越低,减速越早(距离越远)。", "min": 50, "max": 300, "default": 200, - "unit": 10, - "cgroup": "速度控制", - "ctitle": "导航减速效率 0.01m/s^2x(200)", - "cdescr": "值越低,减速越早(距离越远)。" + "unit": 10 }, { "group": "감속제어", @@ -2233,13 +2793,13 @@ "egroup": "SPEED", "etitle": "NaviCountDownMode", "edescr": "0:not use, 1: turn point + speed, 2: turn point + speed + bump", + "cgroup": "速度控制", + "ctitle": "导航倒计时模式", + "cdescr": "0: 不使用, 1: 转向点 + 速度, 2: 转向点 + 速度 + 减速带", "min": 0, "max": 2, "default": 2, - "unit": 1, - "cgroup": "速度控制", - "ctitle": "导航倒计时模式", - "cdescr": "0: 不使用, 1: 转向点 + 速度, 2: 转向点 + 速度 + 减速带" + "unit": 1 }, { "group": "감속제어", @@ -2249,13 +2809,13 @@ "egroup": "SPEED", "etitle": "TurnSpeedControlMode", "edescr": "0:not use, 1:vision, 2:vision+route, 3:route(always)", + "cgroup": "速度控制", + "ctitle": "转向速度控制模式", + "cdescr": "0: 不使用, 1: 视觉, 2: 视觉 + 路线, 3: 路线 (始终)", "min": 0, "max": 3, "default": 1, - "unit": 1, - "cgroup": "速度控制", - "ctitle": "转向速度控制模式", - "cdescr": "0: 不使用, 1: 视觉, 2: 视觉 + 路线, 3: 路线 (始终)" + "unit": 1 }, { "group": "감속제어", @@ -2265,13 +2825,13 @@ "egroup": "SPEED", "etitle": "MapTurnSpeedFactor", "edescr": "The smaller the value, the more the speed will decrease along the route.", + "cgroup": "速度控制", + "ctitle": "地图转向速度因子", + "cdescr": "值越小,沿路线减速越多。", "min": 50, "max": 300, "default": 100, - "unit": 10, - "cgroup": "速度控制", - "ctitle": "地图转向速度因子", - "cdescr": "值越小,沿路线减速越多。" + "unit": 10 }, { "group": "감속제어", @@ -2281,13 +2841,13 @@ "egroup": "SPEED", "etitle": "ModelTurnSpeed time(0)x0.1sec", "edescr": "future model speed", + "cgroup": "速度控制", + "ctitle": "模型转向速度时间 (0)x0.1s", + "cdescr": "未来模型速度", "min": 0, "max": 80, "default": 0, - "unit": 10, - "cgroup": "速度控制", - "ctitle": "模型转向速度时间 (0)x0.1s", - "cdescr": "未来模型速度" + "unit": 10 }, { "group": "감속제어", @@ -2297,23 +2857,23 @@ "egroup": "SPEED", "etitle": "NaviSpeedLimitRatio(105)%", "edescr": "Reduce speed to the road speed limit*set value at the speed camera", + "cgroup": "速度控制", + "ctitle": "导航限速比例 (105)%", + "cdescr": "在限速摄像头处将速度降至 道路限速 * 设定值", "min": 80, "max": 120, "default": 105, - "unit": 1, - "cgroup": "速度控制", - "ctitle": "导航限速比例 (105)%", - "cdescr": "在限速摄像头处将速度降至 道路限速 * 设定值" + "unit": 1 }, { "group": "화면", - "egroup": "DISPLAY", - "cgroup": "显示", "name": "ShowCustomBrightness", "title": "주행중 화면밝기(100%)", "descr": "주행 시작 약 10초 후 화면밝기 비율\n0: 주변밝기 자동조절\n100: 항상 최대 밝기", + "egroup": "DISPLAY", "etitle": "Onroad Screen Brightness(100%)", "edescr": "Screen brightness ratio ~10s after driving starts\n0: Auto adjust by ambient light\n100: Always max brightness", + "cgroup": "显示", "ctitle": "行车中屏幕亮度(100%)", "cdescr": "行车开始约10秒后的屏幕亮度比例\n0: 根据环境亮度自动调节\n100: 始终最大亮度", "min": 0, @@ -2323,13 +2883,13 @@ }, { "group": "화면", - "egroup": "DISPLAY", - "cgroup": "显示", "name": "ShowModelView", "title": "주행화면 표시 모드(C4)", "descr": "주행 중 화면 밝기가 설정값 근처까지 낮아졌을 때 표시 방식\n0: 카메라와 모델 표시\n1: 카메라만 표시\n2: 모델만 표시\n3: 카메라와 모델 숨김", + "egroup": "DISPLAY", "etitle": "Onroad Display Mode(C4)", "edescr": "Display mode when onroad screen brightness reaches the configured level\n0: Show camera and model\n1: Show camera only\n2: Show model only\n3: Hide camera and model", + "cgroup": "显示", "ctitle": "行车画面显示模式(C4)", "cdescr": "行车中屏幕亮度接近设定值时的显示方式\n0: 显示摄像头和模型\n1: 仅显示摄像头\n2: 仅显示模型\n3: 隐藏摄像头和模型", "min": 0, @@ -2339,13 +2899,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHud", "title": "외부 HUD 활성화(0)", "descr": "0: 끄기\n1: TURZX 9.2인치\n2: TURZX 12.3인치", + "egroup": "CLUSTER HUD", "etitle": "External HUD Display(0)", "edescr": "0: Off\n1: TURZX 9.2 inch\n2: TURZX 12.3 inch", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 显示(0)", "cdescr": "0: 关闭\n1: TURZX 9.2 英寸\n2: TURZX 12.3 英寸", "min": 0, @@ -2355,13 +2915,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHudDebug", - "title": "\uc678\ubd80 HUD \ub514\ubc84\uae45(0)", - "descr": "0: \uc628\ub85c\ub4dc\uc5d0\uc11c\ub9cc\n1: \ud56d\uc0c1\n2: \ud56d\uc0c1+UI\n3: \ud56d\uc0c1+Navi", + "title": "외부 HUD 디버깅(0)", + "descr": "0: 온로드에서만\n1: 항상\n2: 항상+UI\n3: 항상+Navi", + "egroup": "CLUSTER HUD", "etitle": "External HUD Debug(0)", "edescr": "0: Onroad only\n1: Always\n2: Always + UI\n3: Always + Navi", + "cgroup": "外部 HUD", "ctitle": "External HUD Debug(0)", "cdescr": "0: Onroad only\n1: Always\n2: Always + UI\n3: Always + Navi", "min": 0, @@ -2370,14 +2930,14 @@ "unit": 1 }, { - "group": "\uc678\ubd80 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "\u5916\u90e8 HUD", + "group": "외부 HUD", "name": "ClusterHudBrightness", "title": "외부 HUD 밝기(0)", "descr": "0: 자동\n1~100: 밝기", + "egroup": "CLUSTER HUD", "etitle": "External HUD Brightness(0)", "edescr": "0: Auto\n1-100: Brightness", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 亮度(0)", "cdescr": "0: 自动\n1-100: 亮度", "min": 0, @@ -2387,13 +2947,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHudEncoder", "title": "외부 HUD 인코더(0)", "descr": "0: 자동(하드웨어 H264 -> 소프트웨어 H264 -> JPEG)\n1: JPEG\n2: 하드웨어 H264\n3: 소프트웨어 H264(ffmpeg/libx264)", + "egroup": "CLUSTER HUD", "etitle": "External HUD Encoder(0)", "edescr": "0: Auto (Hardware H264 -> Software H264 -> JPEG)\n1: JPEG\n2: Hardware H264\n3: Software H264 (ffmpeg/libx264)", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 编码器(0)", "cdescr": "0: Auto (Hardware H264 -> Software H264 -> JPEG)\n1: JPEG\n2: Hardware H264\n3: Software H264 (ffmpeg/libx264)", "min": 0, @@ -2403,13 +2963,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHudTheme", "title": "외부 HUD 테마(0)", "descr": "0: 자동\n1: 다크\n2: 라이트", + "egroup": "CLUSTER HUD", "etitle": "External HUD Theme(0)", "edescr": "0: Auto\n1: Dark\n2: Light", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 主题(0)", "cdescr": "0: 自动\n1: 深色\n2: 浅色", "min": 0, @@ -2419,13 +2979,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHudLiveFps", "title": "외부 HUD 라이브 FPS 제한(1)", "descr": "0: 무제한(진단용)\n1: 10 FPS(기본)\n2: 20 FPS\n3: 30 FPS\n4: 40 FPS\n5: 50 FPS\n6: 60 FPS", + "egroup": "CLUSTER HUD", "etitle": "External HUD Live FPS Limit(1)", "edescr": "0: Uncapped (diagnostic)\n1: 10 FPS (default)\n2: 20 FPS\n3: 30 FPS\n4: 40 FPS\n5: 50 FPS\n6: 60 FPS", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 实时 FPS 限制(1)", "cdescr": "0: Uncapped (diagnostic)\n1: 10 FPS (default)\n2: 20 FPS\n3: 30 FPS\n4: 40 FPS\n5: 50 FPS\n6: 60 FPS", "min": 0, @@ -2435,13 +2995,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHudScreenMode", "title": "외부 HUD 화면 모드(0)", "descr": "0: 기본\n1: 디버그\n2: 디버그(시스템)\n3: 디버그(그래프1)\n4: 디버그(그래프2)", + "egroup": "CLUSTER HUD", "etitle": "External HUD Screen Mode(0)", "edescr": "0: Default\n1: Debug\n2: Debug (system)\n3: Debug (graph1)\n4: Debug (graph2)\n5: Navi Debug", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 画面模式(0)", "cdescr": "0: 默认\n1: 调试\n2: 调试(系统)\n3: 调试(图表1)\n4: 调试(图表2)", "min": 0, @@ -2451,13 +3011,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHudRadarInfo", "title": "외부 HUD 레이더 정보 표시(4)", "descr": "0: 표시 안함\n1: 속도(차량만)\n2: 속도,거리(차량만)\n3: 속도(모두)\n4: 속도,거리(모두)", + "egroup": "CLUSTER HUD", "etitle": "External HUD Radar Info(4)", "edescr": "0: Off\n1: Speed (vehicles only)\n2: Speed, distance (vehicles only)\n3: Speed (all)\n4: Speed, distance (all)", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 雷达信息(4)", "cdescr": "0: 关闭\n1: 速度(仅车辆)\n2: 速度,距离(仅车辆)\n3: 速度(全部)\n4: 速度,距离(全部)", "min": 0, @@ -2467,13 +3027,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHudRadarDisplay", "title": "외부 HUD 레이더 표시 옵션(0)", "descr": "0: 기본(가까운 레이더 포인트 병합)\n1: 상세(레이더 포인트 병합 안함)", + "egroup": "CLUSTER HUD", "etitle": "External HUD Radar Display(0)", "edescr": "0: Default (merge nearby radar points)\n1: Detail (raw radar points)", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 雷达显示选项(0)", "cdescr": "0: 默认(合并相近雷达点)\n1: 详细(不合并雷达点)", "min": 0, @@ -2483,13 +3043,13 @@ }, { "group": "외부 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "外部 HUD", "name": "ClusterHudRadarSourceColor", "title": "외부 HUD 레이더 소스 색구분(0)", "descr": "0: 기본(모든 차량 회색)\n1: 켜짐(SCC=R, HDA=B, RADAR=O)", + "egroup": "CLUSTER HUD", "etitle": "External HUD Radar Source Colors(0)", "edescr": "0: Default (all vehicles gray)\n1: On (SCC=R, HDA=B, RADAR=O)", + "cgroup": "外部 HUD", "ctitle": "外部 HUD 雷达来源颜色(0)", "cdescr": "0: 默认(所有车辆灰色)\n1: 开启(SCC=R, HDA=B, RADAR=O)", "min": 0, @@ -2498,14 +3058,14 @@ "unit": 1 }, { - "group": "\uc678\ubd80 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "\u5916\u90e8 HUD", + "group": "외부 HUD", "name": "ClusterHudCoreMode", - "title": "\uc678\ubd80 HUD CPU \ucf54\uc5b4 \uc9c0\uc815(0)", - "descr": "CLUSTER_REALTIME=1\uc77c \ub54c\ub9cc \uc801\uc6a9\n0: 1,2,3,4\n1: \ubaa8\ub4e0 \ucf54\uc5b4\n\ubcc0\uacbd \uc2dc \ud074\ub7ec\uc2a4\ud130 HUD\ub97c \uc7ac\uc2dc\uc791\ud569\ub2c8\ub2e4.", + "title": "외부 HUD CPU 코어 지정(0)", + "descr": "CLUSTER_REALTIME=1일 때만 적용\n0: 1,2,3,4\n1: 모든 코어\n변경 시 클러스터 HUD를 재시작합니다.", + "egroup": "CLUSTER HUD", "etitle": "External HUD CPU Core Mode(0)", "edescr": "Applies only when CLUSTER_REALTIME=1\n0: cores 1,2,3,4\n1: all cores\nChanging this restarts the cluster HUD process.", + "cgroup": "外部 HUD", "ctitle": "External HUD CPU Core Mode(0)", "cdescr": "Applies only when CLUSTER_REALTIME=1\n0: cores 1,2,3,4\n1: all cores\nChanging this restarts the cluster HUD process.", "min": 0, @@ -2514,14 +3074,14 @@ "unit": 1 }, { - "group": "\uc678\ubd80 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "\u5916\u90e8 HUD", + "group": "외부 HUD", "name": "ClusterHudPriority", - "title": "\uc678\ubd80 HUD \uc2e4\uc2dc\uac04 \uc6b0\uc120\uc21c\uc704(10)", - "descr": "CLUSTER_REALTIME=1\uc77c \ub54c\ub9cc \uc801\uc6a9\n1-99\n\uae30\ubcf8 10\n\ubcc0\uacbd \uc2dc \ud074\ub7ec\uc2a4\ud130 HUD\ub97c \uc7ac\uc2dc\uc791\ud569\ub2c8\ub2e4.", + "title": "외부 HUD 실시간 우선순위(10)", + "descr": "CLUSTER_REALTIME=1일 때만 적용\n1-99\n기본 10\n변경 시 클러스터 HUD를 재시작합니다.", + "egroup": "CLUSTER HUD", "etitle": "External HUD Realtime Priority(10)", "edescr": "Applies only when CLUSTER_REALTIME=1\n1-99\nDefault 10\nChanging this restarts the cluster HUD process.", + "cgroup": "外部 HUD", "ctitle": "External HUD Realtime Priority(10)", "cdescr": "Applies only when CLUSTER_REALTIME=1\n1-99\nDefault 10\nChanging this restarts the cluster HUD process.", "min": 1, @@ -2530,14 +3090,14 @@ "unit": 1 }, { - "group": "\uc678\ubd80 HUD", - "egroup": "CLUSTER HUD", - "cgroup": "\u5916\u90e8 HUD", + "group": "외부 HUD", "name": "ClusterHudCameraViewMode", - "title": "\uc678\ubd80 HUD \uce74\uba54\ub77c \ubdf0 \ubaa8\ub4dc(0)", - "descr": "0: \uae30\ubcf8\n1: \ubaa8\ub4dc1(\ud558\ub2e8\ubdf0)", + "title": "외부 HUD 카메라 뷰 모드(0)", + "descr": "0: 기본\n1: 모드1(하단뷰)", + "egroup": "CLUSTER HUD", "etitle": "External HUD Camera View Mode(0)", "edescr": "0: Default\n1: Mode 1 (bottom view)", + "cgroup": "外部 HUD", "ctitle": "External HUD Camera View Mode(0)", "cdescr": "0: Default\n1: Mode 1 (bottom view)", "min": 0,