From a4c558e7fcf4a31e668b16457d3e1b8c56e9b0b4 Mon Sep 17 00:00:00 2001 From: jominki354 Date: Wed, 29 Jul 2026 18:08:41 +0900 Subject: [PATCH] route work --- .../carrot/server/features/dashcam/catalog.py | 34 +- .../server/features/dashcam/upload_jobs.py | 295 +++++++++++- .../carrot/server/tests/test_web_upload.py | 223 ++++++++- .../web/css/generated/design-system.css | 2 +- .../carrot/web/css/generated/logs.css | 2 +- .../carrot/web/generated/asset-manifest.json | 8 +- .../selfdrive/carrot/web/js/generated/app.js | 2 +- .../selfdrive/carrot/web/js/generated/logs.js | 92 ++-- .../carrot/web/js/realtime/mini_hud_mode.js | 1 - .../carrot/web/js/translations/en.js | 22 + .../carrot/web/js/translations/ko.js | 24 +- .../carrot/web/js/translations/zh.js | 24 +- .../carrot/web/src/entries/design_system.css | 1 + .../carrot/web/src/features/logs/dashcam.js | 423 ++++++++++++------ .../carrot/web/src/features/logs/runtime.js | 13 +- .../carrot/web/src/features/logs/style.css | 216 --------- .../web/src/features/logs/upload_progress.js | 122 +++++ .../web/src/features/logs/upload_summary.js | 46 ++ .../web/src/ui/components/dialog/dialog.js | 103 ++++- .../web/src/ui/components/dialog/facade.js | 2 + .../web/src/ui/components/dialog/style.css | 136 +++++- .../src/ui/components/progress/progress.js | 97 ++++ .../web/src/ui/components/progress/style.css | 108 +++++ .../web/tests/dashcam_upload_summary.test.mjs | 45 ++ .../web/tests/logs_player_transport.test.mjs | 83 +++- .../web/tests/upload_progress_state.test.mjs | 130 ++++++ openpilot/selfdrive/carrot/web_upload.py | 37 +- 27 files changed, 1841 insertions(+), 450 deletions(-) create mode 100644 openpilot/selfdrive/carrot/web/src/features/logs/upload_progress.js create mode 100644 openpilot/selfdrive/carrot/web/src/features/logs/upload_summary.js create mode 100644 openpilot/selfdrive/carrot/web/src/ui/components/progress/progress.js create mode 100644 openpilot/selfdrive/carrot/web/src/ui/components/progress/style.css create mode 100644 openpilot/selfdrive/carrot/web/tests/dashcam_upload_summary.test.mjs create mode 100644 openpilot/selfdrive/carrot/web/tests/upload_progress_state.test.mjs diff --git a/openpilot/selfdrive/carrot/server/features/dashcam/catalog.py b/openpilot/selfdrive/carrot/server/features/dashcam/catalog.py index bef3d02a0f..71a8869081 100644 --- a/openpilot/selfdrive/carrot/server/features/dashcam/catalog.py +++ b/openpilot/selfdrive/carrot/server/features/dashcam/catalog.py @@ -14,8 +14,13 @@ DASHCAM_SEGMENT_SECONDS = 60 +QCAMERA_UPLOAD_NAMES = ("qcamera.ts", "qcamera.mp4") RLOG_SOURCE_NAMES = ("rlog.zst", "rlog.bz2", "rlog") QLOG_SOURCE_NAMES = ("qlog.zst", "qlog.bz2", "qlog") +UPLOAD_SOURCE_GROUPS = ( + ("qcamera", QCAMERA_UPLOAD_NAMES), + ("rlog", RLOG_SOURCE_NAMES), +) _MODERN_ROUTE_PATTERN = re.compile(r"^([0-9a-fA-F]{8})--([0-9a-fA-F]{10})$") # A finished segment's recording end time never changes, so once we've stat'd a @@ -242,13 +247,32 @@ def route_time_bounds(segments_asc: list[str]) -> tuple[int, int]: def segment_file_summary(segment_dir_path: str) -> list[dict[str, Any]]: + """Return the original files selected for a segment upload. + + Upload exactly one qcamera source and one rlog source. Prefer logger output + over browser-oriented derivatives and never include reduced or auxiliary + artifacts. An rlog is required because the uploaded segment cannot be + analyzed without it; qcamera is optional so log-only segments remain useful. + """ out: list[dict[str, Any]] = [] - for name in ("qcamera.mp4", "qcamera.ts", "rlog.zst", "rlog.bz2", "rlog", "qlog.zst", "qlog.bz2", "qlog"): - path = os.path.join(segment_dir_path, name) - if os.path.isfile(path): + for kind, names in UPLOAD_SOURCE_GROUPS: + for name in names: + path = os.path.join(segment_dir_path, name) try: + if not os.path.isfile(path): + continue size = os.path.getsize(path) except OSError: - size = 0 - out.append({"name": name, "size": size, "sizeLabel": file_size_label(size)}) + continue + if size <= 0: + continue + out.append({ + "kind": kind, + "name": name, + "size": size, + "sizeLabel": file_size_label(size), + }) + break + if not any(item["kind"] == "rlog" for item in out): + raise web.HTTPNotFound(text="rlog not found") return out diff --git a/openpilot/selfdrive/carrot/server/features/dashcam/upload_jobs.py b/openpilot/selfdrive/carrot/server/features/dashcam/upload_jobs.py index c9b868090e..2a25f143bc 100644 --- a/openpilot/selfdrive/carrot/server/features/dashcam/upload_jobs.py +++ b/openpilot/selfdrive/carrot/server/features/dashcam/upload_jobs.py @@ -4,6 +4,7 @@ import os import time import uuid +from collections import deque from datetime import datetime from typing import Any @@ -23,6 +24,28 @@ UPLOAD_JOB_KEEP_COUNT = 12 UPLOAD_JOB_MAX_LOG_CHARS = 60000 UPLOAD_JOB_STALE_SECONDS = 30 * 60 +UPLOAD_PHASE_QUEUED = "queued" +UPLOAD_PHASE_PREPARING = "preparing" +UPLOAD_PHASE_UPLOADING = "uploading" +UPLOAD_PHASE_NOTIFYING = "notifying" +UPLOAD_PHASE_CANCELING = "canceling" +UPLOAD_PHASE_COMPLETE = "complete" +UPLOAD_PHASE_CANCELED = "canceled" +UPLOAD_PHASE_FAILED = "failed" +UPLOAD_PREPARING_END_PERCENT = 8 +UPLOAD_TRANSFERRING_END_PERCENT = 97 +UPLOAD_NOTIFYING_START_PERCENT = 98 +UPLOAD_NOTIFYING_END_PERCENT = 99 +UPLOAD_PHASES = frozenset({ + UPLOAD_PHASE_QUEUED, + UPLOAD_PHASE_PREPARING, + UPLOAD_PHASE_UPLOADING, + UPLOAD_PHASE_NOTIFYING, + UPLOAD_PHASE_CANCELING, + UPLOAD_PHASE_COMPLETE, + UPLOAD_PHASE_CANCELED, + UPLOAD_PHASE_FAILED, +}) _jobs: dict[str, dict[str, Any]] = {} @@ -72,19 +95,63 @@ def progress( current: int | None = None, total: int | None = None, percent: int | None = None, + phase: str | None = None, + phase_current: int | None = None, + phase_total: int | None = None, + bytes_current: int | None = None, + bytes_total: int | None = None, + bytes_per_second: int | None = None, ) -> None: + changed = False if message is not None: - job["message"] = str(message) + normalized_message = str(message) + changed = changed or job.get("message") != normalized_message + job["message"] = normalized_message + if phase is not None: + normalized_phase = str(phase).strip().lower() + if normalized_phase not in UPLOAD_PHASES: + raise ValueError(f"unsupported upload phase: {phase}") + changed = changed or job.get("phase") != normalized_phase + job["phase"] = normalized_phase if current is not None: - job["step_current"] = max(0, int(current)) + normalized_current = max(0, int(current)) + changed = changed or job.get("step_current") != normalized_current + job["step_current"] = normalized_current if total is not None: - job["step_total"] = max(0, int(total)) + normalized_total = max(0, int(total)) + changed = changed or job.get("step_total") != normalized_total + job["step_total"] = normalized_total + if phase_current is not None: + normalized_phase_current = max(0, int(phase_current)) + changed = changed or job.get("phase_current") != normalized_phase_current + job["phase_current"] = normalized_phase_current + if phase_total is not None: + normalized_phase_total = max(0, int(phase_total)) + changed = changed or job.get("phase_total") != normalized_phase_total + job["phase_total"] = normalized_phase_total + if bytes_current is not None: + normalized_bytes_current = max(0, int(bytes_current)) + changed = changed or job.get("bytes_current") != normalized_bytes_current + job["bytes_current"] = normalized_bytes_current + if bytes_total is not None: + normalized_bytes_total = max(0, int(bytes_total)) + changed = changed or job.get("bytes_total") != normalized_bytes_total + job["bytes_total"] = normalized_bytes_total + if bytes_per_second is not None: + normalized_bytes_per_second = max(0, int(bytes_per_second)) + changed = changed or job.get("bytes_per_second") != normalized_bytes_per_second + job["bytes_per_second"] = normalized_bytes_per_second if percent is None: - c = job.get("step_current") - t = job.get("step_total") - if isinstance(c, int) and isinstance(t, int) and t > 0: - percent = int(max(0, min(100, round((c / t) * 100)))) - job["progress"] = percent + percent = job.get("progress") + if percent is not None: + normalized_percent = int(max(0, min(100, round(float(percent))))) + previous_percent = job.get("progress") + if isinstance(previous_percent, (int, float)) and job.get("status") == "running": + normalized_percent = max(int(previous_percent), normalized_percent) + changed = changed or previous_percent != normalized_percent + job["progress"] = normalized_percent + if changed: + job["revision"] = max(0, int(job.get("revision") or 0)) + 1 touch(job) @@ -104,7 +171,7 @@ def cancel_job(job_id: str) -> dict[str, Any]: if job.get("status") in ("done", "failed", "canceled"): return {"ok": True, "already_done": True, **snapshot(job)} job["cancel_requested"] = True - progress(job, message="Canceling upload") + progress(job, message="Canceling upload", phase=UPLOAD_PHASE_CANCELING) append(job, "Cancel requested") return {"ok": True, **snapshot(job)} @@ -119,9 +186,16 @@ def snapshot(job: dict[str, Any]) -> dict[str, Any]: "cancel_requested": bool(job.get("cancel_requested")), "log": job.get("log") or "", "progress": job.get("progress"), + "revision": max(0, int(job.get("revision") or 0)), + "phase": job.get("phase") or UPLOAD_PHASE_QUEUED, "message": job.get("message") or "", "step_current": job.get("step_current"), "step_total": job.get("step_total"), + "phase_current": job.get("phase_current"), + "phase_total": job.get("phase_total"), + "bytes_current": job.get("bytes_current"), + "bytes_total": job.get("bytes_total"), + "bytes_per_second": job.get("bytes_per_second"), "error": job.get("error"), "created_at": job.get("created_at"), "updated_at": job.get("updated_at"), @@ -137,11 +211,22 @@ def finish( error: str | None = None, status: str | None = None, ) -> None: - job["status"] = status or ("done" if ok else "failed") + final_status = status or ("done" if ok else "failed") + job["status"] = final_status + job["phase"] = ( + UPLOAD_PHASE_CANCELED + if final_status == "canceled" + else UPLOAD_PHASE_COMPLETE + if ok + else UPLOAD_PHASE_FAILED + ) job["result"] = result or {"ok": bool(ok)} job["error"] = error or (None if ok else job["result"].get("error")) if ok: job["progress"] = 100 + job["phase_current"] = 1 + job["phase_total"] = 1 + job["revision"] = max(0, int(job.get("revision") or 0)) + 1 touch(job) prune() @@ -205,9 +290,16 @@ def create_job(segments: list[str]) -> dict[str, Any]: "status": "running", "log": "", "progress": 0, + "revision": 0, + "phase": UPLOAD_PHASE_QUEUED, "message": "", "step_current": 0, "step_total": len(segments), + "phase_current": 0, + "phase_total": len(segments), + "bytes_current": 0, + "bytes_total": 0, + "bytes_per_second": 0, "error": None, "result": None, "cancel_requested": False, @@ -258,7 +350,16 @@ async def run_upload_segments(segments: list[str], job: dict[str, Any] | None = job["remote_base_path"] = remote_base_path job["upload_target"] = "web" job["partial_results"] = [] - progress(job, message="Preparing upload", current=0, total=total, percent=0) + progress( + job, + message="Preparing upload", + current=0, + total=total, + percent=0, + phase=UPLOAD_PHASE_PREPARING, + phase_current=0, + phase_total=total, + ) ensure_not_canceled(job) @@ -269,21 +370,139 @@ async def run_upload_segments(segments: list[str], job: dict[str, Any] | None = concurrency = max(1, min(6, int(os.environ.get("CARROT_WEB_UPLOAD_CONCURRENCY", "3") or "3"))) except Exception: concurrency = 3 + + prepare_sem = asyncio.Semaphore(concurrency) + + async def prepare_one(idx0: int, segment: str) -> tuple[int, list[Any], Exception | None]: + try: + async with prepare_sem: + return idx0, await asyncio.to_thread(segment_file_summary, segment_dir(segment)), None + except Exception as exc: + return idx0, [], exc + + prepared: list[tuple[list[Any], Exception | None] | None] = [None] * total + prepared_count = 0 + prepare_tasks = [ + asyncio.create_task(prepare_one(idx0, segment)) + for idx0, segment in enumerate(segments) + ] + try: + for prepare_task in asyncio.as_completed(prepare_tasks): + idx0, files, prepare_error = await prepare_task + prepared[idx0] = (files, prepare_error) + prepared_count += 1 + ensure_not_canceled(job) + if job: + prepare_percent = ( + round((prepared_count / total) * UPLOAD_PREPARING_END_PERCENT) + if total > 0 + else UPLOAD_PREPARING_END_PERCENT + ) + progress( + job, + message=f"Prepared {prepared_count}/{total}", + current=prepared_count, + total=total, + percent=prepare_percent, + phase=UPLOAD_PHASE_PREPARING, + phase_current=prepared_count, + phase_total=total, + ) + except BaseException: + for prepare_task in prepare_tasks: + if not prepare_task.done(): + prepare_task.cancel() + await asyncio.gather(*prepare_tasks, return_exceptions=True) + raise + + normalized_prepared = [ + item if item is not None else ([], RuntimeError("segment preparation did not complete")) + for item in prepared + ] + total_bytes = sum( + max(0, int(item.get("size") or 0)) + for files, error in normalized_prepared + if error is None + for item in files + ) + if job: + progress( + job, + message="Uploading files", + current=0, + total=total, + percent=UPLOAD_PREPARING_END_PERCENT, + phase=UPLOAD_PHASE_UPLOADING, + phase_current=0, + phase_total=total_bytes if total_bytes > 0 else total, + bytes_current=0, + bytes_total=total_bytes, + bytes_per_second=0, + ) + sem = asyncio.Semaphore(concurrency) completed = 0 + logical_bytes = 0 + transmitted_bytes = 0 + logical_by_file: dict[tuple[int, str], int] = {} + transfer_started = time.monotonic() + speed_samples: deque[tuple[float, int]] = deque([(transfer_started, 0)]) + + def current_percent() -> int: + byte_ratio = logical_bytes / total_bytes if total_bytes > 0 else 0 + step_ratio = completed / total if total > 0 else 0 + transfer_ratio = max(0.0, min(1.0, max(byte_ratio, step_ratio))) + transfer_span = UPLOAD_TRANSFERRING_END_PERCENT - UPLOAD_PREPARING_END_PERCENT + return min( + UPLOAD_TRANSFERRING_END_PERCENT, + UPLOAD_PREPARING_END_PERCENT + round(transfer_ratio * transfer_span), + ) async def upload_one(idx0: int, segment: str) -> None: - nonlocal completed + nonlocal completed, logical_bytes, transmitted_bytes idx = idx0 + 1 - files: list[Any] = [] + files, prepare_error = normalized_prepared[idx0] + + def on_file_progress(filename: str, sent: int, file_size: int, delta: int) -> None: + nonlocal logical_bytes, transmitted_bytes + if not job: + return + key = (idx0, filename) + previous = logical_by_file.get(key, 0) + bounded = max(0, min(int(sent), max(0, int(file_size)))) + current = max(previous, bounded) + logical_by_file[key] = current + logical_bytes += current - previous + transmitted_bytes += max(0, int(delta)) + + now = time.monotonic() + speed_samples.append((now, transmitted_bytes)) + while len(speed_samples) > 2 and now - speed_samples[0][0] > 3.0: + speed_samples.popleft() + sample_time, sample_bytes = speed_samples[0] + elapsed = max(0.001, now - sample_time) + current_bytes = min(total_bytes, logical_bytes) + progress( + job, + percent=current_percent(), + phase=UPLOAD_PHASE_UPLOADING, + phase_current=current_bytes if total_bytes > 0 else completed, + phase_total=total_bytes if total_bytes > 0 else total, + bytes_current=current_bytes, + bytes_total=total_bytes, + bytes_per_second=max(0, round((transmitted_bytes - sample_bytes) / elapsed)), + ) + async with sem: if is_cancel_requested(job): return if job: append(job, f"[{idx}/{total}] {segment}") try: + if prepare_error is not None: + raise prepare_error segment_path = segment_dir(segment) - files = await asyncio.to_thread(segment_file_summary, segment_path) + def should_cancel() -> bool: if job: touch(job) @@ -296,6 +515,8 @@ def should_cancel() -> bool: base_url, token, should_cancel if job else None, + filenames=[str(item["name"]) for item in files], + on_progress=on_file_progress if job else None, ) results[idx0] = { "segment": segment, @@ -325,7 +546,16 @@ def should_cancel() -> bool: completed += 1 if job: job["partial_results"] = [r for r in results if r is not None] - progress(job, message=f"Uploaded {completed}/{total}", current=completed, total=total) + progress( + job, + message=f"Uploaded {completed}/{total}", + current=completed, + total=total, + percent=current_percent(), + phase=UPLOAD_PHASE_UPLOADING, + phase_current=min(total_bytes, logical_bytes) if total_bytes > 0 else completed, + phase_total=total_bytes if total_bytes > 0 else total, + ) await asyncio.gather(*(upload_one(i, seg) for i, seg in enumerate(segments))) @@ -348,17 +578,48 @@ def should_cancel() -> bool: response_payload["shareText"] = upload.upload_share_text(response_payload) if job: - progress(job, message="Sending notification", current=total, total=total, percent=98) + progress( + job, + message="Sending notification", + current=total, + total=total, + percent=UPLOAD_NOTIFYING_START_PERCENT, + phase=UPLOAD_PHASE_NOTIFYING, + phase_current=0, + phase_total=2, + ) ensure_not_canceled(job) response_payload["webComplete"] = await send_web_upload_complete( base_url, token, response_payload, ) + if job: + progress( + job, + message="Sending notification", + current=total, + total=total, + percent=UPLOAD_NOTIFYING_END_PERCENT, + phase=UPLOAD_PHASE_NOTIFYING, + phase_current=1, + phase_total=2, + ) response_payload["discord"] = await upload.send_discord_webhook( upload.discord_webhook_url(params), response_payload, ) + if job: + progress( + job, + message="Finalizing upload", + current=total, + total=total, + percent=UPLOAD_NOTIFYING_END_PERCENT, + phase=UPLOAD_PHASE_NOTIFYING, + phase_current=2, + phase_total=2, + ) return response_payload @@ -386,7 +647,7 @@ async def run_job(job: dict[str, Any]) -> None: } result["shareText"] = upload.upload_share_text(result) append(job, "CANCELED") - progress(job, message="Upload canceled", percent=0) + progress(job, message="Upload canceled", percent=0, phase=UPLOAD_PHASE_CANCELED) finish(job, ok=False, result=result, error=str(exc), status="canceled") except Exception as exc: result = {"ok": False, "error": str(exc)} diff --git a/openpilot/selfdrive/carrot/server/tests/test_web_upload.py b/openpilot/selfdrive/carrot/server/tests/test_web_upload.py index 9e1b9a5588..8760cef618 100644 --- a/openpilot/selfdrive/carrot/server/tests/test_web_upload.py +++ b/openpilot/selfdrive/carrot/server/tests/test_web_upload.py @@ -3,8 +3,10 @@ from pathlib import Path import pytest +from aiohttp import web from openpilot.selfdrive.carrot import web_upload +from openpilot.selfdrive.carrot.server.features.dashcam import catalog from openpilot.selfdrive.carrot.server.features.dashcam import upload from openpilot.selfdrive.carrot.server.features.dashcam import upload_jobs from openpilot.selfdrive.carrot.server.services import dashcam_upload_report @@ -56,6 +58,104 @@ def test_dashcam_finished_task_cannot_leave_running_job(): upload_jobs.jobs().clear() +def test_dashcam_upload_job_exposes_stable_phase_codes(): + upload_jobs.jobs().clear() + job = upload_jobs.create_job(["route--0"]) + + assert upload_jobs.snapshot(job)["phase"] == "queued" + + upload_jobs.progress(job, current=1, total=1, phase=upload_jobs.UPLOAD_PHASE_UPLOADING) + assert upload_jobs.snapshot(job)["phase"] == "uploading" + + upload_jobs.finish(job, ok=True, result={"ok": True}) + snapshot = upload_jobs.snapshot(job) + assert snapshot["status"] == "done" + assert snapshot["phase"] == "complete" + upload_jobs.jobs().clear() + + +def test_dashcam_upload_progress_is_monotonic_and_revisioned(): + upload_jobs.jobs().clear() + job = upload_jobs.create_job(["route--0", "route--1"]) + assert upload_jobs.snapshot(job)["revision"] == 0 + + upload_jobs.progress( + job, + phase=upload_jobs.UPLOAD_PHASE_PREPARING, + current=1, + total=2, + phase_current=1, + phase_total=2, + percent=4, + ) + preparing = upload_jobs.snapshot(job) + assert preparing["progress"] == 4 + assert preparing["phase_current"] == 1 + assert preparing["phase_total"] == 2 + assert preparing["revision"] == 1 + + upload_jobs.progress( + job, + phase=upload_jobs.UPLOAD_PHASE_UPLOADING, + percent=2, + bytes_current=128, + bytes_total=1024, + bytes_per_second=512, + ) + uploading = upload_jobs.snapshot(job) + assert uploading["progress"] == 4 + assert uploading["bytes_current"] == 128 + assert uploading["bytes_total"] == 1024 + assert uploading["bytes_per_second"] == 512 + assert uploading["revision"] == 2 + + upload_jobs.finish(job, ok=True, result={"ok": True}) + complete = upload_jobs.snapshot(job) + assert complete["progress"] == 100 + assert complete["revision"] == 3 + upload_jobs.jobs().clear() + + +def test_dashcam_upload_cancel_and_failure_keep_visible_progress(): + upload_jobs.jobs().clear() + + canceled_job = upload_jobs.create_job(["route--0"]) + upload_jobs.progress( + canceled_job, + phase=upload_jobs.UPLOAD_PHASE_UPLOADING, + percent=41, + ) + canceled = upload_jobs.cancel_job(canceled_job["id"]) + assert canceled["phase"] == "canceling" + assert canceled["progress"] == 41 + upload_jobs.finish( + canceled_job, + ok=False, + status="canceled", + result={"ok": False, "canceled": True}, + ) + canceled = upload_jobs.snapshot(canceled_job) + assert canceled["phase"] == "canceled" + assert canceled["progress"] == 41 + + failed_job = upload_jobs.create_job(["route--1"]) + upload_jobs.progress( + failed_job, + phase=upload_jobs.UPLOAD_PHASE_UPLOADING, + percent=62, + ) + upload_jobs.finish( + failed_job, + ok=False, + error="network failed", + result={"ok": False, "error": "network failed"}, + ) + failed = upload_jobs.snapshot(failed_job) + assert failed["phase"] == "failed" + assert failed["progress"] == 62 + upload_jobs.jobs().clear() + + def test_dashcam_start_job_finalizes_unhandled_task_failure(monkeypatch): upload_jobs.jobs().clear() @@ -197,8 +297,15 @@ def test_dashcam_upload_report_does_not_merge_nonconsecutive_segments(): def test_dashcam_upload_completion_notifies_web_server_and_discord(monkeypatch): segment = "00000cfb--69588de3d7--10" notifications = [] + uploaded_files = [] + progress_snapshots = [] async def fake_upload_folder(*args, **kwargs): + uploaded_files.extend(kwargs["filenames"]) + on_progress = kwargs.get("on_progress") + if on_progress: + on_progress("qcamera.ts", 12, 12, 12) + on_progress("rlog.zst", 34, 34, 34) return True async def fake_web_complete(base_url, token, payload): @@ -219,11 +326,23 @@ async def fake_discord(webhook_url, payload): monkeypatch.setattr(upload, "discord_webhook_url", lambda params: "https://discord.example/webhook") monkeypatch.setattr(upload, "send_discord_webhook", fake_discord) monkeypatch.setattr(upload_jobs, "segment_dir", lambda value: "/tmp/segment") - monkeypatch.setattr(upload_jobs, "segment_file_summary", lambda value: []) + monkeypatch.setattr(upload_jobs, "segment_file_summary", lambda value: [ + {"kind": "qcamera", "name": "qcamera.ts", "size": 12}, + {"kind": "rlog", "name": "rlog.zst", "size": 34}, + ]) monkeypatch.setattr(upload_jobs, "upload_folder_to_web", fake_upload_folder) monkeypatch.setattr(upload_jobs, "send_web_upload_complete", fake_web_complete) + real_progress = upload_jobs.progress + + def capture_progress(job, **kwargs): + real_progress(job, **kwargs) + progress_snapshots.append(upload_jobs.snapshot(job)) - result = asyncio.run(upload_jobs.run_upload_segments([segment])) + monkeypatch.setattr(upload_jobs, "progress", capture_progress) + + upload_jobs.jobs().clear() + job = upload_jobs.create_job([segment]) + result = asyncio.run(upload_jobs.run_upload_segments([segment], job)) assert result["ok"] is True assert result["webComplete"] == {"ok": True, "status": 200} @@ -232,6 +351,28 @@ async def fake_discord(webhook_url, payload): ("web", "https://upload.example", "session-token", segment), ("discord", "https://discord.example/webhook", "shared upload report"), ] + assert uploaded_files == ["qcamera.ts", "rlog.zst"] + snapshot = upload_jobs.snapshot(job) + assert snapshot["bytes_current"] == 46 + assert snapshot["bytes_total"] == 46 + assert snapshot["bytes_per_second"] >= 0 + assert snapshot["step_current"] == 1 + assert snapshot["step_total"] == 1 + assert snapshot["progress"] == 99 + assert snapshot["phase"] == "notifying" + assert snapshot["phase_current"] == 2 + assert snapshot["phase_total"] == 2 + assert [item["progress"] for item in progress_snapshots] == sorted( + item["progress"] for item in progress_snapshots + ) + assert {"preparing", "uploading", "notifying"} <= { + item["phase"] for item in progress_snapshots + } + assert any( + item["phase"] == "preparing" and 0 < item["progress"] <= upload_jobs.UPLOAD_PREPARING_END_PERCENT + for item in progress_snapshots + ) + upload_jobs.jobs().clear() def test_tmux_target_uses_automatic_session_token(monkeypatch): @@ -452,6 +593,84 @@ def test_dashcam_web_upload_streams_and_verifies_every_file(tmp_path: Path, monk assert [request[2]["X-File-Size"] for request in session.requests] == ["11", "8"] +def test_dashcam_web_upload_reports_chunk_progress(tmp_path: Path, monkeypatch): + (tmp_path / "qcamera.ts").write_bytes(b"camera-data") + FakeSession.instances = [] + FakeSession.size_deltas = [] + monkeypatch.setattr(web_upload, "ClientSession", FakeSession) + updates = [] + + assert asyncio.run(web_upload.upload_folder_to_web( + str(tmp_path), + "device", + "route|0", + "https://upload.example", + "token", + on_progress=lambda *args: updates.append(args), + )) + + assert updates == [ + ("qcamera.ts", 0, 11, 0), + ("qcamera.ts", 11, 11, 11), + ] + + +def test_dashcam_upload_summary_selects_only_original_qcamera_and_rlog(tmp_path: Path): + (tmp_path / "qcamera.ts").write_bytes(b"original-video") + (tmp_path / "qcamera.mp4").write_bytes(b"converted-video") + (tmp_path / "rlog.zst").write_bytes(b"original-rlog") + (tmp_path / "rlog.bz2").write_bytes(b"fallback-rlog") + (tmp_path / "qlog.zst").write_bytes(b"reduced-log") + (tmp_path / "fcamera.hevc").write_bytes(b"auxiliary-video") + + files = catalog.segment_file_summary(str(tmp_path)) + + assert [(item["kind"], item["name"], item["size"]) for item in files] == [ + ("qcamera", "qcamera.ts", len(b"original-video")), + ("rlog", "rlog.zst", len(b"original-rlog")), + ] + + +def test_dashcam_upload_summary_allows_rlog_without_qcamera(tmp_path: Path): + (tmp_path / "rlog.bz2").write_bytes(b"original-rlog") + + files = catalog.segment_file_summary(str(tmp_path)) + + assert [(item["kind"], item["name"]) for item in files] == [("rlog", "rlog.bz2")] + + +def test_dashcam_upload_summary_requires_rlog(tmp_path: Path): + (tmp_path / "qcamera.ts").write_bytes(b"original-video") + + with pytest.raises(web.HTTPNotFound) as exc_info: + catalog.segment_file_summary(str(tmp_path)) + assert exc_info.value.text == "rlog not found" + + +def test_dashcam_web_upload_honors_explicit_file_selection(tmp_path: Path, monkeypatch): + (tmp_path / "qcamera.ts").write_bytes(b"camera-data") + (tmp_path / "rlog.zst").write_bytes(b"log-data") + (tmp_path / "qlog.zst").write_bytes(b"excluded-data") + FakeSession.instances = [] + FakeSession.size_deltas = [] + monkeypatch.setattr(web_upload, "ClientSession", FakeSession) + + assert asyncio.run(web_upload.upload_folder_to_web( + str(tmp_path), + "device", + "route|0", + "https://upload.example", + "token", + filenames=("qcamera.ts", "rlog.zst"), + )) + + session = FakeSession.instances[-1] + assert [request[0].rsplit("/", 1)[-1] for request in session.requests] == [ + "qcamera.ts", + "rlog.zst", + ] + + def test_dashcam_web_upload_retries_size_mismatch(tmp_path: Path, monkeypatch): (tmp_path / "qlog.zst").write_bytes(b"retry-me") FakeSession.instances = [] diff --git a/openpilot/selfdrive/carrot/web/css/generated/design-system.css b/openpilot/selfdrive/carrot/web/css/generated/design-system.css index 287c6e6ec2..0c125c9cbe 100644 --- a/openpilot/selfdrive/carrot/web/css/generated/design-system.css +++ b/openpilot/selfdrive/carrot/web/css/generated/design-system.css @@ -1,2 +1,2 @@ /* @generated by web/build.mjs; edit web/src/entries/design_system.css instead. */ -.card{background:var(--md-surface-cont);border:1px solid var(--md-stroke-soft);border-radius:var(--r-md);padding:var(--sp-lg);margin-bottom:var(--sp-lg)}.value-surface{background:var(--md-surface-cont);border:1px solid var(--md-stroke-soft);padding:var(--sp-sm) var(--sp-md);border-radius:var(--control-radius);font-size:var(--fs-body-sm);color:var(--md-on-surface-var)}.c-switch{--switch-w: 52px;--switch-h: 32px;position:relative;display:inline-flex;flex:0 0 var(--switch-w);width:var(--switch-w);height:var(--switch-h);cursor:pointer;-webkit-tap-highlight-color:transparent}.c-switch--sm{--switch-w: 44px;--switch-h: 28px}.c-switch__input{position:absolute;opacity:0;pointer-events:none}.c-switch__track{position:absolute;inset:0;border:1px solid color-mix(in srgb,var(--md-outline-var) 52%,transparent);border-radius:var(--r-round);background:var(--md-surface-cont-h);transition:background var(--motion-quick) var(--ease-standard),border-color var(--motion-quick) var(--ease-standard)}.c-switch__track:after{content:"";position:absolute;top:50%;left:4px;width:calc(var(--switch-h) - 10px);height:calc(var(--switch-h) - 10px);border-radius:var(--r-round);background:var(--md-on-surface-var);box-shadow:var(--shadow-1);transform:translateY(-50%);transition:transform var(--motion-base) var(--ease-standard),background var(--motion-quick) var(--ease-standard)}.c-switch__input:checked+.c-switch__track{border-color:color-mix(in srgb,var(--md-primary) 74%,var(--md-outline-var));background:color-mix(in srgb,var(--md-primary) 80%,var(--md-surface-cont-h))}.c-switch__input:checked+.c-switch__track:after{transform:translate(calc(var(--switch-w) - var(--switch-h)),-50%);background:var(--md-on-primary)}.c-switch__input:focus-visible+.c-switch__track{outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset)}.c-switch__input:disabled+.c-switch__track{opacity:var(--state-disabled-opacity)}.c-close{--close-size: 32px;--close-icon: 16px;--close-radius: 10px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:var(--close-size);height:var(--close-size);padding:0;border:1px solid color-mix(in srgb,var(--md-outline-var) 40%,transparent);border-radius:var(--close-radius);background:color-mix(in srgb,var(--md-surface-cont) 82%,#000);color:var(--md-on-surface);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background var(--motion-quick) var(--ease-standard),color var(--motion-quick) var(--ease-standard),border-color var(--motion-quick) var(--ease-standard),transform var(--motion-quick) var(--ease-standard)}.c-close svg{width:var(--close-icon);height:var(--close-icon);display:block}.c-close svg path{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}.c-close:hover{background:color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont));border-color:color-mix(in srgb,var(--md-primary) 32%,var(--md-outline-var));color:var(--md-on-surface)}.c-close:focus-visible{outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset)}.c-close:active{transform:scale(.94)}.c-close--sm{--close-size: 26px;--close-icon: 14px;--close-radius: 8px}.btn{padding:10px 20px;border:1px solid color-mix(in srgb,var(--md-outline-var) 46%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface);border-radius:var(--control-radius);cursor:pointer;font-size:var(--fs-label-lg);font-weight:750;min-height:var(--control-min-height);box-sizing:border-box;transition:background .14s ease,border-color .14s ease,color .14s ease,transform .12s ease;letter-spacing:.1px}.btn:hover,.btn:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary));outline:none}.btn:active{background:color-mix(in srgb,var(--md-surface-cont-hh) 88%,var(--md-primary));transform:translateY(1px)}.btn.active{border-color:color-mix(in srgb,var(--md-primary) 48%,var(--md-outline-var));background:var(--md-primary-state);color:var(--md-primary);font-weight:850}.smallBtn{padding:var(--sp-sm) var(--sp-md);border-radius:var(--control-radius);border:1px solid color-mix(in srgb,var(--md-outline-var) 46%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface);cursor:pointer;font-size:var(--fs-label-lg);font-weight:750;min-height:44px;box-sizing:border-box;transition:background .14s ease,border-color .14s ease,color .14s ease,transform .12s ease}.smallBtn:hover,.smallBtn:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary));outline:none}.smallBtn:active{background:color-mix(in srgb,var(--md-surface-cont-hh) 88%,var(--md-primary));transform:translateY(1px)}.btn--filled,.smallBtn.btn--filled{background:var(--md-action-filled);border-color:color-mix(in srgb,var(--md-action-filled) 78%,var(--md-outline-var));color:var(--md-on-action-filled)}.btn--danger{color:var(--md-error)}.btn--danger:active{background:var(--md-error-cont);color:var(--md-on-error-cont)}.val{min-width:84px;text-align:center;font-weight:700;font-family:var(--font-mono);color:var(--md-on-surface)}.input-text{flex:1;min-width:200px;padding:var(--sp-md) var(--sp-lg);border-radius:var(--r-md);border:1px solid var(--md-outline-var);background:var(--md-surface-cont);color:var(--md-on-surface);font-size:var(--fs-body-md);font-family:var(--font-mono);box-sizing:border-box;outline:none;transition:border-color .15s}.input-text:focus{border-color:var(--md-primary)}.chip{--chip-font-size: var(--fs-label-sm);--chip-font-weight: 750;--chip-min-height: 22px;--chip-padding-block: 2px;--chip-padding-inline: 10px;display:inline-flex;align-items:center;gap:6px;min-height:var(--chip-min-height);padding:var(--chip-padding-block) var(--chip-padding-inline);border:1px solid color-mix(in srgb,var(--md-outline-var) 42%,transparent);border-radius:var(--r-round);background:color-mix(in srgb,var(--md-surface-cont-h) 80%,var(--md-surface-cont));color:var(--md-on-surface-var);font-size:var(--chip-font-size);font-weight:var(--chip-font-weight);line-height:1;white-space:nowrap}.chip--compact{--chip-font-size: 10px;--chip-font-weight: 820;--chip-min-height: 18px;--chip-padding-block: 0px;--chip-padding-inline: 7px}.chip--accent{border-color:color-mix(in srgb,var(--md-primary) 38%,transparent);background:color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont-h));color:color-mix(in srgb,var(--md-primary) 70%,var(--md-on-surface))}.chip--danger{border-color:color-mix(in srgb,var(--md-error) 42%,transparent);background:color-mix(in srgb,var(--md-error) 12%,var(--md-surface-cont-h));color:color-mix(in srgb,var(--md-error) 70%,var(--md-on-surface))}.chip--success{border-color:color-mix(in srgb,var(--md-success) 42%,transparent);background:var(--md-success-cont);color:var(--md-on-success-cont)}.chip--warning{border-color:color-mix(in srgb,var(--md-warning) 42%,transparent);background:var(--md-warning-cont);color:var(--md-on-warning-cont)}.chip--info{border-color:color-mix(in srgb,var(--md-info) 42%,transparent);background:var(--md-info-cont);color:var(--md-on-info-cont)}.app-dialog{position:fixed;top:var(--app-vv-top, 0px);left:0;right:0;height:var(--app-vv-height, 100dvh);z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--sp-lg);pointer-events:none}#appDialog{z-index:calc(var(--z-modal) + 10)}.app-dialog[hidden]{display:none!important}.app-dialog.is-open{pointer-events:auto}.app-dialog__backdrop{position:absolute;inset:0;border:0;padding:0;background:var(--popup-backdrop);opacity:0;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%,var(--dialog-sheet-width));max-height:var(--dialog-sheet-max-height);display:flex;flex-direction:column;padding:var(--sp-lg);border:1px solid var(--popup-border-color);border-radius:var(--popup-radius);background:var(--popup-bg);box-shadow:var(--popup-shadow);opacity:0;transform:translateY(14px) scale(.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) scale(1)}.app-dialog__head{margin-bottom:var(--sp-sm);flex-shrink:0}.app-dialog__title{font-size:var(--fs-title-sm);font-weight:800;color:var(--md-on-surface)}.app-dialog__body{color:var(--md-on-surface-var);font-size:var(--fs-body-md);line-height:1.55;white-space:pre-wrap;word-break:break-word;flex:1 1 auto;min-height:0;overflow-y:auto}.app-dialog__metaList{display:flex;flex-direction:column;gap:6px}.app-dialog__metaLine{color:var(--md-on-surface-var);font-size:var(--fs-body-md);line-height:1.5}.app-dialog__metaSubtle{margin-top:4px;color:color-mix(in srgb,var(--md-on-surface-var) 82%,var(--md-outline));font-size:12px;line-height:1.45}.app-dialog__choices{--dialog-choice-heading-fg: var(--md-on-surface-var);--dialog-choice-heading-size: var(--fs-label-sm);display:grid;gap:6px;margin-top:var(--sp-md);overflow-y:auto;min-height:0;flex:1 1 auto;padding-right:2px}.app-dialog__choices[hidden]{display:none!important}.app-dialog__choiceBtn{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:var(--control-min-height);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:color-mix(in srgb,var(--md-surface-cont-hh) 74%,var(--md-primary-state));color:var(--md-on-surface);font-weight:800}.app-dialog__choices--list{gap:var(--sp-sm);padding:0;border:0;background:transparent}.app-dialog__choices--list .app-dialog__choiceGroup{display:flex;flex-direction:column;gap:inherit;min-width:0}.app-dialog__choiceHeading{padding:var(--sp-xs) var(--sp-sm) 0;color:var(--dialog-choice-heading-fg);font-size:var(--dialog-choice-heading-size);font-weight:800;line-height:1.4}.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:var(--sp-md) var(--sp-lg);border:1px solid var(--popup-item-border);border-radius:var(--popup-item-radius);background:var(--popup-item-bg);box-shadow:none}.app-dialog__choices--list .app-dialog__choiceBtn--action:hover,.app-dialog__choices--list .app-dialog__choiceBtn--action:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary));outline:none}.app-dialog__choices--list .app-dialog__choiceBtn--action:active{transform:translateY(1px)}.app-dialog__choices--list .app-dialog__choiceBtn--action.is-current{border-color:var(--popup-item-active-border);background:var(--popup-item-active-bg);color:var(--md-primary);font-weight:850}.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__choices--value-grid .app-dialog__choiceBtn--value{min-height:42px;padding:0 6px;justify-content:center;border:1px solid var(--popup-item-border);border-radius:var(--popup-item-radius);background:var(--popup-item-bg);text-align:center;white-space:nowrap;font-variant-numeric:tabular-nums}.app-dialog__choices--value-grid .app-dialog__choiceBtn--value:hover,.app-dialog__choices--value-grid .app-dialog__choiceBtn--value:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--popup-item-bg) 90%,var(--md-primary));outline:none}.app-dialog__choices--value-grid .app-dialog__choiceBtn--value.is-current{border-color:var(--popup-item-active-border);background:var(--popup-item-active-bg);color:var(--md-primary);font-weight:850}.app-dialog__inputWrap{margin-top:var(--sp-md)}.app-dialog__inputWrap[hidden]{display:none!important}.app-dialog__input{width:100%;min-width:0}.app-dialog__actions{display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap;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}#appDialogConfirm.btn--filled{background:var(--md-action-filled);border-color:color-mix(in srgb,var(--md-action-filled) 78%,var(--md-outline-var));color:var(--md-on-action-filled);box-shadow:none}#appDialogConfirm.btn--filled:hover,#appDialogConfirm.btn--filled:focus-visible{background:color-mix(in srgb,var(--md-action-filled) 88%,#fff);border-color:color-mix(in srgb,var(--md-action-filled) 84%,var(--md-outline-var));color:var(--md-on-action-filled)}#appDialogConfirm.btn--filled:active{background:color-mix(in srgb,var(--md-action-filled) 86%,#2a1204)}@media(max-width:640px)and (not (min-aspect-ratio:13/10)){.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(.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-dialog--form .app-dialog__body[hidden]{display:none!important}.app-dialog--form .app-dialog__inputWrap{display:grid;gap:var(--sp-xs);margin-top:var(--sp-sm)}.app-dialog--input{align-items:center}html[data-kb-open="1"] .app-dialog--input{align-items:flex-end;padding-bottom:calc(var(--kb-gap) + var(--kb-inset) + env(safe-area-inset-bottom,0px))}.app-dialog__inputError{color:var(--md-error);font-size:var(--fs-label-sm);line-height:1.35;overflow-wrap:anywhere}.app-dialog__inputError[hidden]{display:none!important}.ui-dropdown-menu{position:relative;display:inline-flex;align-items:center;justify-content:flex-end}.ui-dropdown-menu__button{appearance:none;-webkit-appearance:none;min-width:var(--menu-trigger-size);min-height:var(--menu-trigger-size);border:1px solid color-mix(in srgb,var(--md-outline-var) 46%,transparent);border-radius:8px;background:var(--md-surface-cont);color:var(--md-on-surface);display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;font:inherit;font-size:13px;font-weight:800;line-height:1;transition:background .14s ease,border-color .14s ease,color .14s ease,transform .12s ease}.ui-menu-icon{width:var(--menu-icon-size);height:var(--menu-icon-size);flex:0 0 var(--menu-icon-size);display:inline-flex;flex-direction:column;align-items:stretch;justify-content:center;gap:var(--menu-icon-line-gap);color:currentColor;pointer-events:none}.ui-menu-icon__line{display:block;width:100%;height:var(--menu-icon-line-height);border-radius:var(--r-round);background:currentColor}.ui-dropdown-menu__button:hover,.ui-dropdown-menu__button:focus-visible,.ui-dropdown-menu.is-open .ui-dropdown-menu__button{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary));outline:none}.ui-dropdown-menu__button:active{transform:translateY(1px)}.ui-dropdown-menu__panel{min-width:156px;display:grid;gap:6px;padding:var(--sp-sm);border:1px solid var(--popup-border-color);border-radius:var(--popup-radius);background:var(--popup-bg);color:var(--md-on-surface);box-shadow:var(--popup-shadow)}.ui-dropdown-menu__panel[hidden]{display:none!important}.ui-dropdown-menu__item{width:100%;min-height:40px;padding:0 12px;border:1px solid var(--popup-item-border);border-radius:var(--popup-item-radius);background:var(--popup-item-bg);color:var(--md-on-surface);display:flex;align-items:center;justify-content:flex-start;gap:8px;font:inherit;font-size:13px;font-weight:800;text-align:left;cursor:pointer;transition:background .14s ease,color .14s ease}.ui-dropdown-menu__item:hover,.ui-dropdown-menu__item:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--popup-item-bg) 90%,var(--md-primary));color:var(--md-on-surface);outline:none}.ui-popover{position:relative}.ui-popover__panel{min-width:156px;padding:var(--sp-sm) var(--sp-md);border:1px solid var(--popup-border-color);border-radius:var(--popup-radius);background:var(--popup-bg);color:var(--md-on-surface);box-shadow:var(--popup-shadow)}.ui-popover__panel[hidden]{display:none!important}.ui-popover__text{display:block;color:var(--md-on-surface);font-size:var(--fs-body-sm);font-weight:750;line-height:1.4;overflow-wrap:anywhere}.app-toast-host{position:fixed;left:var(--sp-lg);right:var(--sp-lg);bottom:calc(var(--nav-bar-height) + 18px + env(safe-area-inset-bottom,0px));z-index:var(--z-toast);display:flex;flex-direction:column;align-items:center;gap:var(--sp-sm);pointer-events:none}body[data-page=terminal] .app-toast-host{top:auto;bottom:var(--terminal-toast-bottom, calc(var(--nav-bar-height) + 96px + env(safe-area-inset-bottom, 0px)));left:var(--terminal-toast-left, var(--sp-lg));right:auto;width:var(--terminal-toast-width, calc(100vw - (var(--sp-lg) * 2)));z-index:calc(var(--z-toast) - 2)}.app-toast{display:flex;align-items:center;gap:12px;width:min(100%,440px);padding:14px 18px;border:1px solid var(--toast-border);border-radius:var(--toast-radius);background:var(--toast-bg);color:var(--toast-fg);box-shadow:var(--toast-shadow);font-size:15px;font-weight:600;line-height:1.45;opacity:0;transform:translateY(16px);transition:opacity .2s ease,transform .26s cubic-bezier(.2,0,0,1)}.app-toast.is-visible{opacity:1;transform:translateY(0)}.app-toast__icon{flex:none;width:var(--toast-icon-size);height:var(--toast-icon-size);border-radius:50%;display:grid;place-items:center;background:var(--toast-icon-default);color:var(--toast-icon-fg);font:900 14px/1 system-ui,sans-serif}.app-toast__msg{flex:1;min-width:0;white-space:pre-wrap;overflow-wrap:anywhere}.app-toast.is-info .app-toast__icon{background:var(--toast-icon-info)}.app-toast.is-success .app-toast__icon{background:var(--toast-icon-success)}.app-toast.is-warn .app-toast__icon{background:var(--toast-icon-warn)}.app-toast.is-error .app-toast__icon{background:var(--toast-icon-error)}.c-state-surface{--state-surface-text: var(--md-on-surface);--state-surface-feature-color: var(--md-on-surface);--state-surface-title-color: color-mix(in srgb, var(--md-on-surface-var) 82%, var(--md-surface));--state-surface-description-color: color-mix(in srgb, var(--md-on-surface-var) 78%, transparent);--state-surface-font-weight: 700;--state-surface-overlay-bg: color-mix(in srgb, var(--md-surface) 84%, var(--drive-workspace-bg));--state-surface-max-width: 42ch;--state-surface-feature-size-min: 30px;--state-surface-feature-size-max: 50px;--state-surface-title-size-min: 34px;--state-surface-title-size-max: 58px;--state-surface-description-size: 14px;--state-surface-copy-gap: var(--sp-sm);--state-surface-action-gap: var(--sp-lg);--state-surface-action-row-gap: var(--sp-sm);--state-surface-action-min-height: 46px;--state-surface-action-max-width: 280px;--state-surface-action-font-size: 16px;--state-surface-secondary-bg: color-mix(in srgb, var(--md-surface-cont-h) 86%, transparent);--state-surface-status-accent: var(--md-outline-var);--state-surface-fog-neutral: color-mix(in srgb, var(--md-on-surface-var) 7%, transparent);--state-surface-fog-accent: color-mix(in srgb, var(--md-primary) 8%, transparent);--state-surface-fog-blur: 64px;--state-surface-fog-duration: 14s;--state-surface-fog-start-opacity: .62;--state-surface-fog-active-opacity: 1;position:absolute;z-index:var(--drive-layer-overlay);isolation:isolate;inset:0;display:grid;min-width:0;min-height:0;container-type:inline-size;place-items:safe center;padding:clamp(var(--sp-md),4cqi,var(--sp-2xl));overflow:auto;overscroll-behavior:contain;background:var(--state-surface-overlay-bg);color:var(--state-surface-text);pointer-events:auto}.c-state-surface:before,.c-state-surface:after{position:absolute;border-radius:var(--r-round);content:"";filter:blur(var(--state-surface-fog-blur));opacity:var(--state-surface-fog-active-opacity);pointer-events:none;will-change:opacity,transform}.c-state-surface:before{inset:6% 12%;background:var(--state-surface-fog-neutral);animation:state-surface-fog-neutral var(--state-surface-fog-duration) var(--ease-standard) infinite alternate}.c-state-surface:after{right:22%;bottom:18%;width:24%;height:30%;background:var(--state-surface-fog-accent);animation:state-surface-fog-accent var(--state-surface-fog-duration) var(--ease-standard) infinite alternate-reverse}.c-state-surface[hidden]{display:none!important}.c-state-surface--info{--state-surface-status-accent: var(--md-info)}.c-state-surface--warning{--state-surface-status-accent: var(--md-warning)}.c-state-surface--error{--state-surface-status-accent: var(--md-error)}.c-state-surface__body{position:relative;z-index:1;display:grid;width:min(100%,var(--state-surface-max-width));margin-block:auto;place-items:center;gap:var(--state-surface-action-gap);text-align:center}.c-state-surface__copy{display:grid;min-width:0;place-items:center;gap:var(--state-surface-copy-gap)}.c-state-surface__feature,.c-state-surface__title,.c-state-surface__description{max-inline-size:100%;margin:0;overflow-wrap:anywhere;text-wrap:balance;word-break:keep-all}.c-state-surface__feature,.c-state-surface__title{font-weight:var(--state-surface-font-weight)}.c-state-surface__feature{color:var(--state-surface-feature-color);font-size:clamp(var(--state-surface-feature-size-min),5cqi,var(--state-surface-feature-size-max));line-height:1.06;letter-spacing:-.05em}.c-state-surface__title{color:var(--state-surface-title-color);font-size:clamp(var(--state-surface-title-size-min),6.5cqi,var(--state-surface-title-size-max));line-height:1.02;letter-spacing:-.055em}.c-state-surface__description{color:var(--state-surface-description-color);font-size:var(--state-surface-description-size);font-weight:650;line-height:1.4}.c-state-surface__actions{display:grid;width:min(100%,var(--state-surface-action-max-width));grid-template-columns:minmax(0,1fr);gap:var(--state-surface-action-row-gap)}.c-state-surface__actions[data-layout=single]{grid-template-columns:minmax(0,1fr)}.c-state-surface__actions[data-layout=split]{grid-template-columns:repeat(2,minmax(0,1fr))}.c-state-surface__action{width:100%;min-width:0;min-height:var(--state-surface-action-min-height);margin:0;padding-inline:var(--sp-lg);overflow:hidden;font-size:var(--state-surface-action-font-size);text-overflow:ellipsis;white-space:nowrap}.c-state-surface__action--primary,.c-state-surface__action:where([data-tone=primary]){background:var(--md-action-filled);border-color:color-mix(in srgb,var(--md-action-filled) 78%,var(--md-outline-var));color:var(--md-on-action-filled)}.c-state-surface__action--secondary,.c-state-surface__action:where([data-tone=secondary]){background:var(--state-surface-secondary-bg);color:var(--state-surface-text)}.c-state-surface__action--danger,.c-state-surface__action:where([data-tone=danger]){border-color:color-mix(in srgb,var(--md-error) 54%,var(--md-outline-var));background:var(--md-error-cont);color:var(--md-on-error-cont)}.c-state-surface__action:disabled{opacity:var(--state-disabled-opacity);cursor:not-allowed}.c-state-surface[data-pending=true] .c-state-surface__action:disabled{cursor:wait}.c-state-surface--compact{place-items:start center;padding:var(--sp-sm);overflow:visible;background:transparent;pointer-events:none}.c-state-surface--compact:before,.c-state-surface--compact:after{display:none}.c-state-surface--compact .c-state-surface__body{width:min(100%,560px);margin:0;padding:10px 14px;place-items:stretch;gap:var(--sp-sm);border:1px solid color-mix(in srgb,var(--state-surface-status-accent) 54%,var(--md-outline-var));border-left-width:3px;border-radius:var(--r-md);background:color-mix(in srgb,var(--md-surface-cont-h) 94%,transparent);box-shadow:0 8px 24px #00000052;text-align:left;pointer-events:auto}.c-state-surface--compact .c-state-surface__copy{place-items:start;gap:2px}.c-state-surface--compact .c-state-surface__feature{font-size:12px;line-height:1.25;letter-spacing:0}.c-state-surface--compact .c-state-surface__title{font-size:15px;line-height:1.3;letter-spacing:-.02em}.c-state-surface--compact .c-state-surface__description{font-size:12px;font-weight:650;line-height:1.35}.c-state-surface--compact .c-state-surface__feature,.c-state-surface--compact .c-state-surface__title,.c-state-surface--compact .c-state-surface__description{max-inline-size:100%;overflow-wrap:anywhere;text-wrap:pretty}.c-state-surface--compact .c-state-surface__actions{width:100%;max-width:none;pointer-events:auto}@keyframes state-surface-fog-neutral{0%{opacity:var(--state-surface-fog-start-opacity);transform:translate(-4%,3%) scale(.9)}to{opacity:var(--state-surface-fog-active-opacity);transform:translate(4%,-3%) scale(1.08)}}@keyframes state-surface-fog-accent{0%{transform:translate(8%,6%) scale(.88)}to{transform:translate(-8%,-6%) scale(1.12)}}@media(prefers-reduced-motion:reduce){.c-state-surface:before,.c-state-surface:after{animation:none;will-change:auto}}@media(prefers-contrast:more){.c-state-surface{--state-surface-title-color: var(--md-on-surface);--state-surface-fog-start-opacity: .3;--state-surface-fog-active-opacity: .42}}.c-segmented-control{--segmented-control-gap: var(--sp-xs);--segmented-control-padding: 0;--segmented-control-item-min-height: var(--control-min-height);--segmented-control-item-padding: var(--sp-sm) var(--sp-md);--segmented-control-item-flex: 1 0 max-content;--segmented-control-item-font-size: inherit;--segmented-control-item-font-weight: 750;--segmented-control-item-radius: var(--control-radius);--segmented-control-item-border: color-mix(in srgb, var(--md-outline-var) 46%, transparent);--segmented-control-item-bg: var(--md-surface-cont-h);--segmented-control-item-fg: var(--md-on-surface-var);--segmented-control-selected-border: var(--md-outline);--segmented-control-selected-bg: var(--md-surface-cont-hh);--segmented-control-selected-fg: var(--md-on-surface);--segmented-control-hover-border: var(--segmented-control-selected-border);--segmented-control-hover-fg: var(--segmented-control-selected-fg);display:flex;width:100%;min-width:0;max-width:100%;gap:var(--segmented-control-gap);padding:var(--segmented-control-padding);overflow-x:auto;overflow-y:hidden;overscroll-behavior-inline:contain;scrollbar-width:thin;-webkit-overflow-scrolling:touch}.c-segmented-control__item{appearance:none;flex:var(--segmented-control-item-flex);min-width:0;min-height:var(--segmented-control-item-min-height);padding:var(--segmented-control-item-padding);border:1px solid var(--segmented-control-item-border);border-radius:var(--segmented-control-item-radius);background:var(--segmented-control-item-bg);color:var(--segmented-control-item-fg);font:inherit;font-size:var(--segmented-control-item-font-size);font-weight:var(--segmented-control-item-font-weight);line-height:1;text-align:center;white-space:nowrap;cursor:pointer}.c-segmented-control__item:hover{border-color:var(--segmented-control-hover-border);color:var(--segmented-control-hover-fg)}.c-segmented-control__item:focus-visible{outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset)}.c-segmented-control__item[aria-selected=true],.c-segmented-control__item[aria-pressed=true],.c-segmented-control__item.is-active,.c-segmented-control__item.is-selected{border-color:var(--segmented-control-selected-border);background:var(--segmented-control-selected-bg);color:var(--segmented-control-selected-fg)}.c-segmented-control__item:disabled,.c-segmented-control__item[aria-disabled=true]{opacity:var(--state-disabled-opacity);cursor:not-allowed;pointer-events:none}.c-segmented-control--equal{--segmented-control-item-flex: 1 1 0}.c-segmented-control--equal>.c-segmented-control__item{min-inline-size:0}.c-segmented-control__label{min-inline-size:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis} +.card{background:var(--md-surface-cont);border:1px solid var(--md-stroke-soft);border-radius:var(--r-md);padding:var(--sp-lg);margin-bottom:var(--sp-lg)}.value-surface{background:var(--md-surface-cont);border:1px solid var(--md-stroke-soft);padding:var(--sp-sm) var(--sp-md);border-radius:var(--control-radius);font-size:var(--fs-body-sm);color:var(--md-on-surface-var)}.c-switch{--switch-w: 52px;--switch-h: 32px;position:relative;display:inline-flex;flex:0 0 var(--switch-w);width:var(--switch-w);height:var(--switch-h);cursor:pointer;-webkit-tap-highlight-color:transparent}.c-switch--sm{--switch-w: 44px;--switch-h: 28px}.c-switch__input{position:absolute;opacity:0;pointer-events:none}.c-switch__track{position:absolute;inset:0;border:1px solid color-mix(in srgb,var(--md-outline-var) 52%,transparent);border-radius:var(--r-round);background:var(--md-surface-cont-h);transition:background var(--motion-quick) var(--ease-standard),border-color var(--motion-quick) var(--ease-standard)}.c-switch__track:after{content:"";position:absolute;top:50%;left:4px;width:calc(var(--switch-h) - 10px);height:calc(var(--switch-h) - 10px);border-radius:var(--r-round);background:var(--md-on-surface-var);box-shadow:var(--shadow-1);transform:translateY(-50%);transition:transform var(--motion-base) var(--ease-standard),background var(--motion-quick) var(--ease-standard)}.c-switch__input:checked+.c-switch__track{border-color:color-mix(in srgb,var(--md-primary) 74%,var(--md-outline-var));background:color-mix(in srgb,var(--md-primary) 80%,var(--md-surface-cont-h))}.c-switch__input:checked+.c-switch__track:after{transform:translate(calc(var(--switch-w) - var(--switch-h)),-50%);background:var(--md-on-primary)}.c-switch__input:focus-visible+.c-switch__track{outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset)}.c-switch__input:disabled+.c-switch__track{opacity:var(--state-disabled-opacity)}.c-close{--close-size: 32px;--close-icon: 16px;--close-radius: 10px;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:var(--close-size);height:var(--close-size);padding:0;border:1px solid color-mix(in srgb,var(--md-outline-var) 40%,transparent);border-radius:var(--close-radius);background:color-mix(in srgb,var(--md-surface-cont) 82%,#000);color:var(--md-on-surface);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background var(--motion-quick) var(--ease-standard),color var(--motion-quick) var(--ease-standard),border-color var(--motion-quick) var(--ease-standard),transform var(--motion-quick) var(--ease-standard)}.c-close svg{width:var(--close-icon);height:var(--close-icon);display:block}.c-close svg path{fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round}.c-close:hover{background:color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont));border-color:color-mix(in srgb,var(--md-primary) 32%,var(--md-outline-var));color:var(--md-on-surface)}.c-close:focus-visible{outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset)}.c-close:active{transform:scale(.94)}.c-close--sm{--close-size: 26px;--close-icon: 14px;--close-radius: 8px}.btn{padding:10px 20px;border:1px solid color-mix(in srgb,var(--md-outline-var) 46%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface);border-radius:var(--control-radius);cursor:pointer;font-size:var(--fs-label-lg);font-weight:750;min-height:var(--control-min-height);box-sizing:border-box;transition:background .14s ease,border-color .14s ease,color .14s ease,transform .12s ease;letter-spacing:.1px}.btn:hover,.btn:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary));outline:none}.btn:active{background:color-mix(in srgb,var(--md-surface-cont-hh) 88%,var(--md-primary));transform:translateY(1px)}.btn.active{border-color:color-mix(in srgb,var(--md-primary) 48%,var(--md-outline-var));background:var(--md-primary-state);color:var(--md-primary);font-weight:850}.smallBtn{padding:var(--sp-sm) var(--sp-md);border-radius:var(--control-radius);border:1px solid color-mix(in srgb,var(--md-outline-var) 46%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface);cursor:pointer;font-size:var(--fs-label-lg);font-weight:750;min-height:44px;box-sizing:border-box;transition:background .14s ease,border-color .14s ease,color .14s ease,transform .12s ease}.smallBtn:hover,.smallBtn:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary));outline:none}.smallBtn:active{background:color-mix(in srgb,var(--md-surface-cont-hh) 88%,var(--md-primary));transform:translateY(1px)}.btn--filled,.smallBtn.btn--filled{background:var(--md-action-filled);border-color:color-mix(in srgb,var(--md-action-filled) 78%,var(--md-outline-var));color:var(--md-on-action-filled)}.btn--danger{color:var(--md-error)}.btn--danger:active{background:var(--md-error-cont);color:var(--md-on-error-cont)}.val{min-width:84px;text-align:center;font-weight:700;font-family:var(--font-mono);color:var(--md-on-surface)}.input-text{flex:1;min-width:200px;padding:var(--sp-md) var(--sp-lg);border-radius:var(--r-md);border:1px solid var(--md-outline-var);background:var(--md-surface-cont);color:var(--md-on-surface);font-size:var(--fs-body-md);font-family:var(--font-mono);box-sizing:border-box;outline:none;transition:border-color .15s}.input-text:focus{border-color:var(--md-primary)}.chip{--chip-font-size: var(--fs-label-sm);--chip-font-weight: 750;--chip-min-height: 22px;--chip-padding-block: 2px;--chip-padding-inline: 10px;display:inline-flex;align-items:center;gap:6px;min-height:var(--chip-min-height);padding:var(--chip-padding-block) var(--chip-padding-inline);border:1px solid color-mix(in srgb,var(--md-outline-var) 42%,transparent);border-radius:var(--r-round);background:color-mix(in srgb,var(--md-surface-cont-h) 80%,var(--md-surface-cont));color:var(--md-on-surface-var);font-size:var(--chip-font-size);font-weight:var(--chip-font-weight);line-height:1;white-space:nowrap}.chip--compact{--chip-font-size: 10px;--chip-font-weight: 820;--chip-min-height: 18px;--chip-padding-block: 0px;--chip-padding-inline: 7px}.chip--accent{border-color:color-mix(in srgb,var(--md-primary) 38%,transparent);background:color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont-h));color:color-mix(in srgb,var(--md-primary) 70%,var(--md-on-surface))}.chip--danger{border-color:color-mix(in srgb,var(--md-error) 42%,transparent);background:color-mix(in srgb,var(--md-error) 12%,var(--md-surface-cont-h));color:color-mix(in srgb,var(--md-error) 70%,var(--md-on-surface))}.chip--success{border-color:color-mix(in srgb,var(--md-success) 42%,transparent);background:var(--md-success-cont);color:var(--md-on-success-cont)}.chip--warning{border-color:color-mix(in srgb,var(--md-warning) 42%,transparent);background:var(--md-warning-cont);color:var(--md-on-warning-cont)}.chip--info{border-color:color-mix(in srgb,var(--md-info) 42%,transparent);background:var(--md-info-cont);color:var(--md-on-info-cont)}.app-dialog{position:fixed;top:var(--app-vv-top, 0px);left:0;right:0;height:var(--app-vv-height, 100dvh);z-index:var(--z-modal);display:flex;align-items:center;justify-content:center;padding:var(--sp-lg);pointer-events:none}#appDialog{z-index:calc(var(--z-modal) + 10)}.app-dialog[hidden]{display:none!important}.app-dialog.is-open{pointer-events:auto}.app-dialog__backdrop{position:absolute;inset:0;border:0;padding:0;background:var(--popup-backdrop);opacity:0;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%,var(--dialog-sheet-width));max-height:var(--dialog-sheet-max-height);display:flex;flex-direction:column;padding:var(--sp-lg);border:1px solid var(--popup-border-color);border-radius:var(--popup-radius);background:var(--popup-bg);box-shadow:var(--popup-shadow);opacity:0;transform:translateY(14px) scale(.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) scale(1)}.app-dialog__head{margin-bottom:var(--sp-sm);flex-shrink:0}.app-dialog__title{font-size:var(--fs-title-sm);font-weight:800;color:var(--md-on-surface)}.app-dialog__body{color:var(--md-on-surface-var);font-size:var(--fs-body-md);line-height:1.55;white-space:pre-wrap;word-break:break-word;flex:1 1 auto;min-height:0;overflow-y:auto}.app-dialog__metaList{display:flex;flex-direction:column;gap:var(--sp-sm)}.app-dialog__metaLine{color:var(--md-on-surface-var);font-size:var(--fs-body-md);line-height:1.5}.app-dialog__metaSubtle{margin-top:4px;color:color-mix(in srgb,var(--md-on-surface-var) 82%,var(--md-outline));font-size:12px;line-height:1.45}.app-dialog__metaGroup{display:flex;flex-direction:column;gap:var(--sp-sm);padding-top:var(--sp-xs)}.app-dialog__metaLabel{color:var(--md-on-surface-var);font-size:var(--fs-label-sm);font-weight:800}.app-dialog__metaCodeList{display:flex;flex-direction:column;gap:var(--sp-xs)}.app-dialog__metaCode{overflow-wrap:anywhere;color:var(--md-on-surface);font:700 var(--fs-label-sm)/1.45 var(--font-mono)}.app-dialog__metaSummary{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-sm) var(--sp-lg);color:var(--md-on-surface-var);font-size:var(--fs-label-md)}.app-dialog__metaSummary strong{margin-left:auto;color:var(--md-on-surface);font-weight:850}.app-dialog__uploadBrief{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:var(--sp-md);min-width:0;padding:var(--sp-xs) 0}.app-dialog__uploadBriefCopy,.app-dialog__uploadBriefAmount{display:grid;min-width:0;gap:2px}.app-dialog__uploadBriefTitle{color:var(--md-on-surface);font-size:var(--fs-body-md);font-weight:850;line-height:1.35}.app-dialog__uploadBriefKinds,.app-dialog__uploadBriefAmount span{color:var(--md-on-surface-var);font-size:var(--fs-label-sm);font-weight:750;line-height:1.4}.app-dialog__uploadBriefKinds{overflow-wrap:anywhere}.app-dialog__uploadBriefAmount{justify-items:end;text-align:right;white-space:nowrap}.app-dialog__uploadBriefAmount strong{color:var(--md-on-surface);font-size:var(--fs-title-xs);font-weight:850;line-height:1.2}.app-dialog__metaResultList{display:flex;flex-direction:column;gap:var(--sp-xs)}.app-dialog__metaResult{display:flex;align-items:center;justify-content:space-between;gap:var(--sp-md);min-width:0;padding:var(--sp-xs) 0}.app-dialog__metaResult .app-dialog__metaCode{min-width:0}.app-dialog__metaOverflow{padding:var(--sp-xs) 0;color:var(--md-error);font-size:var(--fs-label-sm);font-weight:800}.app-dialog__metaState{flex:0 0 auto;color:var(--md-on-surface-var);font-size:var(--fs-label-sm);font-weight:850}.app-dialog__metaState[data-tone=success]{color:var(--md-success)}.app-dialog__metaState[data-tone=error]{color:var(--md-error)}.app-dialog--progress .app-dialog__body{flex:0 0 auto;overflow:visible}.app-dialog__choices{--dialog-choice-heading-fg: var(--md-on-surface-var);--dialog-choice-heading-size: var(--fs-label-sm);display:grid;gap:6px;margin-top:var(--sp-md);overflow-y:auto;min-height:0;flex:1 1 auto;padding-right:2px}.app-dialog__choices[hidden]{display:none!important}.app-dialog__choiceBtn{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:var(--control-min-height);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:color-mix(in srgb,var(--md-surface-cont-hh) 74%,var(--md-primary-state));color:var(--md-on-surface);font-weight:800}.app-dialog__choices--list{gap:var(--sp-sm);padding:0;border:0;background:transparent}.app-dialog__choices--list .app-dialog__choiceGroup{display:flex;flex-direction:column;gap:inherit;min-width:0}.app-dialog__choiceHeading{padding:var(--sp-xs) var(--sp-sm) 0;color:var(--dialog-choice-heading-fg);font-size:var(--dialog-choice-heading-size);font-weight:800;line-height:1.4}.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:var(--sp-md) var(--sp-lg);border:1px solid var(--popup-item-border);border-radius:var(--popup-item-radius);background:var(--popup-item-bg);box-shadow:none}.app-dialog__choices--list .app-dialog__choiceBtn--action:hover,.app-dialog__choices--list .app-dialog__choiceBtn--action:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary));outline:none}.app-dialog__choices--list .app-dialog__choiceBtn--action:active{transform:translateY(1px)}.app-dialog__choices--list .app-dialog__choiceBtn--action.is-current{border-color:var(--popup-item-active-border);background:var(--popup-item-active-bg);color:var(--md-primary);font-weight:850}.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__choices--value-grid .app-dialog__choiceBtn--value{min-height:42px;padding:0 6px;justify-content:center;border:1px solid var(--popup-item-border);border-radius:var(--popup-item-radius);background:var(--popup-item-bg);text-align:center;white-space:nowrap;font-variant-numeric:tabular-nums}.app-dialog__choices--value-grid .app-dialog__choiceBtn--value:hover,.app-dialog__choices--value-grid .app-dialog__choiceBtn--value:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--popup-item-bg) 90%,var(--md-primary));outline:none}.app-dialog__choices--value-grid .app-dialog__choiceBtn--value.is-current{border-color:var(--popup-item-active-border);background:var(--popup-item-active-bg);color:var(--md-primary);font-weight:850}.app-dialog__inputWrap{margin-top:var(--sp-md)}.app-dialog__inputWrap[hidden]{display:none!important}.app-dialog__input{width:100%;min-width:0}.app-dialog__actions{display:flex;justify-content:flex-end;gap:8px;flex-wrap:wrap;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}#appDialogConfirm.btn--filled{background:var(--md-action-filled);border-color:color-mix(in srgb,var(--md-action-filled) 78%,var(--md-outline-var));color:var(--md-on-action-filled);box-shadow:none}#appDialogConfirm.btn--filled:hover,#appDialogConfirm.btn--filled:focus-visible{background:color-mix(in srgb,var(--md-action-filled) 88%,#fff);border-color:color-mix(in srgb,var(--md-action-filled) 84%,var(--md-outline-var));color:var(--md-on-action-filled)}#appDialogConfirm.btn--filled:active{background:color-mix(in srgb,var(--md-action-filled) 86%,#2a1204)}@media(max-width:640px)and (not (min-aspect-ratio:13/10)){.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(.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-dialog--form .app-dialog__body[hidden]{display:none!important}.app-dialog--form .app-dialog__inputWrap{display:grid;gap:var(--sp-xs);margin-top:var(--sp-sm)}.app-dialog--input{align-items:center}html[data-kb-open="1"] .app-dialog--input{align-items:flex-end;padding-bottom:calc(var(--kb-gap) + var(--kb-inset) + env(safe-area-inset-bottom,0px))}.app-dialog__inputError{color:var(--md-error);font-size:var(--fs-label-sm);line-height:1.35;overflow-wrap:anywhere}.app-dialog__inputError[hidden]{display:none!important}.app-progress{display:grid;gap:var(--sp-md)}.app-progress__header{display:flex;align-items:baseline;justify-content:space-between;gap:var(--sp-md);min-width:0}.app-progress__message{min-width:0;color:var(--md-on-surface);font-size:var(--fs-body-md);font-weight:800;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.app-progress__value{flex:0 0 auto;color:var(--md-primary);font-size:var(--fs-title-xs);font-weight:900;font-variant-numeric:tabular-nums}.app-progress__track{display:block;width:100%;min-width:0;height:10px;overflow:hidden;appearance:none;-webkit-appearance:none;box-sizing:border-box;border:0;border-radius:var(--r-round);background:color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont-h));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--md-primary) 18%,transparent)}.app-progress__track::-webkit-progress-bar{border-radius:inherit;background:color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont-h))}.app-progress__track::-webkit-progress-value{border-radius:inherit;background:linear-gradient(90deg,color-mix(in srgb,var(--md-primary) 76%,var(--md-action-filled)),var(--md-primary));box-shadow:0 0 8px color-mix(in srgb,var(--md-primary) 62%,transparent);transition:width var(--motion-medium) var(--ease-linear)}.app-progress__track::-moz-progress-bar{border-radius:inherit;background:linear-gradient(90deg,color-mix(in srgb,var(--md-primary) 76%,var(--md-action-filled)),var(--md-primary));box-shadow:0 0 8px color-mix(in srgb,var(--md-primary) 62%,transparent);transition:width var(--motion-medium) var(--ease-linear)}.app-progress__track:indeterminate{background:linear-gradient(90deg,transparent 0 22%,var(--md-primary) 40% 60%,transparent 78% 100%) 0 0 / 220% 100% no-repeat,color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont-h));animation:app-progress-indeterminate 1.05s var(--ease-linear) infinite}.app-progress__summary{color:var(--md-on-surface-var);font-size:var(--fs-label-md);font-weight:750;font-variant-numeric:tabular-nums}@keyframes app-progress-indeterminate{0%{background-position:100% 0,0 0}to{background-position:-100% 0,0 0}}@media(prefers-reduced-motion:reduce){.app-progress__track::-webkit-progress-value,.app-progress__track::-moz-progress-bar{transition:none}.app-progress__track:indeterminate{animation:none;background-position:50% 0,0 0}}.ui-dropdown-menu{position:relative;display:inline-flex;align-items:center;justify-content:flex-end}.ui-dropdown-menu__button{appearance:none;-webkit-appearance:none;min-width:var(--menu-trigger-size);min-height:var(--menu-trigger-size);border:1px solid color-mix(in srgb,var(--md-outline-var) 46%,transparent);border-radius:8px;background:var(--md-surface-cont);color:var(--md-on-surface);display:inline-flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;font:inherit;font-size:13px;font-weight:800;line-height:1;transition:background .14s ease,border-color .14s ease,color .14s ease,transform .12s ease}.ui-menu-icon{width:var(--menu-icon-size);height:var(--menu-icon-size);flex:0 0 var(--menu-icon-size);display:inline-flex;flex-direction:column;align-items:stretch;justify-content:center;gap:var(--menu-icon-line-gap);color:currentColor;pointer-events:none}.ui-menu-icon__line{display:block;width:100%;height:var(--menu-icon-line-height);border-radius:var(--r-round);background:currentColor}.ui-dropdown-menu__button:hover,.ui-dropdown-menu__button:focus-visible,.ui-dropdown-menu.is-open .ui-dropdown-menu__button{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary));outline:none}.ui-dropdown-menu__button:active{transform:translateY(1px)}.ui-dropdown-menu__panel{min-width:156px;display:grid;gap:6px;padding:var(--sp-sm);border:1px solid var(--popup-border-color);border-radius:var(--popup-radius);background:var(--popup-bg);color:var(--md-on-surface);box-shadow:var(--popup-shadow)}.ui-dropdown-menu__panel[hidden]{display:none!important}.ui-dropdown-menu__item{width:100%;min-height:40px;padding:0 12px;border:1px solid var(--popup-item-border);border-radius:var(--popup-item-radius);background:var(--popup-item-bg);color:var(--md-on-surface);display:flex;align-items:center;justify-content:flex-start;gap:8px;font:inherit;font-size:13px;font-weight:800;text-align:left;cursor:pointer;transition:background .14s ease,color .14s ease}.ui-dropdown-menu__item:hover,.ui-dropdown-menu__item:focus-visible{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--popup-item-bg) 90%,var(--md-primary));color:var(--md-on-surface);outline:none}.ui-popover{position:relative}.ui-popover__panel{min-width:156px;padding:var(--sp-sm) var(--sp-md);border:1px solid var(--popup-border-color);border-radius:var(--popup-radius);background:var(--popup-bg);color:var(--md-on-surface);box-shadow:var(--popup-shadow)}.ui-popover__panel[hidden]{display:none!important}.ui-popover__text{display:block;color:var(--md-on-surface);font-size:var(--fs-body-sm);font-weight:750;line-height:1.4;overflow-wrap:anywhere}.app-toast-host{position:fixed;left:var(--sp-lg);right:var(--sp-lg);bottom:calc(var(--nav-bar-height) + 18px + env(safe-area-inset-bottom,0px));z-index:var(--z-toast);display:flex;flex-direction:column;align-items:center;gap:var(--sp-sm);pointer-events:none}body[data-page=terminal] .app-toast-host{top:auto;bottom:var(--terminal-toast-bottom, calc(var(--nav-bar-height) + 96px + env(safe-area-inset-bottom, 0px)));left:var(--terminal-toast-left, var(--sp-lg));right:auto;width:var(--terminal-toast-width, calc(100vw - (var(--sp-lg) * 2)));z-index:calc(var(--z-toast) - 2)}.app-toast{display:flex;align-items:center;gap:12px;width:min(100%,440px);padding:14px 18px;border:1px solid var(--toast-border);border-radius:var(--toast-radius);background:var(--toast-bg);color:var(--toast-fg);box-shadow:var(--toast-shadow);font-size:15px;font-weight:600;line-height:1.45;opacity:0;transform:translateY(16px);transition:opacity .2s ease,transform .26s cubic-bezier(.2,0,0,1)}.app-toast.is-visible{opacity:1;transform:translateY(0)}.app-toast__icon{flex:none;width:var(--toast-icon-size);height:var(--toast-icon-size);border-radius:50%;display:grid;place-items:center;background:var(--toast-icon-default);color:var(--toast-icon-fg);font:900 14px/1 system-ui,sans-serif}.app-toast__msg{flex:1;min-width:0;white-space:pre-wrap;overflow-wrap:anywhere}.app-toast.is-info .app-toast__icon{background:var(--toast-icon-info)}.app-toast.is-success .app-toast__icon{background:var(--toast-icon-success)}.app-toast.is-warn .app-toast__icon{background:var(--toast-icon-warn)}.app-toast.is-error .app-toast__icon{background:var(--toast-icon-error)}.c-state-surface{--state-surface-text: var(--md-on-surface);--state-surface-feature-color: var(--md-on-surface);--state-surface-title-color: color-mix(in srgb, var(--md-on-surface-var) 82%, var(--md-surface));--state-surface-description-color: color-mix(in srgb, var(--md-on-surface-var) 78%, transparent);--state-surface-font-weight: 700;--state-surface-overlay-bg: color-mix(in srgb, var(--md-surface) 84%, var(--drive-workspace-bg));--state-surface-max-width: 42ch;--state-surface-feature-size-min: 30px;--state-surface-feature-size-max: 50px;--state-surface-title-size-min: 34px;--state-surface-title-size-max: 58px;--state-surface-description-size: 14px;--state-surface-copy-gap: var(--sp-sm);--state-surface-action-gap: var(--sp-lg);--state-surface-action-row-gap: var(--sp-sm);--state-surface-action-min-height: 46px;--state-surface-action-max-width: 280px;--state-surface-action-font-size: 16px;--state-surface-secondary-bg: color-mix(in srgb, var(--md-surface-cont-h) 86%, transparent);--state-surface-status-accent: var(--md-outline-var);--state-surface-fog-neutral: color-mix(in srgb, var(--md-on-surface-var) 7%, transparent);--state-surface-fog-accent: color-mix(in srgb, var(--md-primary) 8%, transparent);--state-surface-fog-blur: 64px;--state-surface-fog-duration: 14s;--state-surface-fog-start-opacity: .62;--state-surface-fog-active-opacity: 1;position:absolute;z-index:var(--drive-layer-overlay);isolation:isolate;inset:0;display:grid;min-width:0;min-height:0;container-type:inline-size;place-items:safe center;padding:clamp(var(--sp-md),4cqi,var(--sp-2xl));overflow:auto;overscroll-behavior:contain;background:var(--state-surface-overlay-bg);color:var(--state-surface-text);pointer-events:auto}.c-state-surface:before,.c-state-surface:after{position:absolute;border-radius:var(--r-round);content:"";filter:blur(var(--state-surface-fog-blur));opacity:var(--state-surface-fog-active-opacity);pointer-events:none;will-change:opacity,transform}.c-state-surface:before{inset:6% 12%;background:var(--state-surface-fog-neutral);animation:state-surface-fog-neutral var(--state-surface-fog-duration) var(--ease-standard) infinite alternate}.c-state-surface:after{right:22%;bottom:18%;width:24%;height:30%;background:var(--state-surface-fog-accent);animation:state-surface-fog-accent var(--state-surface-fog-duration) var(--ease-standard) infinite alternate-reverse}.c-state-surface[hidden]{display:none!important}.c-state-surface--info{--state-surface-status-accent: var(--md-info)}.c-state-surface--warning{--state-surface-status-accent: var(--md-warning)}.c-state-surface--error{--state-surface-status-accent: var(--md-error)}.c-state-surface__body{position:relative;z-index:1;display:grid;width:min(100%,var(--state-surface-max-width));margin-block:auto;place-items:center;gap:var(--state-surface-action-gap);text-align:center}.c-state-surface__copy{display:grid;min-width:0;place-items:center;gap:var(--state-surface-copy-gap)}.c-state-surface__feature,.c-state-surface__title,.c-state-surface__description{max-inline-size:100%;margin:0;overflow-wrap:anywhere;text-wrap:balance;word-break:keep-all}.c-state-surface__feature,.c-state-surface__title{font-weight:var(--state-surface-font-weight)}.c-state-surface__feature{color:var(--state-surface-feature-color);font-size:clamp(var(--state-surface-feature-size-min),5cqi,var(--state-surface-feature-size-max));line-height:1.06;letter-spacing:-.05em}.c-state-surface__title{color:var(--state-surface-title-color);font-size:clamp(var(--state-surface-title-size-min),6.5cqi,var(--state-surface-title-size-max));line-height:1.02;letter-spacing:-.055em}.c-state-surface__description{color:var(--state-surface-description-color);font-size:var(--state-surface-description-size);font-weight:650;line-height:1.4}.c-state-surface__actions{display:grid;width:min(100%,var(--state-surface-action-max-width));grid-template-columns:minmax(0,1fr);gap:var(--state-surface-action-row-gap)}.c-state-surface__actions[data-layout=single]{grid-template-columns:minmax(0,1fr)}.c-state-surface__actions[data-layout=split]{grid-template-columns:repeat(2,minmax(0,1fr))}.c-state-surface__action{width:100%;min-width:0;min-height:var(--state-surface-action-min-height);margin:0;padding-inline:var(--sp-lg);overflow:hidden;font-size:var(--state-surface-action-font-size);text-overflow:ellipsis;white-space:nowrap}.c-state-surface__action--primary,.c-state-surface__action:where([data-tone=primary]){background:var(--md-action-filled);border-color:color-mix(in srgb,var(--md-action-filled) 78%,var(--md-outline-var));color:var(--md-on-action-filled)}.c-state-surface__action--secondary,.c-state-surface__action:where([data-tone=secondary]){background:var(--state-surface-secondary-bg);color:var(--state-surface-text)}.c-state-surface__action--danger,.c-state-surface__action:where([data-tone=danger]){border-color:color-mix(in srgb,var(--md-error) 54%,var(--md-outline-var));background:var(--md-error-cont);color:var(--md-on-error-cont)}.c-state-surface__action:disabled{opacity:var(--state-disabled-opacity);cursor:not-allowed}.c-state-surface[data-pending=true] .c-state-surface__action:disabled{cursor:wait}.c-state-surface--compact{place-items:start center;padding:var(--sp-sm);overflow:visible;background:transparent;pointer-events:none}.c-state-surface--compact:before,.c-state-surface--compact:after{display:none}.c-state-surface--compact .c-state-surface__body{width:min(100%,560px);margin:0;padding:10px 14px;place-items:stretch;gap:var(--sp-sm);border:1px solid color-mix(in srgb,var(--state-surface-status-accent) 54%,var(--md-outline-var));border-left-width:3px;border-radius:var(--r-md);background:color-mix(in srgb,var(--md-surface-cont-h) 94%,transparent);box-shadow:0 8px 24px #00000052;text-align:left;pointer-events:auto}.c-state-surface--compact .c-state-surface__copy{place-items:start;gap:2px}.c-state-surface--compact .c-state-surface__feature{font-size:12px;line-height:1.25;letter-spacing:0}.c-state-surface--compact .c-state-surface__title{font-size:15px;line-height:1.3;letter-spacing:-.02em}.c-state-surface--compact .c-state-surface__description{font-size:12px;font-weight:650;line-height:1.35}.c-state-surface--compact .c-state-surface__feature,.c-state-surface--compact .c-state-surface__title,.c-state-surface--compact .c-state-surface__description{max-inline-size:100%;overflow-wrap:anywhere;text-wrap:pretty}.c-state-surface--compact .c-state-surface__actions{width:100%;max-width:none;pointer-events:auto}@keyframes state-surface-fog-neutral{0%{opacity:var(--state-surface-fog-start-opacity);transform:translate(-4%,3%) scale(.9)}to{opacity:var(--state-surface-fog-active-opacity);transform:translate(4%,-3%) scale(1.08)}}@keyframes state-surface-fog-accent{0%{transform:translate(8%,6%) scale(.88)}to{transform:translate(-8%,-6%) scale(1.12)}}@media(prefers-reduced-motion:reduce){.c-state-surface:before,.c-state-surface:after{animation:none;will-change:auto}}@media(prefers-contrast:more){.c-state-surface{--state-surface-title-color: var(--md-on-surface);--state-surface-fog-start-opacity: .3;--state-surface-fog-active-opacity: .42}}.c-segmented-control{--segmented-control-gap: var(--sp-xs);--segmented-control-padding: 0;--segmented-control-item-min-height: var(--control-min-height);--segmented-control-item-padding: var(--sp-sm) var(--sp-md);--segmented-control-item-flex: 1 0 max-content;--segmented-control-item-font-size: inherit;--segmented-control-item-font-weight: 750;--segmented-control-item-radius: var(--control-radius);--segmented-control-item-border: color-mix(in srgb, var(--md-outline-var) 46%, transparent);--segmented-control-item-bg: var(--md-surface-cont-h);--segmented-control-item-fg: var(--md-on-surface-var);--segmented-control-selected-border: var(--md-outline);--segmented-control-selected-bg: var(--md-surface-cont-hh);--segmented-control-selected-fg: var(--md-on-surface);--segmented-control-hover-border: var(--segmented-control-selected-border);--segmented-control-hover-fg: var(--segmented-control-selected-fg);display:flex;width:100%;min-width:0;max-width:100%;gap:var(--segmented-control-gap);padding:var(--segmented-control-padding);overflow-x:auto;overflow-y:hidden;overscroll-behavior-inline:contain;scrollbar-width:thin;-webkit-overflow-scrolling:touch}.c-segmented-control__item{appearance:none;flex:var(--segmented-control-item-flex);min-width:0;min-height:var(--segmented-control-item-min-height);padding:var(--segmented-control-item-padding);border:1px solid var(--segmented-control-item-border);border-radius:var(--segmented-control-item-radius);background:var(--segmented-control-item-bg);color:var(--segmented-control-item-fg);font:inherit;font-size:var(--segmented-control-item-font-size);font-weight:var(--segmented-control-item-font-weight);line-height:1;text-align:center;white-space:nowrap;cursor:pointer}.c-segmented-control__item:hover{border-color:var(--segmented-control-hover-border);color:var(--segmented-control-hover-fg)}.c-segmented-control__item:focus-visible{outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset)}.c-segmented-control__item[aria-selected=true],.c-segmented-control__item[aria-pressed=true],.c-segmented-control__item.is-active,.c-segmented-control__item.is-selected{border-color:var(--segmented-control-selected-border);background:var(--segmented-control-selected-bg);color:var(--segmented-control-selected-fg)}.c-segmented-control__item:disabled,.c-segmented-control__item[aria-disabled=true]{opacity:var(--state-disabled-opacity);cursor:not-allowed;pointer-events:none}.c-segmented-control--equal{--segmented-control-item-flex: 1 1 0}.c-segmented-control--equal>.c-segmented-control__item{min-inline-size:0}.c-segmented-control__label{min-inline-size:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis} diff --git a/openpilot/selfdrive/carrot/web/css/generated/logs.css b/openpilot/selfdrive/carrot/web/css/generated/logs.css index cf3d0e3a89..0937a4b023 100644 --- a/openpilot/selfdrive/carrot/web/css/generated/logs.css +++ b/openpilot/selfdrive/carrot/web/css/generated/logs.css @@ -1,2 +1,2 @@ /* @generated by web/build.mjs; edit web/src/entries/logs.css instead. */ -.page--logs{height:calc(var(--app-vv-height, 100dvh) - var(--app-nav-bottom-gap, var(--nav-bar-height)));min-height:0;background:var(--md-surface);overflow:hidden}@media(min-aspect-ratio:13/10),(horizontal-viewport-segments:2),(vertical-viewport-segments:2),(min-width:640px)and (min-height:650px){.page--logs{height:var(--page-content-height, calc(var(--app-vv-height, 100dvh) - 24px))}}.logs-shell{--logs-content-inline: var(--logs-shell-inline, clamp(12px, 3.5vw, 24px));--logs-route-media-column: minmax(0, 1fr);--logs-scrollbar-gutter: 0px;--logs-tab-track-radius: var(--r-sm);--logs-tab-inset: var(--sp-xs);--logs-tab-min-height: 36px;--logs-tab-font-size: var(--fs-label-sm);--logs-tab-font-weight: 700;height:100%;min-height:0;display:flex;flex-direction:column;max-width:1480px;margin:0 auto;overflow:hidden}.logs-tabbar{position:sticky;top:0;z-index:12;display:flex;align-items:center;justify-content:center;gap:var(--sp-md);padding:8px calc(var(--logs-content-inline) + var(--logs-scrollbar-gutter)) 6px var(--logs-content-inline);background:color-mix(in srgb,var(--md-surface) 96%,transparent);border-bottom:0;backdrop-filter:blur(18px)}.logs-tabs{--segmented-control-gap: var(--logs-tab-inset);--segmented-control-padding: var(--logs-tab-inset);--segmented-control-item-min-height: var(--logs-tab-min-height);--segmented-control-item-padding: 0 var(--sp-sm);--segmented-control-item-font-size: var(--logs-tab-font-size);--segmented-control-item-font-weight: var(--logs-tab-font-weight);--segmented-control-item-radius: calc(var(--logs-tab-track-radius) - var(--logs-tab-inset));--segmented-control-item-border: transparent;--segmented-control-item-bg: transparent;--segmented-control-hover-border: transparent;--segmented-control-hover-fg: var(--md-on-surface);--segmented-control-selected-border: transparent;--segmented-control-selected-bg: var(--md-primary);--segmented-control-selected-fg: var(--md-on-primary);width:auto;flex:1 1 auto;border-radius:var(--logs-tab-track-radius);background:var(--md-surface-cont);border:1px solid color-mix(in srgb,var(--md-outline-var) 36%,transparent)}.logs-menu{flex:0 0 auto;align-self:center}.logs-menu__button{width:var(--menu-trigger-size);height:var(--menu-trigger-size);min-height:var(--menu-trigger-size);padding:0}.logs-menu__button .ui-menu-icon{width:var(--menu-icon-size);height:var(--menu-icon-size)}.logs-panel{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}.logs-panel[hidden]{display:none}.logs-subheader{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:12px clamp(14px,3.5vw,26px) 8px;background:color-mix(in srgb,var(--md-surface-cont) 55%,transparent);border-bottom:1px solid color-mix(in srgb,var(--md-outline-var) 45%,transparent)}.logs-subheader__title{font-size:15px;line-height:1.25;font-weight:800;color:var(--md-on-surface)}.logs-subheader__desc{margin-top:4px;font-size:13px;line-height:1.35;color:var(--md-on-surface-var);opacity:.82}.dashcam-expand-btn,.dashcam-menu-btn{appearance:none;border:0;padding:0;background:transparent;color:var(--md-on-surface-var);display:inline-flex;align-items:center;justify-content:center;line-height:0;cursor:pointer;box-sizing:border-box}.dashcam-expand-btn:hover,.dashcam-menu-btn:hover{background:color-mix(in srgb,var(--md-primary) 10%,transparent);color:var(--md-on-surface)}.dashcam-expand-btn svg,.dashcam-menu-btn svg{width:22px;height:22px}.dashcam-status{flex:0 0 auto;padding:22px clamp(14px,3.5vw,26px);color:var(--md-on-surface-var);text-align:center;font-size:14px}.dashcam-status.is-error{color:#ffb4ab}.logs-empty-state{flex:0 0 auto;display:grid;justify-items:center;width:fit-content;max-width:min(100%,360px);margin:0 auto;padding:14px 18px;border:1px solid color-mix(in srgb,var(--md-outline-var) 28%,transparent);border-radius:8px;background:color-mix(in srgb,var(--md-surface-cont) 46%,transparent);color:var(--md-on-surface);text-align:center}.logs-empty-state__title{color:var(--md-on-surface);font-size:14px;font-weight:850;line-height:1.25}.dashcam-routes{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain;overflow-anchor:none;scrollbar-gutter:stable;padding:6px var(--logs-content-inline) calc(var(--app-nav-bottom-gap, var(--nav-bar-height)) + 16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:10px}.dashcam-routes:has(.logs-empty-state),.screenrecord-list:has(.logs-empty-state){align-items:center;justify-content:center}.dashcam-virtual-spacer{flex:0 0 auto;width:100%;min-height:0;pointer-events:none}.dashcam-route-card{flex:0 0 auto;overflow:hidden;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 50%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface)}.dashcam-route-media{min-width:0}.dashcam-route-preview{position:relative;aspect-ratio:16 / 9;align-self:start;overflow:hidden;cursor:pointer;background:linear-gradient(180deg,var(--md-surface-cont-hh),var(--md-surface-cont))}.dashcam-route-media-info{display:none;padding:10px 12px 12px;border-top:1px solid color-mix(in srgb,var(--md-outline-var) 30%,transparent);background:color-mix(in srgb,var(--md-surface-cont-h) 92%,var(--md-surface));cursor:pointer}.dashcam-route-preview img,.dashcam-segment-thumb img{width:100%;height:100%;object-fit:cover;display:block}.logs-lazy-img{opacity:1;transition:opacity .16s ease}.logs-lazy-img[data-src]{opacity:0}.dashcam-segment-thumb.is-thumb-loading:after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;background:linear-gradient(100deg,var(--md-surface-cont-h) 0%,color-mix(in srgb,var(--md-on-surface) 10%,var(--md-surface-cont-h)) 50%,var(--md-surface-cont-h) 100%);background-size:220% 100%;animation:logsThumbSweep 1.25s ease-in-out infinite}@keyframes logsThumbSweep{0%{background-position:220% 0}to{background-position:-120% 0}}@media(prefers-reduced-motion:reduce){.dashcam-segment-thumb.is-thumb-loading:after{animation:none}}.dashcam-route-preview__shade{position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.08),transparent 45%,rgba(0,0,0,.36));pointer-events:none}.dashcam-route-preview__chips{position:absolute;left:12px;right:12px;bottom:12px;display:flex;flex-wrap:wrap;gap:8px}.dashcam-route-preview__timestamp{position:absolute;top:12px;left:12px;z-index:1;padding:5px 8px;border-radius:7px;background:#0000007a;border:1px solid rgba(255,255,255,.16);color:#fff;font-size:12px;font-weight:700;line-height:1.35;font-variant-numeric:tabular-nums;text-shadow:0 1px 2px rgba(0,0,0,.45);backdrop-filter:blur(8px);pointer-events:none}.dashcam-route-preview .dashcam-route-preview__menu{position:absolute;top:10px;right:10px;z-index:3;width:36px;height:36px;color:#fff;background:#0000007a;border:1px solid rgba(255,255,255,.16);box-shadow:0 3px 12px #0003;backdrop-filter:blur(8px)}.dashcam-route-preview .dashcam-route-preview__menu:hover{background:#000000a3}.dashcam-chip{display:inline-flex;align-items:center;min-height:26px;padding:0 10px;border-radius:999px;background:#0000006b;border:1px solid rgba(255,255,255,.16);color:#fff;font-size:12px;font-weight:750;backdrop-filter:blur(10px)}.dashcam-play-mark{position:absolute;inset:0;display:grid;place-items:center}.dashcam-play-mark svg{width:54px;height:54px;padding:11px;border-radius:999px;color:#fff;background:#0000005c;border:1px solid rgba(255,255,255,.18);box-shadow:0 8px 24px #00000042}.dashcam-route-main{min-width:0;padding:12px 14px}.dashcam-route-head{display:flex;align-items:flex-start;gap:10px;cursor:pointer;border-radius:6px}.dashcam-route-head:hover{background:color-mix(in srgb,var(--md-primary) 7%,transparent)}.dashcam-route-titleblock{min-width:0;flex:1}.dashcam-route-title{font-size:16px;line-height:1.25;font-weight:850;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashcam-route-subtitle{margin-top:4px;font-size:12.5px;color:var(--md-on-surface-var);opacity:.78;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashcam-expand-btn{width:34px;height:34px;border-radius:999px;flex:0 0 auto;margin-right:8px}.dashcam-segments{margin-top:12px}.dashcam-segments.is-collapsed{display:none}.dashcam-selection-row{display:flex;align-items:center;gap:8px;overflow-x:auto;padding-bottom:8px;color:var(--md-on-surface-var);font-size:13px;font-weight:750}.dashcam-selection-count{flex:0 0 auto;color:var(--md-on-surface-var)}.dashcam-selection-row .smallBtn{white-space:nowrap;min-height:34px;padding-inline:12px;border-radius:var(--r-sm);font-size:12.5px;font-weight:800}.dashcam-segment-list{max-height:min(40vh,330px);overflow:auto;overscroll-behavior:contain;overflow-anchor:none;-webkit-overflow-scrolling:touch;touch-action:pan-y;scrollbar-width:none;display:flex;flex-direction:column;gap:8px}.dashcam-segment-list::-webkit-scrollbar{display:none}.dashcam-segment-loader{flex:0 0 auto;width:100%;height:22px;position:relative;overflow:hidden;overflow-anchor:none;border-radius:999px;background:transparent;transition:background-color .16s ease-out}.dashcam-segment-loader.is-loading{background:color-mix(in srgb,var(--md-outline-var) 18%,transparent)}.dashcam-segment-loader:before{content:"";position:absolute;inset:8px 20%;border-radius:inherit;background:var(--md-primary);opacity:0;transition:opacity .16s ease-out}.dashcam-segment-loader.is-loading:before{opacity:1;animation:dashcam-segment-loader .82s ease-in-out infinite}.dashcam-segment-tile{flex:0 0 auto;min-height:70px;display:flex;align-items:center;gap:10px;padding:8px;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 42%,transparent);background:var(--md-surface-cont);cursor:pointer;content-visibility:auto;contain-intrinsic-size:70px 100%}@media(hover:hover){.dashcam-segment-tile:hover{border-color:color-mix(in srgb,var(--md-primary) 44%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 84%,var(--md-primary))}}.dashcam-segment-tile--append{animation:dashcam-segment-append .18s cubic-bezier(.2,0,0,1) both}@keyframes dashcam-segment-append{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes dashcam-segment-loader{0%{transform:translate(-160%)}to{transform:translate(160%)}}.dashcam-segment-thumb{position:relative;width:78px;height:54px;flex:0 0 78px;overflow:hidden;border-radius:6px;background:var(--md-surface-cont-hh)}.dashcam-segment-check{position:absolute;inset:0;display:grid;place-items:center;background:#0000001f}.dashcam-segment-check input{width:20px;height:20px;accent-color:var(--md-primary)}.dashcam-segment-inline-check{width:28px;height:28px;flex:0 0 28px;display:grid;place-items:center;border-radius:6px}.dashcam-segment-inline-check input{width:18px;height:18px;accent-color:var(--md-primary)}.dashcam-segment-body{min-width:0;flex:1}.dashcam-segment-topline{min-width:0;display:flex;align-items:center;gap:var(--sp-xs)}.dashcam-segment-badge{min-width:0;display:inline-flex;align-items:center;min-height:22px;padding:0 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-outline-var) 40%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface-var);font-size:11px;font-weight:850;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dashcam-segment-read-status{min-width:0;display:inline-flex;flex:0 0 auto;margin-left:auto}.dashcam-segment-read-status[hidden]{display:none}.dashcam-segment-name{margin-top:6px;font-size:13px;font-weight:650;color:var(--md-on-surface);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashcam-menu-btn{width:30px;height:30px;border-radius:999px;flex:0 0 auto}.dashcam-menu-btn svg{display:block;transform:translateZ(0)}.dashcam-group-menu-btn{margin-left:auto;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;padding-inline:12px}.dashcam-group-menu-btn svg{width:18px;height:18px}.dashcam-group-menu-btn--row{display:inline-flex;margin-left:auto}.logs-loading-list{display:flex;flex-direction:column;gap:10px;pointer-events:none}.logs-loading-card,.logs-loading-row{position:relative;overflow:hidden;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 36%,transparent);background:var(--md-surface-cont-h);opacity:0;transform:translateY(12px);animation:uiStaggerIn .38s cubic-bezier(.2,0,0,1) both;animation-delay:min(calc(var(--i, 0) * 36ms),.42s)}.logs-loading-card{min-height:184px}.logs-loading-row{min-height:64px}.logs-loading-card:before,.logs-loading-row:before{content:"";position:absolute;inset:0;background:linear-gradient(100deg,transparent 0%,color-mix(in srgb,var(--md-on-surface) 7%,transparent) 48%,transparent 72%);transform:translate(-100%);animation:logsSkeletonSweep 1.35s ease-in-out infinite;animation-delay:calc(var(--i, 0) * 90ms);z-index:1}.logs-loading-card:after,.logs-loading-row:after{content:"";position:absolute;inset:12px;border-radius:6px;background:linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 0 0 / 100% 104px no-repeat,linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 0 122px / 62% 14px no-repeat,linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 0 146px / 38% 12px no-repeat;z-index:0}.logs-loading-row:after{inset:8px 10px;background:linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 0 0 / 76px 48px no-repeat,linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 90px 7px / 52% 14px no-repeat,linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 90px 31px / 34% 11px no-repeat}@keyframes logsSkeletonSweep{to{transform:translate(100%)}}@media(prefers-reduced-motion:reduce){.logs-loading-card,.logs-loading-row{animation:none;opacity:1;transform:none}.dashcam-segment-tile--append{animation:none}.dashcam-segment-loader.is-loading:before{animation:none}.logs-loading-card:before,.logs-loading-row:before{animation:none;opacity:0}}.dashcam-share-card{display:grid;gap:10px;min-height:0}.dashcam-share-card__summary{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;color:var(--md-on-surface);font-size:13px;font-weight:750}.dashcam-share-card__summary span:last-child{color:var(--md-on-surface-var);font-size:12px;font-weight:800;text-align:right}.dashcam-share-card pre{margin:0;max-height:min(42vh,360px);overflow:auto;padding:12px;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 46%,transparent);background:color-mix(in srgb,var(--md-surface-cont-l) 84%,#000);color:var(--md-on-surface);font:12px/1.55 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre-wrap;word-break:break-word}#appDialog:has(.dashcam-share-dialog) .app-dialog__choiceBtn{min-height:40px;text-align:center!important;justify-content:center}#appDialog:has(.dashcam-share-dialog){align-items:center;padding:8px}#appDialog:has(.dashcam-share-dialog) .app-dialog__sheet{max-height:calc(var(--app-vv-height, 100dvh) - 16px)}#appDialog:has(.dashcam-share-dialog) .app-dialog__body{min-height:0}#appDialog:has(.dashcam-share-dialog) .app-dialog__actions{margin-top:10px}.dashcam-upload-progress{position:fixed;inset:0;z-index:170;display:grid;place-items:center;padding:18px;background:var(--popup-backdrop);opacity:0;transition:opacity .16s ease}.dashcam-upload-progress.is-open{opacity:1}.dashcam-upload-progress__sheet{width:min(92vw,430px);padding:18px;border-radius:var(--popup-radius);border:1px solid var(--popup-border-color);background:var(--popup-bg);box-shadow:var(--popup-shadow)}.dashcam-upload-progress__title{color:var(--md-on-surface);font-size:17px;font-weight:850}.dashcam-upload-progress__message{margin-top:8px;color:var(--md-on-surface-var);font-size:13px;font-weight:700}.dashcam-upload-progress__bar{height:4px;margin-top:16px;overflow:hidden;border-radius:999px;background:color-mix(in srgb,var(--md-outline-var) 30%,transparent)}.dashcam-upload-progress__bar span{display:block;width:42%;height:100%;border-radius:inherit;background:var(--md-primary);animation:dashcam-upload-indeterminate 1.1s ease-in-out infinite}.dashcam-upload-progress__summary{margin-top:10px;color:var(--md-on-surface-var);font-size:12px;font-weight:800}.dashcam-upload-progress__actions{display:flex;justify-content:flex-end;margin-top:14px}.dashcam-upload-progress__cancel{min-height:38px;padding-inline:16px;border-radius:var(--dialog-control-radius);border-color:color-mix(in srgb,var(--md-error) 42%,var(--md-outline-var));color:color-mix(in srgb,var(--md-error) 82%,var(--md-on-surface));background:transparent}.dashcam-upload-progress__cancel:disabled{cursor:wait;opacity:.62}@media(min-aspect-ratio:13/10),(horizontal-viewport-segments:2),(vertical-viewport-segments:2),(min-width:640px)and (min-height:650px){.dashcam-share-card{gap:8px}.dashcam-share-card pre{max-height:min(42vh,190px);padding:10px;font-size:11px;line-height:1.45}#appDialog:has(.dashcam-share-dialog) .app-dialog__sheet{width:min(72vw,520px);max-height:calc(var(--app-vv-height, 100dvh) - 16px);padding:12px 14px}#appDialog:has(.dashcam-share-dialog) .app-dialog__head{margin-bottom:6px}#appDialog:has(.dashcam-share-dialog) .app-dialog__title{font-size:14px}#appDialog:has(.dashcam-share-dialog) .app-dialog__actions{margin-top:8px}.dashcam-upload-progress{padding:8px}.dashcam-upload-progress__sheet{width:min(70vw,520px);max-height:calc(var(--app-vv-height, 100dvh) - 16px);overflow:auto;padding:12px}.dashcam-upload-progress__title{font-size:14px}.dashcam-upload-progress__message{margin-top:5px;font-size:12px}.dashcam-upload-progress__bar{margin-top:10px}.dashcam-upload-progress__summary{margin-top:8px;font-size:11px}.dashcam-upload-progress__actions{margin-top:10px}.dashcam-upload-progress__cancel{min-height:34px}}@keyframes dashcam-upload-indeterminate{0%{transform:translate(-115%)}to{transform:translate(260%)}}.dashcam-player-overlay{position:fixed;inset:0;z-index:calc(var(--z-toast) - 1);display:grid;place-items:center;padding:16px;background:#000000ad;opacity:0;transition:opacity .16s ease}.dashcam-player-overlay.is-open{opacity:1}.dashcam-player-overlay.is-menu-open,.dashcam-player-overlay.is-action-open{z-index:calc(var(--z-modal) - 1)}.dashcam-player-dialog{width:auto;max-width:min(92vw,960px);max-height:min(78vh,680px)}.dashcam-player-frame{position:relative;overflow:hidden;border-radius:8px;border:1px solid rgba(255,255,255,.16);background:#000;box-shadow:0 20px 60px #00000070}.dashcam-player-video{width:auto;height:auto;max-width:min(92vw,960px);max-height:min(78vh,680px);display:block;background:#000;object-fit:contain}.dashcam-player-frame .plyr{width:auto;max-width:min(92vw,960px);max-height:min(78vh,680px);background:#000;--plyr-color-main: var(--md-primary)}.dashcam-player-frame .plyr video{max-width:min(92vw,960px);max-height:min(78vh,680px);object-fit:contain;background:#000}.dashcam-player-frame .plyr__controls{z-index:5}.dashcam-player-frame .plyr__control--overlaid{display:none!important}.dashcam-player-transport{z-index:4;pointer-events:auto}.dashcam-player-frame .plyr:-webkit-full-screen,.dashcam-player-frame .plyr:fullscreen,.dashcam-player-frame .plyr.plyr--fullscreen-active{width:100vw!important;height:100vh!important;max-width:none;max-height:none;display:grid;place-items:center;background:#000}.dashcam-player-frame .plyr:-webkit-full-screen video,.dashcam-player-frame .plyr:fullscreen video,.dashcam-player-frame .plyr.plyr--fullscreen-active video{width:100vw!important;height:100vh!important;max-width:none;max-height:none;object-fit:contain;object-position:center center}.dashcam-player-frame .plyr__video-wrapper{display:grid;place-items:center;background:#000}.dashcam-player-frame .plyr:-webkit-full-screen .plyr__video-wrapper,.dashcam-player-frame .plyr:fullscreen .plyr__video-wrapper,.dashcam-player-frame .plyr.plyr--fullscreen-active .plyr__video-wrapper{width:100vw;height:100vh}.dashcam-player-frame .plyr__control svg{display:block;margin:auto}.dashcam-player-overlay--dashcam .dashcam-player-dialog:not(.dashcam-player-dialog--browsable){width:min(84vw,760px);max-width:min(96vw,920px);max-height:min(84vh,760px)}.dashcam-player-overlay--dashcam .dashcam-player-video{width:100%;height:auto;max-width:none;max-height:min(84vh,760px)}.dashcam-player-overlay--dashcam .dashcam-player-frame .plyr,.dashcam-player-overlay--dashcam .dashcam-player-frame .plyr video{width:100%;max-width:none;max-height:min(84vh,760px)}.dashcam-player-top{position:absolute;left:0;right:0;top:0;z-index:3;display:flex;align-items:center;gap:8px;padding:10px;background:linear-gradient(180deg,rgba(0,0,0,.62),transparent);pointer-events:none;transition:opacity .2s ease,transform .2s ease}.dashcam-player-toast{display:none;position:absolute;top:50%;left:50%;z-index:5;padding:12px 22px;border-radius:999px;background:#000000bd;color:#fff;font-weight:700;font-size:18px;letter-spacing:.02em;white-space:nowrap;pointer-events:none;opacity:0;transform:translate(-50%,-50%) scale(.92);transition:opacity .14s ease,transform .14s ease;backdrop-filter:blur(8px);box-shadow:0 10px 28px #0000005c}.dashcam-player-toast.is-visible{opacity:1;transform:translate(-50%,-50%) scale(1)}.dashcam-player-title{min-width:0;flex:1;color:#fff;font-weight:800;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashcam-player-heading{min-width:0;flex:1}.dashcam-player-subtitle{margin-top:2px;color:#fffc;font-size:11px;font-weight:650;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-variant-numeric:tabular-nums}.dashcam-player-close{pointer-events:auto}.dashcam-player-action{width:var(--close-size, 32px);height:var(--close-size, 32px);border:1px solid color-mix(in srgb,var(--md-outline-var) 40%,transparent);border-radius:var(--close-radius, 10px);color:#fff;background:color-mix(in srgb,var(--md-surface-cont) 82%,#000);pointer-events:auto;transition:background var(--motion-quick) var(--ease-standard),border-color var(--motion-quick) var(--ease-standard),color var(--motion-quick) var(--ease-standard)}.dashcam-player-action svg{width:var(--close-icon, 16px);height:var(--close-icon, 16px)}.dashcam-player-action:hover{background:color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont));border-color:color-mix(in srgb,var(--md-primary) 32%,var(--md-outline-var));color:#fff}.dashcam-player-action:disabled{opacity:.55;cursor:default}.screenrecord-list{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable;padding:10px var(--logs-content-inline) calc(var(--app-nav-bottom-gap, var(--nav-bar-height)) + 16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:8px}.dashcam-routes.is-loading-more:after,.screenrecord-list.is-loading-more:after{content:"";flex:0 0 auto;width:22px;height:22px;margin:10px auto 2px;border:2px solid color-mix(in srgb,var(--md-outline-var) 48%,transparent);border-top-color:var(--md-primary);border-radius:999px;animation:screenrecord-loading-spin .78s linear infinite}@keyframes screenrecord-loading-spin{to{transform:rotate(360deg)}}.screenrecord-row{flex:0 0 auto;min-height:64px;display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 44%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface);cursor:pointer;content-visibility:auto;contain-intrinsic-size:auto 64px}.screenrecord-list .screenrecord-row.ui-stagger-item{animation:none}.screenrecord-virtual-spacer{flex:0 0 auto;width:100%;min-height:0;pointer-events:none}.screenrecord-row:hover{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary))}.screenrecord-row__icon{width:38px;height:38px;flex:0 0 38px;display:grid;place-items:center;border-radius:8px;background:color-mix(in srgb,var(--md-primary) 12%,var(--md-surface-cont));color:var(--md-primary);overflow:hidden}.screenrecord-row__thumb{width:76px;height:48px;flex:0 0 76px;display:block;border-radius:6px;background:var(--md-surface-cont);overflow:hidden}.screenrecord-row__thumb img{width:100%;height:100%;object-fit:cover;display:block}.screenrecord-row__icon svg,.screenrecord-download svg{width:21px;height:21px}.screenrecord-row__main{min-width:0;flex:1 1 auto}.screenrecord-row__name{font-size:14px;line-height:1.25;font-weight:750;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.screenrecord-row__meta{margin-top:6px;display:flex;flex-wrap:wrap;gap:6px 10px;color:var(--md-on-surface-var);font-size:12px;line-height:1.25}.screenrecord-download{appearance:none;border:0;width:36px;height:36px;flex:0 0 36px;display:grid;place-items:center;border-radius:8px;color:var(--md-on-surface-var);background:transparent;cursor:pointer}.screenrecord-download:hover{color:var(--md-primary);background:color-mix(in srgb,var(--md-primary) 10%,transparent)}@media(min-width:900px){.logs-shell{--logs-content-inline: clamp(18px, 3vw, 34px);--logs-route-media-column: minmax(260px, 34%)}.dashcam-route-card{display:grid;grid-template-columns:var(--logs-route-media-column) minmax(0,1fr);align-items:start}.dashcam-route-preview{width:100%}.dashcam-route-media-info{display:block}.dashcam-route-main{padding:10px 14px 12px}.dashcam-route-head{justify-content:flex-end;min-height:34px}.dashcam-route-head .dashcam-route-titleblock{display:none}}@media(min-aspect-ratio:13/10),(horizontal-viewport-segments:2),(vertical-viewport-segments:2),(min-width:640px)and (min-height:650px){.logs-shell{--logs-route-media-column: minmax(230px, 32%)}.dashcam-route-card{display:grid;grid-template-columns:var(--logs-route-media-column) minmax(0,1fr);align-items:stretch;height:clamp(178px,45vh,236px)}.dashcam-route-media{min-height:0;height:100%;display:flex;flex-direction:column}.dashcam-route-preview{flex:1 1 auto;min-height:0;width:100%;aspect-ratio:auto}.dashcam-route-media-info{display:block;flex:0 0 auto;min-height:0;overflow:hidden}.dashcam-route-main{min-height:0;height:100%;display:flex;flex-direction:column;padding-top:8px;padding-right:0}.dashcam-route-head{display:none}.dashcam-segments,.dashcam-segments.is-collapsed{min-height:0;flex:1 1 auto;display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px;margin-top:0}.dashcam-selection-row{grid-column:2;grid-row:1;min-height:0;height:100%;flex-direction:column;justify-content:center;align-items:center;gap:6px;box-sizing:border-box;overflow-y:auto;padding:8px;border-left:1px solid color-mix(in srgb,var(--md-outline-var) 38%,transparent);scrollbar-width:none;text-align:center}.dashcam-selection-row::-webkit-scrollbar{display:none}.dashcam-selection-row .dashcam-group-menu-btn--row{margin-left:0}.dashcam-segment-list{grid-column:1;grid-row:1;flex:1 1 auto;min-height:0;max-height:none}.dashcam-selection-count{text-align:center;font-size:12px}.dashcam-selection-row .smallBtn{width:100%;min-height:30px;padding-inline:8px;font-size:12px;white-space:normal;line-height:1.15;overflow-wrap:anywhere}.dashcam-selection-row .btn--filled{padding-inline:8px}.dashcam-segment-tile--compact{min-height:46px;gap:8px;padding:5px 8px 5px 5px;contain-intrinsic-size:46px 100%}.dashcam-segment-thumb--compact{width:56px;height:36px;flex:0 0 56px}.dashcam-segment-check--compact input{width:16px;height:16px}.dashcam-segment-tile--compact .dashcam-segment-body{min-width:0;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px}.dashcam-segment-tile--compact .dashcam-segment-topline{display:contents}.dashcam-segment-tile--compact .dashcam-segment-badge{grid-column:1;grid-row:1;min-height:20px;padding-inline:7px;font-size:10.5px}.dashcam-segment-tile--compact .dashcam-segment-name{min-width:0;grid-column:2;grid-row:1;margin-top:0;font-size:12.5px}.dashcam-segment-tile--compact .dashcam-segment-read-status{grid-column:3;grid-row:1;margin-left:0}.dashcam-segment-tile--compact .dashcam-menu-btn{width:26px;height:26px}}@media(max-height:620px)and (min-aspect-ratio:13/10),(max-height:620px)and (horizontal-viewport-segments:2),(max-height:620px)and (vertical-viewport-segments:2),(min-width:640px)and (min-height:650px)and (max-height:620px){.logs-tabbar{padding-top:6px;padding-bottom:4px;--logs-tab-min-height: 32px;--logs-tab-font-size: 12.5px}.logs-menu__button{min-height:38px}.dashcam-routes,.screenrecord-list{gap:8px;padding-top:4px}.dashcam-route-card{display:grid;grid-template-columns:var(--logs-route-media-column) minmax(0,1fr);align-items:stretch;height:clamp(168px,46vh,196px)}.dashcam-route-media-info{display:block;padding:8px 10px 10px}.dashcam-route-title{font-size:14px}.dashcam-route-subtitle{font-size:11.5px}.dashcam-selection-row{padding:6px 6px 6px 8px}.dashcam-segments,.dashcam-segments.is-collapsed{grid-template-columns:minmax(0,1fr) 86px}.dashcam-segment-list{max-height:none}.dashcam-player-dialog{width:auto;max-width:min(76vw,760px);max-height:68vh}.dashcam-player-video,.dashcam-player-frame .plyr,.dashcam-player-frame .plyr video{max-width:min(76vw,760px);max-height:68vh}.dashcam-player-title{font-size:12.5px}}.dashcam-player-dialog,.dashcam-segment-read-status{--logs-player-status-reading-surface: var(--md-primary);--logs-player-status-reading-ink: var(--md-on-primary);--logs-player-status-recent-surface: var(--md-primary-state-soft);--logs-player-status-recent-ink: var(--md-primary);--logs-player-status-recent-border: color-mix(in srgb, var(--md-primary) 36%, transparent)}.dashcam-player-dialog{--logs-player-dialog-radius: var(--r-md);--logs-player-dialog-border: color-mix(in srgb, var(--md-outline-var) 42%, transparent);--logs-player-dialog-surface: var(--md-surface-cont);--logs-player-dialog-wide: 88rem;--logs-player-dialog-max-height: 40rem;--logs-player-dialog-landscape-aspect: 12 / 5;--logs-player-dialog-portrait-wide: 42rem;--logs-player-dialog-portrait-max-height: 42rem;--logs-player-dialog-portrait-viewport-height: 80dvh;--logs-player-browser-pane-width: clamp(15.5rem, 22dvw, 19.5rem);--logs-player-browser-pane-compact-width: clamp(15rem, 31dvw, 16.5rem);--logs-player-browser-divider: color-mix(in srgb, var(--md-outline-var) 34%, transparent);--logs-player-browser-surface: var(--md-surface-cont);--logs-player-section-padding-inline: var(--sp-md);--logs-player-section-padding-block: var(--sp-sm);--logs-player-summary-gap: var(--sp-sm);--logs-player-summary-count-surface: var(--md-primary-state-soft);--logs-player-summary-count-ink: var(--md-primary);--logs-player-summary-muted: color-mix(in srgb, var(--md-on-surface-var) 72%, transparent);--logs-player-segment-list-gap: var(--sp-xs);--logs-player-segment-list-padding: var(--sp-sm);--logs-player-segment-item-min-height: 4.5rem;--logs-player-segment-item-padding: var(--sp-xs);--logs-player-segment-item-radius: var(--r-sm);--logs-player-segment-item-surface: color-mix(in srgb, var(--md-on-surface) 3%, transparent);--logs-player-segment-item-hover: color-mix(in srgb, var(--md-on-surface) 7%, transparent);--logs-player-segment-item-active: var(--md-primary-state-soft);--logs-player-segment-item-border: color-mix(in srgb, var(--md-outline-var) 28%, transparent);--logs-player-segment-item-active-border: color-mix(in srgb, var(--md-primary) 62%, var(--md-outline-var));--logs-player-segment-thumb-width: 5.5rem;--logs-player-segment-thumb-radius: var(--r-xs);--logs-player-segment-thumb-surface: var(--md-surface-cont-h);--logs-player-segment-muted: color-mix(in srgb, var(--md-on-surface-var) 70%, transparent)}.dashcam-player-layout{width:100%;min-width:0;min-height:0}.dashcam-player-dialog--browsable{--logs-player-browser-current-width: var(--logs-player-browser-pane-width);width:min(calc(var(--app-vv-width, 100dvw) - var(--sp-lg) - var(--sp-lg) - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px)),var(--logs-player-dialog-wide));height:auto;max-height:min(calc(var(--app-vv-height, 100dvh) - var(--sp-lg) - var(--sp-lg) - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px)),var(--logs-player-dialog-max-height));max-width:none;aspect-ratio:var(--logs-player-dialog-landscape-aspect);overflow:hidden;border:1px solid var(--logs-player-dialog-border);border-radius:var(--logs-player-dialog-radius);background:var(--logs-player-dialog-surface);container-name:logs-player-dialog;container-type:inline-size}.dashcam-player-dialog--browsable .dashcam-player-layout{height:100%;display:grid;grid-template-columns:minmax(0,1fr) var(--logs-player-browser-current-width)}.dashcam-player-dialog--browsable .dashcam-player-frame{min-width:0;min-height:0;height:100%;border:0;border-radius:0;box-shadow:none}.dashcam-player-dialog--browsable .dashcam-player-video,.dashcam-player-dialog--browsable .dashcam-player-frame .plyr,.dashcam-player-dialog--browsable .dashcam-player-frame .plyr video{width:100%;height:100%;max-width:none;max-height:none;object-fit:contain}.dashcam-player-browser{min-width:0;min-height:0;display:grid;grid-template-rows:auto minmax(0,1fr);overflow:hidden;border-left:1px solid var(--logs-player-browser-divider);background:var(--logs-player-browser-surface)}.dashcam-player-group-summary{min-width:0;display:grid;gap:var(--logs-player-summary-gap);padding:var(--logs-player-section-padding-block) var(--logs-player-section-padding-inline) var(--sp-md)}.dashcam-player-section-title{margin:0;color:var(--md-on-surface);font-size:var(--fs-title-xs);font-weight:850;line-height:1.3}.dashcam-player-group-summary__body{min-width:0;display:grid;gap:var(--sp-xs)}.dashcam-player-group-summary__main{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:var(--sp-sm)}.dashcam-player-group-summary__title{min-width:0;overflow:hidden;color:var(--md-on-surface);font-size:var(--fs-body-sm);font-weight:850;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.dashcam-player-group-summary__count{flex:0 0 auto;padding:var(--sp-xs) var(--sp-sm);border-radius:var(--r-round);background:var(--logs-player-summary-count-surface);color:var(--logs-player-summary-count-ink);font-size:var(--fs-label-sm);font-weight:850;line-height:1.2;white-space:nowrap}.dashcam-player-group-summary__time{min-width:0;overflow:hidden;color:var(--logs-player-summary-muted);font-size:var(--fs-label-sm);font-variant-numeric:tabular-nums;font-weight:700;line-height:1.35;text-overflow:ellipsis;white-space:nowrap}.dashcam-player-segment-region[hidden]{display:none!important}.dashcam-player-segment-region{min-width:0;min-height:0;overflow:hidden;border-top:1px solid var(--logs-player-browser-divider)}.dashcam-player-segment-browser{min-width:0;min-height:0;height:100%;display:grid;grid-template-rows:auto minmax(0,1fr)}.dashcam-player-segment-browser>.dashcam-player-section-title{padding:var(--logs-player-section-padding-block) var(--logs-player-section-padding-inline)}.dashcam-player-segment-list{min-width:0;min-height:0;display:grid;grid-auto-rows:max-content;align-content:start;gap:var(--logs-player-segment-list-gap);overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;padding:0 var(--logs-player-segment-list-padding) var(--logs-player-segment-list-padding);scrollbar-width:thin}.dashcam-player-segment-item{width:100%;min-width:0;min-height:var(--logs-player-segment-item-min-height);display:grid;grid-template-columns:var(--logs-player-segment-thumb-width) minmax(0,1fr);align-items:center;gap:var(--sp-sm);padding:var(--logs-player-segment-item-padding);border:1px solid var(--logs-player-segment-item-border);border-radius:var(--logs-player-segment-item-radius);background:var(--logs-player-segment-item-surface);color:var(--md-on-surface);font:inherit;text-align:left;cursor:pointer;transition:border-color var(--motion-quick) var(--ease-standard),background var(--motion-quick) var(--ease-standard)}.dashcam-player-segment-item:hover{background:var(--logs-player-segment-item-hover)}.dashcam-player-segment-item:focus-visible{border-color:var(--logs-player-segment-item-active-border);outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset)}.dashcam-player-segment-item.is-active{border-color:var(--logs-player-segment-item-active-border);background:var(--logs-player-segment-item-active)}.dashcam-player-segment-thumb{position:relative;width:100%;overflow:hidden;border-radius:var(--logs-player-segment-thumb-radius);aspect-ratio:16 / 9;background:var(--logs-player-segment-thumb-surface)}.dashcam-player-segment-thumb img{width:100%;height:100%;display:block;object-fit:cover}.dashcam-player-segment-copy{min-width:0;display:grid;gap:var(--sp-xs)}.dashcam-player-segment-meta{min-width:0;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-xs)}.dashcam-player-segment-status{min-width:0;display:inline-flex;flex:0 0 auto}.dashcam-player-segment-status[hidden]{display:none}.dashcam-player-segment-time,.dashcam-player-segment-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dashcam-player-segment-time{color:var(--md-on-surface);font-size:var(--fs-body-sm);font-variant-numeric:tabular-nums;font-weight:850;line-height:1.3}.dashcam-player-segment-name{color:var(--logs-player-segment-muted);font-size:var(--fs-label-sm);font-weight:700;line-height:1.3}.dashcam-player-status-tag{min-width:0;display:inline-flex;align-items:center;justify-content:center;padding:var(--sp-xs) var(--sp-sm);border:1px solid transparent;border-radius:var(--r-round);font-size:var(--fs-label-sm);font-weight:850;line-height:1.2;white-space:nowrap}.dashcam-player-status-tag[data-state=reading]{background:var(--logs-player-status-reading-surface);color:var(--logs-player-status-reading-ink)}.dashcam-player-status-tag[data-state=recent]{border-color:var(--logs-player-status-recent-border);background:var(--logs-player-status-recent-surface);color:var(--logs-player-status-recent-ink)}@container logs-player-dialog (max-width: 46rem){.dashcam-player-layout{--logs-player-segment-thumb-width: 4.75rem}}@media(orientation:portrait){.dashcam-player-dialog--browsable{width:min(calc(var(--app-vv-width, 100dvw) - var(--sp-md) - var(--sp-md) - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px)),var(--logs-player-dialog-portrait-wide));height:min(calc(var(--app-vv-height, 100dvh) - var(--sp-lg) - var(--sp-lg) - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px)),var(--logs-player-dialog-portrait-viewport-height),var(--logs-player-dialog-portrait-max-height));max-height:none;aspect-ratio:auto}.dashcam-player-dialog--browsable .dashcam-player-layout{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}.dashcam-player-dialog--browsable .dashcam-player-frame{height:auto;aspect-ratio:16 / 9}.dashcam-player-browser{border-top:1px solid var(--logs-player-browser-divider);border-left:0}}@media(orientation:landscape)and (max-height:30rem){.dashcam-player-dialog--browsable{--logs-player-browser-current-width: var(--logs-player-browser-pane-compact-width);width:min(calc(var(--app-vv-width, 100dvw) - var(--sp-md) - var(--sp-md) - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px)),var(--logs-player-dialog-wide));max-height:calc(var(--app-vv-height, 100dvh) - var(--sp-md) - var(--sp-md) - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px))}}@media(prefers-reduced-motion:reduce){.dashcam-player-dialog--browsable,.dashcam-player-dialog--browsable *{transition:none!important}}.route-summary,.route-summary-state{--route-summary-gap: var(--sp-md);--route-summary-section-gap: var(--sp-sm);--route-summary-radius: var(--r-md);--route-summary-radius-small: var(--r-sm);--route-summary-border: color-mix(in srgb, var(--md-outline-var) 46%, transparent);--route-summary-divider: color-mix(in srgb, var(--md-outline-var) 30%, transparent);--route-summary-surface: var(--md-surface-cont-l);--route-summary-surface-raised: var(--md-surface-cont-h);--route-summary-text: var(--md-on-surface);--route-summary-muted: color-mix(in srgb, var(--md-on-surface-var) 76%, transparent);--route-summary-accent: var(--md-primary);--route-summary-accent-soft: var(--md-primary-state-soft);--route-summary-auto: var(--md-info);--route-summary-manual: var(--md-error);--route-summary-manual-soft: color-mix(in srgb, var(--md-error) 35%, transparent);--route-summary-gas: var(--md-warning);--route-summary-brake: var(--md-primary);--route-summary-event-over: var(--md-warning);--route-summary-event-over-soft: var(--md-warning-cont);--route-summary-event-hard: var(--md-error);--route-summary-event-hard-soft: color-mix(in srgb, var(--md-error-cont) 24%, var(--md-surface-cont-h));--route-summary-danger: var(--md-error);--route-summary-danger-soft: color-mix(in srgb, var(--md-error-cont) 24%, var(--md-surface-cont-h));--route-summary-progress-track: var(--md-surface-cont-h);--route-summary-progress-fill: var(--md-primary);--route-summary-progress-height: var(--sp-xs);--route-summary-progress-motion: var(--motion-medium);--route-summary-progress-cycle: calc(var(--motion-long) + var(--motion-long) + var(--motion-long));--route-summary-distribution-height: var(--sp-lg)}.route-summary,.route-summary-state{box-sizing:border-box;width:100%;color:var(--route-summary-text);font-family:var(--font-sans);text-align:left;white-space:normal}.route-summary{display:grid;gap:var(--route-summary-gap);container-name:route-summary;container-type:inline-size}#appDialog:has(.route-summary) .app-dialog__sheet{width:min(100%,var(--content-max-width))}.route-summary-hero__subtitle{display:grid;justify-items:start;gap:1px;margin:0;color:var(--route-summary-muted);font:700 var(--fs-label-sm)/1.45 var(--font-sans);font-variant-numeric:tabular-nums}.route-summary-hero__date,.route-summary-hero__range{white-space:nowrap}.route-summary__section{min-width:0;display:grid;align-content:start;gap:var(--route-summary-section-gap);padding:var(--sp-md);border:1px solid var(--route-summary-border);border-radius:var(--route-summary-radius);background:var(--route-summary-surface)}.route-summary__section-title{margin:0;color:var(--route-summary-text);font-size:var(--fs-title-xs);font-weight:800;line-height:1.35}.route-summary__dashboard{min-width:0;display:grid;align-items:start;gap:var(--route-summary-gap)}.route-summary-hero{min-width:0;display:grid;gap:var(--sp-md)}.route-summary-hero__time{min-width:0;display:grid;align-content:start;gap:var(--sp-xs)}.route-summary__metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start;gap:var(--sp-md);margin:0}.route-summary__metrics--distance{padding-top:var(--sp-xs)}.route-summary-metric{min-width:0;display:grid;gap:var(--sp-xs)}.route-summary-metric__label,.route-summary-detail__label{color:var(--route-summary-muted);font-size:var(--fs-label-sm);font-weight:700;line-height:1.35}.route-summary-metric__value{min-width:0;display:flex;align-items:baseline;gap:var(--sp-xs);margin:0;font-variant-numeric:tabular-nums}.route-summary-metric__number{overflow:hidden;font-size:var(--fs-title-md);font-weight:850;line-height:1.2;text-overflow:ellipsis;white-space:nowrap}.route-summary-metric[data-variant=hero] .route-summary-metric__number{color:var(--route-summary-accent);font-size:clamp(var(--fs-title-lg),5cqi,var(--fs-headline))}.route-summary-metric[data-variant=compact] .route-summary-metric__number{font-size:var(--fs-title-sm)}.route-summary-metric__unit{color:var(--route-summary-muted);font-size:var(--fs-label-sm);font-weight:700}.route-summary-events,.route-summary-details{display:grid;gap:0;overflow:hidden;border:0;border-radius:var(--route-summary-radius-small);background:transparent}.route-summary-event,.route-summary-detail{min-width:0;border-top:1px solid var(--route-summary-divider);background:transparent}.route-summary-event:first-child,.route-summary-detail:first-child{border-top:0}.route-summary-composition{display:grid;gap:var(--sp-xs) var(--sp-md)}.route-summary-composition__stat{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:var(--sp-xs);align-items:baseline}.route-summary-composition__label,.route-summary-event__label{min-width:0;overflow:hidden;font-size:var(--fs-body-sm);font-weight:750;line-height:1.4;text-overflow:ellipsis;white-space:nowrap}.route-summary-composition__label{display:inline-flex;align-items:center;gap:var(--sp-xs)}.route-summary-composition__label:before{width:.55em;height:.55em;flex:0 0 auto;border-radius:var(--r-round);background:currentColor;content:""}.route-summary-composition__duration,.route-summary-composition__ratio{color:var(--route-summary-muted);font:750 var(--fs-label-sm)/1.35 var(--font-mono);font-variant-numeric:tabular-nums}.route-summary-composition__stat[data-kind=auto]{color:var(--route-summary-auto)}.route-summary-composition__stat[data-kind=manual]{color:var(--route-summary-manual)}.route-summary-composition__stat[data-kind=gas]{color:var(--route-summary-gas)}.route-summary-composition__stat[data-kind=brake]{color:var(--route-summary-brake)}.route-summary-composition__stat[data-kind] .route-summary-composition__duration,.route-summary-composition__stat[data-kind] .route-summary-composition__ratio{color:inherit}.route-summary-distribution{display:flex;width:100%;height:var(--route-summary-distribution-height);overflow:hidden;border:1px solid var(--route-summary-border);border-radius:var(--r-sm);background:var(--route-summary-surface-raised)}.route-summary-distribution__segment,.route-summary-distribution__manual{display:block;flex:var(--route-summary-weight, 0) 1 0%;height:100%;box-sizing:border-box}.route-summary-distribution__manual{min-width:0;display:flex;overflow:hidden;border:2px solid var(--route-summary-manual);background:var(--route-summary-manual-soft)}.route-summary-distribution__manual>*+*{border-left:1px solid var(--route-summary-surface)}.route-summary-distribution__segment[data-kind=auto]{background:var(--route-summary-auto)}.route-summary-distribution__segment[data-kind=manual]{background:var(--route-summary-manual)}.route-summary-distribution__segment[data-kind=gas]{background:var(--route-summary-gas)}.route-summary-distribution__segment[data-kind=brake]{background:var(--route-summary-brake)}.route-summary-distribution__segment[data-kind=other]{background:var(--route-summary-manual-soft)}.route-summary-event{display:grid;gap:var(--sp-sm);padding:var(--sp-sm) var(--sp-md)}.route-summary-event[data-tone=danger]{background:var(--route-summary-danger-soft)}.route-summary-event__head{display:flex;align-items:center;gap:var(--sp-sm)}.route-summary-event__count{min-width:2.25em;margin-left:auto;padding:2px var(--sp-sm);border-radius:var(--r-round);background:var(--route-summary-surface-raised);color:var(--route-summary-text);font:800 var(--fs-label-sm)/1.4 var(--font-mono);text-align:center}.route-summary-event[data-tone=danger] .route-summary-event__count{background:var(--route-summary-danger);color:var(--md-on-error)}.route-summary-event__times{display:flex;flex-wrap:wrap;gap:var(--sp-xs);margin:0;padding:0;list-style:none}.route-summary-event__time{display:inline-flex;align-items:baseline;gap:var(--sp-xs);padding:3px var(--sp-sm);border:1px solid color-mix(in srgb,var(--route-summary-event-over) 42%,transparent);border-radius:var(--r-sm);background:var(--route-summary-event-over-soft);color:var(--route-summary-event-over);font:700 var(--fs-label-sm)/1.35 var(--font-mono);font-variant-numeric:tabular-nums}.route-summary-event[data-tone=danger] .route-summary-event__time{border-color:color-mix(in srgb,var(--route-summary-event-hard) 46%,transparent);background:var(--route-summary-event-hard-soft);color:var(--route-summary-event-hard)}.route-summary-event__peak{color:inherit;font-weight:850}.route-summary-detail{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--sp-md);align-items:baseline;padding:var(--sp-sm) var(--sp-md)}.route-summary-detail__value{margin:0;font:800 var(--fs-body-sm)/1.4 var(--font-mono);font-variant-numeric:tabular-nums;text-align:right}.route-summary__warning{margin:0;padding:var(--sp-sm) var(--sp-md);border-radius:var(--route-summary-radius-small);background:var(--route-summary-danger-soft);color:var(--route-summary-text);font-size:var(--fs-label-md);font-weight:700;line-height:1.45}.route-summary-metadata{overflow:hidden;border:1px solid var(--route-summary-border);border-radius:var(--route-summary-radius);background:var(--route-summary-surface)}.route-summary-metadata__summary{min-height:var(--control-min-height);display:flex;align-items:center;padding-inline:var(--sp-md);color:var(--route-summary-muted);font-size:var(--fs-label-md);font-weight:750;cursor:pointer}.route-summary-metadata__summary:focus-visible{outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:calc(0px - var(--focus-ring-width))}.route-summary-metadata__list{display:grid;gap:1px;margin:0;border-top:1px solid var(--route-summary-divider);background:var(--route-summary-divider)}.route-summary-metadata__row{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.5fr);gap:var(--sp-md);padding:var(--sp-sm) var(--sp-md);background:var(--route-summary-surface);font-size:var(--fs-label-sm);line-height:1.4}.route-summary-metadata__row dt{color:var(--route-summary-muted)}.route-summary-metadata__row dd{min-width:0;margin:0;overflow-wrap:anywhere;text-align:right}.route-summary-state{display:grid;place-content:center;gap:var(--sp-md);padding-block:var(--sp-xl);color:var(--route-summary-muted);font-size:var(--fs-body-sm);line-height:1.45;text-align:center}.route-summary__empty{min-height:calc(var(--control-min-height) + var(--control-min-height) + var(--sp-xl));display:grid;place-content:center;padding:var(--sp-lg);color:var(--route-summary-muted);font-size:var(--fs-body-sm);line-height:1.45;text-align:center}.route-summary-state__title{color:var(--route-summary-text);font-size:var(--fs-body-md);font-weight:800;line-height:1.4}.route-summary-state__description{color:var(--route-summary-muted);font-size:var(--fs-label-md);font-variant-numeric:tabular-nums;line-height:1.4}.route-summary-state__progress{position:relative;width:100%;height:var(--route-summary-progress-height);overflow:hidden;border-radius:var(--r-round);background:var(--route-summary-progress-track)}.route-summary-state__progress-fill{display:block;width:calc(var(--route-summary-progress, 0) * 1%);height:100%;border-radius:inherit;background:var(--route-summary-progress-fill);transition:width var(--route-summary-progress-motion) var(--ease-standard)}.route-summary-state__progress[data-mode=indeterminate] .route-summary-state__progress-fill{width:32%;animation:route-summary-progress-indeterminate var(--route-summary-progress-cycle) var(--ease-standard) infinite}@keyframes route-summary-progress-indeterminate{0%{transform:translate(-110%)}to{transform:translate(330%)}}@container route-summary (min-width: 540px){.route-summary-hero{grid-template-columns:minmax(0,.75fr) minmax(0,1.75fr);align-items:end}.route-summary-composition,.route-summary__dashboard{grid-template-columns:repeat(2,minmax(0,1fr))}.route-summary__section--events{grid-column:1 / -1}.route-summary__metrics--distance{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(prefers-reduced-motion:reduce){.route-summary *,.route-summary-state *{animation:none;scroll-behavior:auto;transition:none}} +.page--logs{height:calc(var(--app-vv-height, 100dvh) - var(--app-nav-bottom-gap, var(--nav-bar-height)));min-height:0;background:var(--md-surface);overflow:hidden}@media(min-aspect-ratio:13/10),(horizontal-viewport-segments:2),(vertical-viewport-segments:2),(min-width:640px)and (min-height:650px){.page--logs{height:var(--page-content-height, calc(var(--app-vv-height, 100dvh) - 24px))}}.logs-shell{--logs-content-inline: var(--logs-shell-inline, clamp(12px, 3.5vw, 24px));--logs-route-media-column: minmax(0, 1fr);--logs-scrollbar-gutter: 0px;--logs-tab-track-radius: var(--r-sm);--logs-tab-inset: var(--sp-xs);--logs-tab-min-height: 36px;--logs-tab-font-size: var(--fs-label-sm);--logs-tab-font-weight: 700;height:100%;min-height:0;display:flex;flex-direction:column;max-width:1480px;margin:0 auto;overflow:hidden}.logs-tabbar{position:sticky;top:0;z-index:12;display:flex;align-items:center;justify-content:center;gap:var(--sp-md);padding:8px calc(var(--logs-content-inline) + var(--logs-scrollbar-gutter)) 6px var(--logs-content-inline);background:color-mix(in srgb,var(--md-surface) 96%,transparent);border-bottom:0;backdrop-filter:blur(18px)}.logs-tabs{--segmented-control-gap: var(--logs-tab-inset);--segmented-control-padding: var(--logs-tab-inset);--segmented-control-item-min-height: var(--logs-tab-min-height);--segmented-control-item-padding: 0 var(--sp-sm);--segmented-control-item-font-size: var(--logs-tab-font-size);--segmented-control-item-font-weight: var(--logs-tab-font-weight);--segmented-control-item-radius: calc(var(--logs-tab-track-radius) - var(--logs-tab-inset));--segmented-control-item-border: transparent;--segmented-control-item-bg: transparent;--segmented-control-hover-border: transparent;--segmented-control-hover-fg: var(--md-on-surface);--segmented-control-selected-border: transparent;--segmented-control-selected-bg: var(--md-primary);--segmented-control-selected-fg: var(--md-on-primary);width:auto;flex:1 1 auto;border-radius:var(--logs-tab-track-radius);background:var(--md-surface-cont);border:1px solid color-mix(in srgb,var(--md-outline-var) 36%,transparent)}.logs-menu{flex:0 0 auto;align-self:center}.logs-menu__button{width:var(--menu-trigger-size);height:var(--menu-trigger-size);min-height:var(--menu-trigger-size);padding:0}.logs-menu__button .ui-menu-icon{width:var(--menu-icon-size);height:var(--menu-icon-size)}.logs-panel{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;overflow:hidden}.logs-panel[hidden]{display:none}.logs-subheader{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:12px clamp(14px,3.5vw,26px) 8px;background:color-mix(in srgb,var(--md-surface-cont) 55%,transparent);border-bottom:1px solid color-mix(in srgb,var(--md-outline-var) 45%,transparent)}.logs-subheader__title{font-size:15px;line-height:1.25;font-weight:800;color:var(--md-on-surface)}.logs-subheader__desc{margin-top:4px;font-size:13px;line-height:1.35;color:var(--md-on-surface-var);opacity:.82}.dashcam-expand-btn,.dashcam-menu-btn{appearance:none;border:0;padding:0;background:transparent;color:var(--md-on-surface-var);display:inline-flex;align-items:center;justify-content:center;line-height:0;cursor:pointer;box-sizing:border-box}.dashcam-expand-btn:hover,.dashcam-menu-btn:hover{background:color-mix(in srgb,var(--md-primary) 10%,transparent);color:var(--md-on-surface)}.dashcam-expand-btn svg,.dashcam-menu-btn svg{width:22px;height:22px}.dashcam-status{flex:0 0 auto;padding:22px clamp(14px,3.5vw,26px);color:var(--md-on-surface-var);text-align:center;font-size:14px}.dashcam-status.is-error{color:#ffb4ab}.logs-empty-state{flex:0 0 auto;display:grid;justify-items:center;width:fit-content;max-width:min(100%,360px);margin:0 auto;padding:14px 18px;border:1px solid color-mix(in srgb,var(--md-outline-var) 28%,transparent);border-radius:8px;background:color-mix(in srgb,var(--md-surface-cont) 46%,transparent);color:var(--md-on-surface);text-align:center}.logs-empty-state__title{color:var(--md-on-surface);font-size:14px;font-weight:850;line-height:1.25}.dashcam-routes{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain;overflow-anchor:none;scrollbar-gutter:stable;padding:6px var(--logs-content-inline) calc(var(--app-nav-bottom-gap, var(--nav-bar-height)) + 16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:10px}.dashcam-routes:has(.logs-empty-state),.screenrecord-list:has(.logs-empty-state){align-items:center;justify-content:center}.dashcam-virtual-spacer{flex:0 0 auto;width:100%;min-height:0;pointer-events:none}.dashcam-route-card{flex:0 0 auto;overflow:hidden;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 50%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface)}.dashcam-route-media{min-width:0}.dashcam-route-preview{position:relative;aspect-ratio:16 / 9;align-self:start;overflow:hidden;cursor:pointer;background:linear-gradient(180deg,var(--md-surface-cont-hh),var(--md-surface-cont))}.dashcam-route-media-info{display:none;padding:10px 12px 12px;border-top:1px solid color-mix(in srgb,var(--md-outline-var) 30%,transparent);background:color-mix(in srgb,var(--md-surface-cont-h) 92%,var(--md-surface));cursor:pointer}.dashcam-route-preview img,.dashcam-segment-thumb img{width:100%;height:100%;object-fit:cover;display:block}.logs-lazy-img{opacity:1;transition:opacity .16s ease}.logs-lazy-img[data-src]{opacity:0}.dashcam-segment-thumb.is-thumb-loading:after{content:"";position:absolute;inset:0;z-index:2;pointer-events:none;background:linear-gradient(100deg,var(--md-surface-cont-h) 0%,color-mix(in srgb,var(--md-on-surface) 10%,var(--md-surface-cont-h)) 50%,var(--md-surface-cont-h) 100%);background-size:220% 100%;animation:logsThumbSweep 1.25s ease-in-out infinite}@keyframes logsThumbSweep{0%{background-position:220% 0}to{background-position:-120% 0}}@media(prefers-reduced-motion:reduce){.dashcam-segment-thumb.is-thumb-loading:after{animation:none}}.dashcam-route-preview__shade{position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,.08),transparent 45%,rgba(0,0,0,.36));pointer-events:none}.dashcam-route-preview__chips{position:absolute;left:12px;right:12px;bottom:12px;display:flex;flex-wrap:wrap;gap:8px}.dashcam-route-preview__timestamp{position:absolute;top:12px;left:12px;z-index:1;padding:5px 8px;border-radius:7px;background:#0000007a;border:1px solid rgba(255,255,255,.16);color:#fff;font-size:12px;font-weight:700;line-height:1.35;font-variant-numeric:tabular-nums;text-shadow:0 1px 2px rgba(0,0,0,.45);backdrop-filter:blur(8px);pointer-events:none}.dashcam-route-preview .dashcam-route-preview__menu{position:absolute;top:10px;right:10px;z-index:3;width:36px;height:36px;color:#fff;background:#0000007a;border:1px solid rgba(255,255,255,.16);box-shadow:0 3px 12px #0003;backdrop-filter:blur(8px)}.dashcam-route-preview .dashcam-route-preview__menu:hover{background:#000000a3}.dashcam-chip{display:inline-flex;align-items:center;min-height:26px;padding:0 10px;border-radius:999px;background:#0000006b;border:1px solid rgba(255,255,255,.16);color:#fff;font-size:12px;font-weight:750;backdrop-filter:blur(10px)}.dashcam-play-mark{position:absolute;inset:0;display:grid;place-items:center}.dashcam-play-mark svg{width:54px;height:54px;padding:11px;border-radius:999px;color:#fff;background:#0000005c;border:1px solid rgba(255,255,255,.18);box-shadow:0 8px 24px #00000042}.dashcam-route-main{min-width:0;padding:12px 14px}.dashcam-route-head{display:flex;align-items:flex-start;gap:10px;cursor:pointer;border-radius:6px}.dashcam-route-head:hover{background:color-mix(in srgb,var(--md-primary) 7%,transparent)}.dashcam-route-titleblock{min-width:0;flex:1}.dashcam-route-title{font-size:16px;line-height:1.25;font-weight:850;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashcam-route-subtitle{margin-top:4px;font-size:12.5px;color:var(--md-on-surface-var);opacity:.78;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashcam-expand-btn{width:34px;height:34px;border-radius:999px;flex:0 0 auto;margin-right:8px}.dashcam-segments{margin-top:12px}.dashcam-segments.is-collapsed{display:none}.dashcam-selection-row{display:flex;align-items:center;gap:8px;overflow-x:auto;padding-bottom:8px;color:var(--md-on-surface-var);font-size:13px;font-weight:750}.dashcam-selection-count{flex:0 0 auto;color:var(--md-on-surface-var)}.dashcam-selection-row .smallBtn{white-space:nowrap;min-height:34px;padding-inline:12px;border-radius:var(--r-sm);font-size:12.5px;font-weight:800}.dashcam-segment-list{max-height:min(40vh,330px);overflow:auto;overscroll-behavior:contain;overflow-anchor:none;-webkit-overflow-scrolling:touch;touch-action:pan-y;scrollbar-width:none;display:flex;flex-direction:column;gap:8px}.dashcam-segment-list::-webkit-scrollbar{display:none}.dashcam-segment-loader{flex:0 0 auto;width:100%;height:22px;position:relative;overflow:hidden;overflow-anchor:none;border-radius:999px;background:transparent;transition:background-color .16s ease-out}.dashcam-segment-loader.is-loading{background:color-mix(in srgb,var(--md-outline-var) 18%,transparent)}.dashcam-segment-loader:before{content:"";position:absolute;inset:8px 20%;border-radius:inherit;background:var(--md-primary);opacity:0;transition:opacity .16s ease-out}.dashcam-segment-loader.is-loading:before{opacity:1;animation:dashcam-segment-loader .82s ease-in-out infinite}.dashcam-segment-tile{flex:0 0 auto;min-height:70px;display:flex;align-items:center;gap:10px;padding:8px;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 42%,transparent);background:var(--md-surface-cont);cursor:pointer;content-visibility:auto;contain-intrinsic-size:70px 100%}@media(hover:hover){.dashcam-segment-tile:hover{border-color:color-mix(in srgb,var(--md-primary) 44%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 84%,var(--md-primary))}}.dashcam-segment-tile--append{animation:dashcam-segment-append .18s cubic-bezier(.2,0,0,1) both}@keyframes dashcam-segment-append{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@keyframes dashcam-segment-loader{0%{transform:translate(-160%)}to{transform:translate(160%)}}.dashcam-segment-thumb{position:relative;width:78px;height:54px;flex:0 0 78px;overflow:hidden;border-radius:6px;background:var(--md-surface-cont-hh)}.dashcam-segment-check{position:absolute;inset:0;display:grid;place-items:center;background:#0000001f}.dashcam-segment-check input{width:20px;height:20px;accent-color:var(--md-primary)}.dashcam-segment-inline-check{width:28px;height:28px;flex:0 0 28px;display:grid;place-items:center;border-radius:6px}.dashcam-segment-inline-check input{width:18px;height:18px;accent-color:var(--md-primary)}.dashcam-segment-body{min-width:0;flex:1}.dashcam-segment-topline{min-width:0;display:flex;align-items:center;gap:var(--sp-xs)}.dashcam-segment-badge{min-width:0;display:inline-flex;align-items:center;min-height:22px;padding:0 8px;border-radius:999px;border:1px solid color-mix(in srgb,var(--md-outline-var) 40%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface-var);font-size:11px;font-weight:850;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dashcam-segment-read-status{min-width:0;display:inline-flex;flex:0 0 auto;margin-left:auto}.dashcam-segment-read-status[hidden]{display:none}.dashcam-segment-name{margin-top:6px;font-size:13px;font-weight:650;color:var(--md-on-surface);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashcam-menu-btn{width:30px;height:30px;border-radius:999px;flex:0 0 auto}.dashcam-menu-btn svg{display:block;transform:translateZ(0)}.dashcam-group-menu-btn{margin-left:auto;flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;padding-inline:12px}.dashcam-group-menu-btn svg{width:18px;height:18px}.dashcam-group-menu-btn--row{display:inline-flex;margin-left:auto}.logs-loading-list{display:flex;flex-direction:column;gap:10px;pointer-events:none}.logs-loading-card,.logs-loading-row{position:relative;overflow:hidden;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 36%,transparent);background:var(--md-surface-cont-h);opacity:0;transform:translateY(12px);animation:uiStaggerIn .38s cubic-bezier(.2,0,0,1) both;animation-delay:min(calc(var(--i, 0) * 36ms),.42s)}.logs-loading-card{min-height:184px}.logs-loading-row{min-height:64px}.logs-loading-card:before,.logs-loading-row:before{content:"";position:absolute;inset:0;background:linear-gradient(100deg,transparent 0%,color-mix(in srgb,var(--md-on-surface) 7%,transparent) 48%,transparent 72%);transform:translate(-100%);animation:logsSkeletonSweep 1.35s ease-in-out infinite;animation-delay:calc(var(--i, 0) * 90ms);z-index:1}.logs-loading-card:after,.logs-loading-row:after{content:"";position:absolute;inset:12px;border-radius:6px;background:linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 0 0 / 100% 104px no-repeat,linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 0 122px / 62% 14px no-repeat,linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 0 146px / 38% 12px no-repeat;z-index:0}.logs-loading-row:after{inset:8px 10px;background:linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 0 0 / 76px 48px no-repeat,linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 90px 7px / 52% 14px no-repeat,linear-gradient(var(--md-surface-cont),var(--md-surface-cont)) 90px 31px / 34% 11px no-repeat}@keyframes logsSkeletonSweep{to{transform:translate(100%)}}@media(prefers-reduced-motion:reduce){.logs-loading-card,.logs-loading-row{animation:none;opacity:1;transform:none}.dashcam-segment-tile--append{animation:none}.dashcam-segment-loader.is-loading:before{animation:none}.logs-loading-card:before,.logs-loading-row:before{animation:none;opacity:0}}.dashcam-player-overlay{position:fixed;inset:0;z-index:calc(var(--z-toast) - 1);display:grid;place-items:center;padding:16px;background:#000000ad;opacity:0;transition:opacity .16s ease}.dashcam-player-overlay.is-open{opacity:1}.dashcam-player-overlay.is-menu-open,.dashcam-player-overlay.is-action-open{z-index:calc(var(--z-modal) - 1)}.dashcam-player-dialog{width:auto;max-width:min(92vw,960px);max-height:min(78vh,680px)}.dashcam-player-frame{position:relative;overflow:hidden;border-radius:8px;border:1px solid rgba(255,255,255,.16);background:#000;box-shadow:0 20px 60px #00000070}.dashcam-player-video{width:auto;height:auto;max-width:min(92vw,960px);max-height:min(78vh,680px);display:block;background:#000;object-fit:contain}.dashcam-player-frame .plyr{width:auto;max-width:min(92vw,960px);max-height:min(78vh,680px);background:#000;--plyr-color-main: var(--md-primary)}.dashcam-player-frame .plyr video{max-width:min(92vw,960px);max-height:min(78vh,680px);object-fit:contain;background:#000}.dashcam-player-frame .plyr__controls{z-index:5}.dashcam-player-frame .plyr__control--overlaid{display:none!important}.dashcam-player-transport{z-index:4;pointer-events:auto}.dashcam-player-frame .plyr:-webkit-full-screen,.dashcam-player-frame .plyr:fullscreen,.dashcam-player-frame .plyr.plyr--fullscreen-active{width:100vw!important;height:100vh!important;max-width:none;max-height:none;display:grid;place-items:center;background:#000}.dashcam-player-frame .plyr:-webkit-full-screen video,.dashcam-player-frame .plyr:fullscreen video,.dashcam-player-frame .plyr.plyr--fullscreen-active video{width:100vw!important;height:100vh!important;max-width:none;max-height:none;object-fit:contain;object-position:center center}.dashcam-player-frame .plyr__video-wrapper{display:grid;place-items:center;background:#000}.dashcam-player-frame .plyr:-webkit-full-screen .plyr__video-wrapper,.dashcam-player-frame .plyr:fullscreen .plyr__video-wrapper,.dashcam-player-frame .plyr.plyr--fullscreen-active .plyr__video-wrapper{width:100vw;height:100vh}.dashcam-player-frame .plyr__control svg{display:block;margin:auto}.dashcam-player-overlay--dashcam .dashcam-player-dialog:not(.dashcam-player-dialog--browsable){width:min(84vw,760px);max-width:min(96vw,920px);max-height:min(84vh,760px)}.dashcam-player-overlay--dashcam .dashcam-player-video{width:100%;height:auto;max-width:none;max-height:min(84vh,760px)}.dashcam-player-overlay--dashcam .dashcam-player-frame .plyr,.dashcam-player-overlay--dashcam .dashcam-player-frame .plyr video{width:100%;max-width:none;max-height:min(84vh,760px)}.dashcam-player-top{position:absolute;left:0;right:0;top:0;z-index:3;display:flex;align-items:center;gap:8px;padding:10px;background:linear-gradient(180deg,rgba(0,0,0,.62),transparent);pointer-events:none;transition:opacity .2s ease,transform .2s ease}.dashcam-player-toast{display:none;position:absolute;top:50%;left:50%;z-index:5;padding:12px 22px;border-radius:999px;background:#000000bd;color:#fff;font-weight:700;font-size:18px;letter-spacing:.02em;white-space:nowrap;pointer-events:none;opacity:0;transform:translate(-50%,-50%) scale(.92);transition:opacity .14s ease,transform .14s ease;backdrop-filter:blur(8px);box-shadow:0 10px 28px #0000005c}.dashcam-player-toast.is-visible{opacity:1;transform:translate(-50%,-50%) scale(1)}.dashcam-player-title{min-width:0;flex:1;color:#fff;font-weight:800;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dashcam-player-heading{min-width:0;flex:1}.dashcam-player-subtitle{margin-top:2px;color:#fffc;font-size:11px;font-weight:650;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-variant-numeric:tabular-nums}.dashcam-player-close{pointer-events:auto}.dashcam-player-action{width:var(--close-size, 32px);height:var(--close-size, 32px);border:1px solid color-mix(in srgb,var(--md-outline-var) 40%,transparent);border-radius:var(--close-radius, 10px);color:#fff;background:color-mix(in srgb,var(--md-surface-cont) 82%,#000);pointer-events:auto;transition:background var(--motion-quick) var(--ease-standard),border-color var(--motion-quick) var(--ease-standard),color var(--motion-quick) var(--ease-standard)}.dashcam-player-action svg{width:var(--close-icon, 16px);height:var(--close-icon, 16px)}.dashcam-player-action:hover{background:color-mix(in srgb,var(--md-primary) 14%,var(--md-surface-cont));border-color:color-mix(in srgb,var(--md-primary) 32%,var(--md-outline-var));color:#fff}.dashcam-player-action:disabled{opacity:.55;cursor:default}.screenrecord-list{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable;padding:10px var(--logs-content-inline) calc(var(--app-nav-bottom-gap, var(--nav-bar-height)) + 16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:8px}.dashcam-routes.is-loading-more:after,.screenrecord-list.is-loading-more:after{content:"";flex:0 0 auto;width:22px;height:22px;margin:10px auto 2px;border:2px solid color-mix(in srgb,var(--md-outline-var) 48%,transparent);border-top-color:var(--md-primary);border-radius:999px;animation:screenrecord-loading-spin .78s linear infinite}@keyframes screenrecord-loading-spin{to{transform:rotate(360deg)}}.screenrecord-row{flex:0 0 auto;min-height:64px;display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:8px;border:1px solid color-mix(in srgb,var(--md-outline-var) 44%,transparent);background:var(--md-surface-cont-h);color:var(--md-on-surface);cursor:pointer;content-visibility:auto;contain-intrinsic-size:auto 64px}.screenrecord-list .screenrecord-row.ui-stagger-item{animation:none}.screenrecord-virtual-spacer{flex:0 0 auto;width:100%;min-height:0;pointer-events:none}.screenrecord-row:hover{border-color:color-mix(in srgb,var(--md-primary) 38%,var(--md-outline-var));background:color-mix(in srgb,var(--md-surface-cont-h) 88%,var(--md-primary))}.screenrecord-row__icon{width:38px;height:38px;flex:0 0 38px;display:grid;place-items:center;border-radius:8px;background:color-mix(in srgb,var(--md-primary) 12%,var(--md-surface-cont));color:var(--md-primary);overflow:hidden}.screenrecord-row__thumb{width:76px;height:48px;flex:0 0 76px;display:block;border-radius:6px;background:var(--md-surface-cont);overflow:hidden}.screenrecord-row__thumb img{width:100%;height:100%;object-fit:cover;display:block}.screenrecord-row__icon svg,.screenrecord-download svg{width:21px;height:21px}.screenrecord-row__main{min-width:0;flex:1 1 auto}.screenrecord-row__name{font-size:14px;line-height:1.25;font-weight:750;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.screenrecord-row__meta{margin-top:6px;display:flex;flex-wrap:wrap;gap:6px 10px;color:var(--md-on-surface-var);font-size:12px;line-height:1.25}.screenrecord-download{appearance:none;border:0;width:36px;height:36px;flex:0 0 36px;display:grid;place-items:center;border-radius:8px;color:var(--md-on-surface-var);background:transparent;cursor:pointer}.screenrecord-download:hover{color:var(--md-primary);background:color-mix(in srgb,var(--md-primary) 10%,transparent)}@media(min-width:900px){.logs-shell{--logs-content-inline: clamp(18px, 3vw, 34px);--logs-route-media-column: minmax(260px, 34%)}.dashcam-route-card{display:grid;grid-template-columns:var(--logs-route-media-column) minmax(0,1fr);align-items:start}.dashcam-route-preview{width:100%}.dashcam-route-media-info{display:block}.dashcam-route-main{padding:10px 14px 12px}.dashcam-route-head{justify-content:flex-end;min-height:34px}.dashcam-route-head .dashcam-route-titleblock{display:none}}@media(min-aspect-ratio:13/10),(horizontal-viewport-segments:2),(vertical-viewport-segments:2),(min-width:640px)and (min-height:650px){.logs-shell{--logs-route-media-column: minmax(230px, 32%)}.dashcam-route-card{display:grid;grid-template-columns:var(--logs-route-media-column) minmax(0,1fr);align-items:stretch;height:clamp(178px,45vh,236px)}.dashcam-route-media{min-height:0;height:100%;display:flex;flex-direction:column}.dashcam-route-preview{flex:1 1 auto;min-height:0;width:100%;aspect-ratio:auto}.dashcam-route-media-info{display:block;flex:0 0 auto;min-height:0;overflow:hidden}.dashcam-route-main{min-height:0;height:100%;display:flex;flex-direction:column;padding-top:8px;padding-right:0}.dashcam-route-head{display:none}.dashcam-segments,.dashcam-segments.is-collapsed{min-height:0;flex:1 1 auto;display:grid;grid-template-columns:minmax(0,1fr) 96px;gap:8px;margin-top:0}.dashcam-selection-row{grid-column:2;grid-row:1;min-height:0;height:100%;flex-direction:column;justify-content:center;align-items:center;gap:6px;box-sizing:border-box;overflow-y:auto;padding:8px;border-left:1px solid color-mix(in srgb,var(--md-outline-var) 38%,transparent);scrollbar-width:none;text-align:center}.dashcam-selection-row::-webkit-scrollbar{display:none}.dashcam-selection-row .dashcam-group-menu-btn--row{margin-left:0}.dashcam-segment-list{grid-column:1;grid-row:1;flex:1 1 auto;min-height:0;max-height:none}.dashcam-selection-count{text-align:center;font-size:12px}.dashcam-selection-row .smallBtn{width:100%;min-height:30px;padding-inline:8px;font-size:12px;white-space:normal;line-height:1.15;overflow-wrap:anywhere}.dashcam-selection-row .btn--filled{padding-inline:8px}.dashcam-segment-tile--compact{min-height:46px;gap:8px;padding:5px 8px 5px 5px;contain-intrinsic-size:46px 100%}.dashcam-segment-thumb--compact{width:56px;height:36px;flex:0 0 56px}.dashcam-segment-check--compact input{width:16px;height:16px}.dashcam-segment-tile--compact .dashcam-segment-body{min-width:0;display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:8px}.dashcam-segment-tile--compact .dashcam-segment-topline{display:contents}.dashcam-segment-tile--compact .dashcam-segment-badge{grid-column:1;grid-row:1;min-height:20px;padding-inline:7px;font-size:10.5px}.dashcam-segment-tile--compact .dashcam-segment-name{min-width:0;grid-column:2;grid-row:1;margin-top:0;font-size:12.5px}.dashcam-segment-tile--compact .dashcam-segment-read-status{grid-column:3;grid-row:1;margin-left:0}.dashcam-segment-tile--compact .dashcam-menu-btn{width:26px;height:26px}}@media(max-height:620px)and (min-aspect-ratio:13/10),(max-height:620px)and (horizontal-viewport-segments:2),(max-height:620px)and (vertical-viewport-segments:2),(min-width:640px)and (min-height:650px)and (max-height:620px){.logs-tabbar{padding-top:6px;padding-bottom:4px;--logs-tab-min-height: 32px;--logs-tab-font-size: 12.5px}.logs-menu__button{min-height:38px}.dashcam-routes,.screenrecord-list{gap:8px;padding-top:4px}.dashcam-route-card{display:grid;grid-template-columns:var(--logs-route-media-column) minmax(0,1fr);align-items:stretch;height:clamp(168px,46vh,196px)}.dashcam-route-media-info{display:block;padding:8px 10px 10px}.dashcam-route-title{font-size:14px}.dashcam-route-subtitle{font-size:11.5px}.dashcam-selection-row{padding:6px 6px 6px 8px}.dashcam-segments,.dashcam-segments.is-collapsed{grid-template-columns:minmax(0,1fr) 86px}.dashcam-segment-list{max-height:none}.dashcam-player-dialog{width:auto;max-width:min(76vw,760px);max-height:68vh}.dashcam-player-video,.dashcam-player-frame .plyr,.dashcam-player-frame .plyr video{max-width:min(76vw,760px);max-height:68vh}.dashcam-player-title{font-size:12.5px}}.dashcam-player-dialog,.dashcam-segment-read-status{--logs-player-status-reading-surface: var(--md-primary);--logs-player-status-reading-ink: var(--md-on-primary);--logs-player-status-recent-surface: var(--md-primary-state-soft);--logs-player-status-recent-ink: var(--md-primary);--logs-player-status-recent-border: color-mix(in srgb, var(--md-primary) 36%, transparent)}.dashcam-player-dialog{--logs-player-dialog-radius: var(--r-md);--logs-player-dialog-border: color-mix(in srgb, var(--md-outline-var) 42%, transparent);--logs-player-dialog-surface: var(--md-surface-cont);--logs-player-dialog-wide: 88rem;--logs-player-dialog-max-height: 40rem;--logs-player-dialog-landscape-aspect: 12 / 5;--logs-player-dialog-portrait-wide: 42rem;--logs-player-dialog-portrait-max-height: 42rem;--logs-player-dialog-portrait-viewport-height: 80dvh;--logs-player-browser-pane-width: clamp(15.5rem, 22dvw, 19.5rem);--logs-player-browser-pane-compact-width: clamp(15rem, 31dvw, 16.5rem);--logs-player-browser-divider: color-mix(in srgb, var(--md-outline-var) 34%, transparent);--logs-player-browser-surface: var(--md-surface-cont);--logs-player-section-padding-inline: var(--sp-md);--logs-player-section-padding-block: var(--sp-sm);--logs-player-summary-gap: var(--sp-sm);--logs-player-summary-count-surface: var(--md-primary-state-soft);--logs-player-summary-count-ink: var(--md-primary);--logs-player-summary-muted: color-mix(in srgb, var(--md-on-surface-var) 72%, transparent);--logs-player-segment-list-gap: var(--sp-xs);--logs-player-segment-list-padding: var(--sp-sm);--logs-player-segment-item-min-height: 4.5rem;--logs-player-segment-item-padding: var(--sp-xs);--logs-player-segment-item-radius: var(--r-sm);--logs-player-segment-item-surface: color-mix(in srgb, var(--md-on-surface) 3%, transparent);--logs-player-segment-item-hover: color-mix(in srgb, var(--md-on-surface) 7%, transparent);--logs-player-segment-item-active: var(--md-primary-state-soft);--logs-player-segment-item-border: color-mix(in srgb, var(--md-outline-var) 28%, transparent);--logs-player-segment-item-active-border: color-mix(in srgb, var(--md-primary) 62%, var(--md-outline-var));--logs-player-segment-thumb-width: 5.5rem;--logs-player-segment-thumb-radius: var(--r-xs);--logs-player-segment-thumb-surface: var(--md-surface-cont-h);--logs-player-segment-muted: color-mix(in srgb, var(--md-on-surface-var) 70%, transparent)}.dashcam-player-layout{width:100%;min-width:0;min-height:0}.dashcam-player-dialog--browsable{--logs-player-browser-current-width: var(--logs-player-browser-pane-width);width:min(calc(var(--app-vv-width, 100dvw) - var(--sp-lg) - var(--sp-lg) - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px)),var(--logs-player-dialog-wide));height:auto;max-height:min(calc(var(--app-vv-height, 100dvh) - var(--sp-lg) - var(--sp-lg) - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px)),var(--logs-player-dialog-max-height));max-width:none;aspect-ratio:var(--logs-player-dialog-landscape-aspect);overflow:hidden;border:1px solid var(--logs-player-dialog-border);border-radius:var(--logs-player-dialog-radius);background:var(--logs-player-dialog-surface);container-name:logs-player-dialog;container-type:inline-size}.dashcam-player-dialog--browsable .dashcam-player-layout{height:100%;display:grid;grid-template-columns:minmax(0,1fr) var(--logs-player-browser-current-width)}.dashcam-player-dialog--browsable .dashcam-player-frame{min-width:0;min-height:0;height:100%;border:0;border-radius:0;box-shadow:none}.dashcam-player-dialog--browsable .dashcam-player-video,.dashcam-player-dialog--browsable .dashcam-player-frame .plyr,.dashcam-player-dialog--browsable .dashcam-player-frame .plyr video{width:100%;height:100%;max-width:none;max-height:none;object-fit:contain}.dashcam-player-browser{min-width:0;min-height:0;display:grid;grid-template-rows:auto minmax(0,1fr);overflow:hidden;border-left:1px solid var(--logs-player-browser-divider);background:var(--logs-player-browser-surface)}.dashcam-player-group-summary{min-width:0;display:grid;gap:var(--logs-player-summary-gap);padding:var(--logs-player-section-padding-block) var(--logs-player-section-padding-inline) var(--sp-md)}.dashcam-player-section-title{margin:0;color:var(--md-on-surface);font-size:var(--fs-title-xs);font-weight:850;line-height:1.3}.dashcam-player-group-summary__body{min-width:0;display:grid;gap:var(--sp-xs)}.dashcam-player-group-summary__main{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:var(--sp-sm)}.dashcam-player-group-summary__title{min-width:0;overflow:hidden;color:var(--md-on-surface);font-size:var(--fs-body-sm);font-weight:850;line-height:1.3;text-overflow:ellipsis;white-space:nowrap}.dashcam-player-group-summary__count{flex:0 0 auto;padding:var(--sp-xs) var(--sp-sm);border-radius:var(--r-round);background:var(--logs-player-summary-count-surface);color:var(--logs-player-summary-count-ink);font-size:var(--fs-label-sm);font-weight:850;line-height:1.2;white-space:nowrap}.dashcam-player-group-summary__time{min-width:0;overflow:hidden;color:var(--logs-player-summary-muted);font-size:var(--fs-label-sm);font-variant-numeric:tabular-nums;font-weight:700;line-height:1.35;text-overflow:ellipsis;white-space:nowrap}.dashcam-player-segment-region[hidden]{display:none!important}.dashcam-player-segment-region{min-width:0;min-height:0;overflow:hidden;border-top:1px solid var(--logs-player-browser-divider)}.dashcam-player-segment-browser{min-width:0;min-height:0;height:100%;display:grid;grid-template-rows:auto minmax(0,1fr)}.dashcam-player-segment-browser>.dashcam-player-section-title{padding:var(--logs-player-section-padding-block) var(--logs-player-section-padding-inline)}.dashcam-player-segment-list{min-width:0;min-height:0;display:grid;grid-auto-rows:max-content;align-content:start;gap:var(--logs-player-segment-list-gap);overflow-x:hidden;overflow-y:auto;overscroll-behavior:contain;padding:0 var(--logs-player-segment-list-padding) var(--logs-player-segment-list-padding);scrollbar-width:thin}.dashcam-player-segment-item{width:100%;min-width:0;min-height:var(--logs-player-segment-item-min-height);display:grid;grid-template-columns:var(--logs-player-segment-thumb-width) minmax(0,1fr);align-items:center;gap:var(--sp-sm);padding:var(--logs-player-segment-item-padding);border:1px solid var(--logs-player-segment-item-border);border-radius:var(--logs-player-segment-item-radius);background:var(--logs-player-segment-item-surface);color:var(--md-on-surface);font:inherit;text-align:left;cursor:pointer;transition:border-color var(--motion-quick) var(--ease-standard),background var(--motion-quick) var(--ease-standard)}.dashcam-player-segment-item:hover{background:var(--logs-player-segment-item-hover)}.dashcam-player-segment-item:focus-visible{border-color:var(--logs-player-segment-item-active-border);outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:var(--focus-ring-offset)}.dashcam-player-segment-item.is-active{border-color:var(--logs-player-segment-item-active-border);background:var(--logs-player-segment-item-active)}.dashcam-player-segment-thumb{position:relative;width:100%;overflow:hidden;border-radius:var(--logs-player-segment-thumb-radius);aspect-ratio:16 / 9;background:var(--logs-player-segment-thumb-surface)}.dashcam-player-segment-thumb img{width:100%;height:100%;display:block;object-fit:cover}.dashcam-player-segment-copy{min-width:0;display:grid;gap:var(--sp-xs)}.dashcam-player-segment-meta{min-width:0;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-xs)}.dashcam-player-segment-status{min-width:0;display:inline-flex;flex:0 0 auto}.dashcam-player-segment-status[hidden]{display:none}.dashcam-player-segment-time,.dashcam-player-segment-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dashcam-player-segment-time{color:var(--md-on-surface);font-size:var(--fs-body-sm);font-variant-numeric:tabular-nums;font-weight:850;line-height:1.3}.dashcam-player-segment-name{color:var(--logs-player-segment-muted);font-size:var(--fs-label-sm);font-weight:700;line-height:1.3}.dashcam-player-status-tag{min-width:0;display:inline-flex;align-items:center;justify-content:center;padding:var(--sp-xs) var(--sp-sm);border:1px solid transparent;border-radius:var(--r-round);font-size:var(--fs-label-sm);font-weight:850;line-height:1.2;white-space:nowrap}.dashcam-player-status-tag[data-state=reading]{background:var(--logs-player-status-reading-surface);color:var(--logs-player-status-reading-ink)}.dashcam-player-status-tag[data-state=recent]{border-color:var(--logs-player-status-recent-border);background:var(--logs-player-status-recent-surface);color:var(--logs-player-status-recent-ink)}@container logs-player-dialog (max-width: 46rem){.dashcam-player-layout{--logs-player-segment-thumb-width: 4.75rem}}@media(orientation:portrait){.dashcam-player-dialog--browsable{width:min(calc(var(--app-vv-width, 100dvw) - var(--sp-md) - var(--sp-md) - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px)),var(--logs-player-dialog-portrait-wide));height:min(calc(var(--app-vv-height, 100dvh) - var(--sp-lg) - var(--sp-lg) - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px)),var(--logs-player-dialog-portrait-viewport-height),var(--logs-player-dialog-portrait-max-height));max-height:none;aspect-ratio:auto}.dashcam-player-dialog--browsable .dashcam-player-layout{grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr)}.dashcam-player-dialog--browsable .dashcam-player-frame{height:auto;aspect-ratio:16 / 9}.dashcam-player-browser{border-top:1px solid var(--logs-player-browser-divider);border-left:0}}@media(orientation:landscape)and (max-height:30rem){.dashcam-player-dialog--browsable{--logs-player-browser-current-width: var(--logs-player-browser-pane-compact-width);width:min(calc(var(--app-vv-width, 100dvw) - var(--sp-md) - var(--sp-md) - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px)),var(--logs-player-dialog-wide));max-height:calc(var(--app-vv-height, 100dvh) - var(--sp-md) - var(--sp-md) - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px))}}@media(prefers-reduced-motion:reduce){.dashcam-player-dialog--browsable,.dashcam-player-dialog--browsable *{transition:none!important}}.route-summary,.route-summary-state{--route-summary-gap: var(--sp-md);--route-summary-section-gap: var(--sp-sm);--route-summary-radius: var(--r-md);--route-summary-radius-small: var(--r-sm);--route-summary-border: color-mix(in srgb, var(--md-outline-var) 46%, transparent);--route-summary-divider: color-mix(in srgb, var(--md-outline-var) 30%, transparent);--route-summary-surface: var(--md-surface-cont-l);--route-summary-surface-raised: var(--md-surface-cont-h);--route-summary-text: var(--md-on-surface);--route-summary-muted: color-mix(in srgb, var(--md-on-surface-var) 76%, transparent);--route-summary-accent: var(--md-primary);--route-summary-accent-soft: var(--md-primary-state-soft);--route-summary-auto: var(--md-info);--route-summary-manual: var(--md-error);--route-summary-manual-soft: color-mix(in srgb, var(--md-error) 35%, transparent);--route-summary-gas: var(--md-warning);--route-summary-brake: var(--md-primary);--route-summary-event-over: var(--md-warning);--route-summary-event-over-soft: var(--md-warning-cont);--route-summary-event-hard: var(--md-error);--route-summary-event-hard-soft: color-mix(in srgb, var(--md-error-cont) 24%, var(--md-surface-cont-h));--route-summary-danger: var(--md-error);--route-summary-danger-soft: color-mix(in srgb, var(--md-error-cont) 24%, var(--md-surface-cont-h));--route-summary-progress-track: var(--md-surface-cont-h);--route-summary-progress-fill: var(--md-primary);--route-summary-progress-height: var(--sp-xs);--route-summary-progress-motion: var(--motion-medium);--route-summary-progress-cycle: calc(var(--motion-long) + var(--motion-long) + var(--motion-long));--route-summary-distribution-height: var(--sp-lg)}.route-summary,.route-summary-state{box-sizing:border-box;width:100%;color:var(--route-summary-text);font-family:var(--font-sans);text-align:left;white-space:normal}.route-summary{display:grid;gap:var(--route-summary-gap);container-name:route-summary;container-type:inline-size}#appDialog:has(.route-summary) .app-dialog__sheet{width:min(100%,var(--content-max-width))}.route-summary-hero__subtitle{display:grid;justify-items:start;gap:1px;margin:0;color:var(--route-summary-muted);font:700 var(--fs-label-sm)/1.45 var(--font-sans);font-variant-numeric:tabular-nums}.route-summary-hero__date,.route-summary-hero__range{white-space:nowrap}.route-summary__section{min-width:0;display:grid;align-content:start;gap:var(--route-summary-section-gap);padding:var(--sp-md);border:1px solid var(--route-summary-border);border-radius:var(--route-summary-radius);background:var(--route-summary-surface)}.route-summary__section-title{margin:0;color:var(--route-summary-text);font-size:var(--fs-title-xs);font-weight:800;line-height:1.35}.route-summary__dashboard{min-width:0;display:grid;align-items:start;gap:var(--route-summary-gap)}.route-summary-hero{min-width:0;display:grid;gap:var(--sp-md)}.route-summary-hero__time{min-width:0;display:grid;align-content:start;gap:var(--sp-xs)}.route-summary__metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:start;gap:var(--sp-md);margin:0}.route-summary__metrics--distance{padding-top:var(--sp-xs)}.route-summary-metric{min-width:0;display:grid;gap:var(--sp-xs)}.route-summary-metric__label,.route-summary-detail__label{color:var(--route-summary-muted);font-size:var(--fs-label-sm);font-weight:700;line-height:1.35}.route-summary-metric__value{min-width:0;display:flex;align-items:baseline;gap:var(--sp-xs);margin:0;font-variant-numeric:tabular-nums}.route-summary-metric__number{overflow:hidden;font-size:var(--fs-title-md);font-weight:850;line-height:1.2;text-overflow:ellipsis;white-space:nowrap}.route-summary-metric[data-variant=hero] .route-summary-metric__number{color:var(--route-summary-accent);font-size:clamp(var(--fs-title-lg),5cqi,var(--fs-headline))}.route-summary-metric[data-variant=compact] .route-summary-metric__number{font-size:var(--fs-title-sm)}.route-summary-metric__unit{color:var(--route-summary-muted);font-size:var(--fs-label-sm);font-weight:700}.route-summary-events,.route-summary-details{display:grid;gap:0;overflow:hidden;border:0;border-radius:var(--route-summary-radius-small);background:transparent}.route-summary-event,.route-summary-detail{min-width:0;border-top:1px solid var(--route-summary-divider);background:transparent}.route-summary-event:first-child,.route-summary-detail:first-child{border-top:0}.route-summary-composition{display:grid;gap:var(--sp-xs) var(--sp-md)}.route-summary-composition__stat{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:var(--sp-xs);align-items:baseline}.route-summary-composition__label,.route-summary-event__label{min-width:0;overflow:hidden;font-size:var(--fs-body-sm);font-weight:750;line-height:1.4;text-overflow:ellipsis;white-space:nowrap}.route-summary-composition__label{display:inline-flex;align-items:center;gap:var(--sp-xs)}.route-summary-composition__label:before{width:.55em;height:.55em;flex:0 0 auto;border-radius:var(--r-round);background:currentColor;content:""}.route-summary-composition__duration,.route-summary-composition__ratio{color:var(--route-summary-muted);font:750 var(--fs-label-sm)/1.35 var(--font-mono);font-variant-numeric:tabular-nums}.route-summary-composition__stat[data-kind=auto]{color:var(--route-summary-auto)}.route-summary-composition__stat[data-kind=manual]{color:var(--route-summary-manual)}.route-summary-composition__stat[data-kind=gas]{color:var(--route-summary-gas)}.route-summary-composition__stat[data-kind=brake]{color:var(--route-summary-brake)}.route-summary-composition__stat[data-kind] .route-summary-composition__duration,.route-summary-composition__stat[data-kind] .route-summary-composition__ratio{color:inherit}.route-summary-distribution{display:flex;width:100%;height:var(--route-summary-distribution-height);overflow:hidden;border:1px solid var(--route-summary-border);border-radius:var(--r-sm);background:var(--route-summary-surface-raised)}.route-summary-distribution__segment,.route-summary-distribution__manual{display:block;flex:var(--route-summary-weight, 0) 1 0%;height:100%;box-sizing:border-box}.route-summary-distribution__manual{min-width:0;display:flex;overflow:hidden;border:2px solid var(--route-summary-manual);background:var(--route-summary-manual-soft)}.route-summary-distribution__manual>*+*{border-left:1px solid var(--route-summary-surface)}.route-summary-distribution__segment[data-kind=auto]{background:var(--route-summary-auto)}.route-summary-distribution__segment[data-kind=manual]{background:var(--route-summary-manual)}.route-summary-distribution__segment[data-kind=gas]{background:var(--route-summary-gas)}.route-summary-distribution__segment[data-kind=brake]{background:var(--route-summary-brake)}.route-summary-distribution__segment[data-kind=other]{background:var(--route-summary-manual-soft)}.route-summary-event{display:grid;gap:var(--sp-sm);padding:var(--sp-sm) var(--sp-md)}.route-summary-event[data-tone=danger]{background:var(--route-summary-danger-soft)}.route-summary-event__head{display:flex;align-items:center;gap:var(--sp-sm)}.route-summary-event__count{min-width:2.25em;margin-left:auto;padding:2px var(--sp-sm);border-radius:var(--r-round);background:var(--route-summary-surface-raised);color:var(--route-summary-text);font:800 var(--fs-label-sm)/1.4 var(--font-mono);text-align:center}.route-summary-event[data-tone=danger] .route-summary-event__count{background:var(--route-summary-danger);color:var(--md-on-error)}.route-summary-event__times{display:flex;flex-wrap:wrap;gap:var(--sp-xs);margin:0;padding:0;list-style:none}.route-summary-event__time{display:inline-flex;align-items:baseline;gap:var(--sp-xs);padding:3px var(--sp-sm);border:1px solid color-mix(in srgb,var(--route-summary-event-over) 42%,transparent);border-radius:var(--r-sm);background:var(--route-summary-event-over-soft);color:var(--route-summary-event-over);font:700 var(--fs-label-sm)/1.35 var(--font-mono);font-variant-numeric:tabular-nums}.route-summary-event[data-tone=danger] .route-summary-event__time{border-color:color-mix(in srgb,var(--route-summary-event-hard) 46%,transparent);background:var(--route-summary-event-hard-soft);color:var(--route-summary-event-hard)}.route-summary-event__peak{color:inherit;font-weight:850}.route-summary-detail{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:var(--sp-md);align-items:baseline;padding:var(--sp-sm) var(--sp-md)}.route-summary-detail__value{margin:0;font:800 var(--fs-body-sm)/1.4 var(--font-mono);font-variant-numeric:tabular-nums;text-align:right}.route-summary__warning{margin:0;padding:var(--sp-sm) var(--sp-md);border-radius:var(--route-summary-radius-small);background:var(--route-summary-danger-soft);color:var(--route-summary-text);font-size:var(--fs-label-md);font-weight:700;line-height:1.45}.route-summary-metadata{overflow:hidden;border:1px solid var(--route-summary-border);border-radius:var(--route-summary-radius);background:var(--route-summary-surface)}.route-summary-metadata__summary{min-height:var(--control-min-height);display:flex;align-items:center;padding-inline:var(--sp-md);color:var(--route-summary-muted);font-size:var(--fs-label-md);font-weight:750;cursor:pointer}.route-summary-metadata__summary:focus-visible{outline:var(--focus-ring-width) solid var(--focus-ring-color);outline-offset:calc(0px - var(--focus-ring-width))}.route-summary-metadata__list{display:grid;gap:1px;margin:0;border-top:1px solid var(--route-summary-divider);background:var(--route-summary-divider)}.route-summary-metadata__row{min-width:0;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.5fr);gap:var(--sp-md);padding:var(--sp-sm) var(--sp-md);background:var(--route-summary-surface);font-size:var(--fs-label-sm);line-height:1.4}.route-summary-metadata__row dt{color:var(--route-summary-muted)}.route-summary-metadata__row dd{min-width:0;margin:0;overflow-wrap:anywhere;text-align:right}.route-summary-state{display:grid;place-content:center;gap:var(--sp-md);padding-block:var(--sp-xl);color:var(--route-summary-muted);font-size:var(--fs-body-sm);line-height:1.45;text-align:center}.route-summary__empty{min-height:calc(var(--control-min-height) + var(--control-min-height) + var(--sp-xl));display:grid;place-content:center;padding:var(--sp-lg);color:var(--route-summary-muted);font-size:var(--fs-body-sm);line-height:1.45;text-align:center}.route-summary-state__title{color:var(--route-summary-text);font-size:var(--fs-body-md);font-weight:800;line-height:1.4}.route-summary-state__description{color:var(--route-summary-muted);font-size:var(--fs-label-md);font-variant-numeric:tabular-nums;line-height:1.4}.route-summary-state__progress{position:relative;width:100%;height:var(--route-summary-progress-height);overflow:hidden;border-radius:var(--r-round);background:var(--route-summary-progress-track)}.route-summary-state__progress-fill{display:block;width:calc(var(--route-summary-progress, 0) * 1%);height:100%;border-radius:inherit;background:var(--route-summary-progress-fill);transition:width var(--route-summary-progress-motion) var(--ease-standard)}.route-summary-state__progress[data-mode=indeterminate] .route-summary-state__progress-fill{width:32%;animation:route-summary-progress-indeterminate var(--route-summary-progress-cycle) var(--ease-standard) infinite}@keyframes route-summary-progress-indeterminate{0%{transform:translate(-110%)}to{transform:translate(330%)}}@container route-summary (min-width: 540px){.route-summary-hero{grid-template-columns:minmax(0,.75fr) minmax(0,1.75fr);align-items:end}.route-summary-composition,.route-summary__dashboard{grid-template-columns:repeat(2,minmax(0,1fr))}.route-summary__section--events{grid-column:1 / -1}.route-summary__metrics--distance{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(prefers-reduced-motion:reduce){.route-summary *,.route-summary-state *{animation:none;scroll-behavior:auto;transition:none}} diff --git a/openpilot/selfdrive/carrot/web/generated/asset-manifest.json b/openpilot/selfdrive/carrot/web/generated/asset-manifest.json index 5c2ce00bbd..e20a662720 100644 --- a/openpilot/selfdrive/carrot/web/generated/asset-manifest.json +++ b/openpilot/selfdrive/carrot/web/generated/asset-manifest.json @@ -6,7 +6,7 @@ "kind": "bundle", "source": "src/entries/app.js", "path": "js/generated/app.js", - "hash": "1af713de15b55281ee1a3e3a15dab05129aac5f7f22f65eaf14ea8cf7846f9bb" + "hash": "d55ee0caca57fd432655cda0d5eb381bb49d1bcf9da9a4b80b053d657eb37487" }, { "id": "drive.runtime", @@ -41,7 +41,7 @@ "kind": "bundle", "source": "src/entries/logs.js", "path": "js/generated/logs.js", - "hash": "bf9b37460a74c44188c14565701fad39a93f08683ea9c47ca5223a24700ab3b4" + "hash": "367bacf74535b6ad535b7b554491a2b7c7f150ba7ec9fb7054f4ad5b459c58ee" }, { "id": "terminal.runtime", @@ -90,7 +90,7 @@ "kind": "bundle", "source": "src/entries/design_system.css", "path": "css/generated/design-system.css", - "hash": "2a4cb7be71efb614c8d087124d664b6895e4a97351c98c646783e16bee65c0e6" + "hash": "68169a78e3fa9e18be4d2ca3f4d9c97a4281df52e27d6fe1396c8413bd737211" }, { "id": "drive.styles", @@ -125,7 +125,7 @@ "kind": "bundle", "source": "src/entries/logs.css", "path": "css/generated/logs.css", - "hash": "62e3527995bea1be410774a30955e04ca2662d439dbe70fbb9c9d809f7e3bb16" + "hash": "999280fcd3d4c792b4f688457b5fd4006fbee38bd1dc75b22927b64eaa73da9d" }, { "id": "terminal.styles", diff --git a/openpilot/selfdrive/carrot/web/js/generated/app.js b/openpilot/selfdrive/carrot/web/js/generated/app.js index d2c47e04ba..bb54c03662 100644 --- a/openpilot/selfdrive/carrot/web/js/generated/app.js +++ b/openpilot/selfdrive/carrot/web/js/generated/app.js @@ -1,2 +1,2 @@ /* @generated by web/build.mjs; edit web/src/entries/app.js instead. */ -(()=>{var Mt=/^[0-9a-f]{64}$/,Rt=/^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))(?!.*\\).+$/,J=class extends Error{constructor(t,n){super(t,n),this.name="AssetManifestError"}};function zt(e){if(e?.schemaVersion!==1||!Array.isArray(e.assets))throw new J("Invalid asset manifest schema");let t=new Map,n=new Set;for(let r of e.assets){if(typeof r?.id!="string"||typeof r.path!="string"||!Rt.test(r.path)||typeof r.hash!="string"||!Mt.test(r.hash)||t.has(r.id)||n.has(r.path))throw new J("Invalid asset manifest entry");t.set(r.id,r),n.add(r.path)}return t}function Pt(e){let t=zt(e);return Object.freeze({resolve(n){let r=t.get(n);if(!r)throw new J(`Unknown asset id: ${n}`);return`/${r.path}?v=${r.hash}`}})}function Be(e=document,t=globalThis){let n=e.getElementById("carrotAssetManifest");if(!n)throw new J("Asset manifest element is missing");let r;try{r=JSON.parse(n.textContent||"")}catch(o){throw new J("Asset manifest JSON is invalid",{cause:o})}let i=Pt(r);return t.CarrotAssetUrl=i,i}var ie=Object.freeze({webUi:"web_ui",profile:"profile",restore:"restore",resetDefaults:"reset_defaults",intro:"intro",undo:"undo",device:"device"});function Ut(e){let t=String(e||"").trim();return Object.values(ie).includes(t)?t:ie.webUi}function qe(e={}){let t=e.postJson;if(typeof t!="function")throw new TypeError("postJson must be a function");let n=e.endpoint||"/api/param_set",r=typeof e.onCommitted=="function"?e.onCommitted:null,i=typeof e.dispatchEvent=="function"?e.dispatchEvent:null;async function o(a,f,A={}){let E=String(a||"").trim();if(!E)throw new TypeError("A parameter name is required");let b=Ut(A.source),l=await t(n,{name:E,value:f,source:b}),d=l&&Object.prototype.hasOwnProperty.call(l,"value")?l.value:f,s=String(d)!==String(f),g=Object.freeze({name:E,value:d,requested:f,source:b,adjusted:s});return r?.(g),i?.(g),g}return Object.freeze({commit:o,sources:ie})}function Bt(e){return typeof e=="object"&&e!==null||typeof e=="function"}function $e(e=globalThis,t={}){if(!Bt(e))throw new TypeError("A facade target is required");let n=t.createSettingCommitter||qe;if(typeof n!="function")throw new TypeError("A committer factory is required");let r=Object.freeze({create:n,sources:ie});return e.CarrotParamCommit=r,r}var qt=["a[href]","area[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])','[contenteditable]:not([contenteditable="false"])',"audio[controls]","video[controls]","iframe","object","embed","summary"].join(",");function Ve(e,t){let n=t.document??e?.ownerDocument??globalThis.document,r=t.window??n?.defaultView??globalThis.window??globalThis,i=t.requestAnimationFrame??r?.requestAnimationFrame?.bind(r)??(f=>globalThis.setTimeout(f,0)),o=t.cancelAnimationFrame??r?.cancelAnimationFrame?.bind(r)??(f=>globalThis.clearTimeout(f)),a=t.getComputedStyle??r?.getComputedStyle?.bind(r)??(()=>({}));return{documentRoot:n,requestFrame:i,cancelFrame:o,getComputedStyle:a}}function Ge(e,t={}){if(!e?.querySelectorAll)return[];let{getComputedStyle:n}=Ve(e,t);return Array.from(e.querySelectorAll(qt)).filter(r=>{if(r.disabled||r.hidden||r.hasAttribute?.("disabled"))return!1;let i=r.getAttribute?.("tabindex");if(i!=null&&Number(i)<0)return!1;let o=r;for(;o&&o!==e;){if(o.hasAttribute?.("inert")||o.getAttribute?.("aria-hidden")==="true")return!1;o=o.parentElement??o.parentNode}let a=r.getBoundingClientRect?.();if(a&&a.width===0&&a.height===0)return!1;let f=n(r)||{};return f.visibility!=="hidden"&&f.display!=="none"})}function $t(e,t){let n=typeof t=="function"?t():t;return n?typeof n=="string"?e.querySelector?.(n)??null:typeof n.focus=="function"?n:null:null}function le(e,t={},n={}){if(!e)return Object.freeze({activate(){},deactivate(){},isActive:()=>!1});let{documentRoot:r,requestFrame:i,cancelFrame:o}=Ve(e,n),a=!1,f=0,A=null,E=null;function b(d){if(!a)return;if(d.key==="Escape"&&typeof t.escape=="function"){t.escape(d);return}if(d.key!=="Tab")return;let s=Ge(e,n);if(!s.length){d.preventDefault(),e.focus?.();return}let g=s[0],x=s[s.length-1],p=r?.activeElement;d.shiftKey?(p===g||!e.contains?.(p))&&(d.preventDefault(),x.focus()):(p===x||!e.contains?.(p))&&(d.preventDefault(),g.focus())}function l(){E!=null&&(o(E),E=null)}return Object.freeze({activate(){if(a)return;a=!0;let d=++f;if(A=t.returnFocus||r?.activeElement||null,e.hasAttribute?.("tabindex")||e.setAttribute?.("tabindex","-1"),r?.addEventListener?.("keydown",b,!0),t.focusInitial===!1)return;let s=$t(e,t.initialFocus),x=(!s||s===e||typeof e.contains!="function"||e.contains(s)?s:null)||Ge(e,n)[0]||e;E=i(()=>{E=null,!(!a||d!==f)&&x?.focus?.({preventScroll:t.initialFocusPreventScroll!==!1})})},deactivate(d={}){if(!a){f+=1,l();return}a=!1,f+=1,l(),r?.removeEventListener?.("keydown",b,!0);let s=A;if(A=null,d.restoreFocus===!1||typeof s?.focus!="function")return;(typeof r?.contains=="function"?r.contains(s):s.isConnected!==!1)&&s.focus({preventScroll:!0})},isActive(){return a}})}var Gt=180,Vt=Object.freeze(["app-dialog--choice","app-dialog--choice-list","app-dialog--choice-grid","app-dialog--choice-value-grid","app-dialog--form","app-dialog--input"]),Ht=Object.freeze(["appDialog","appBranchPicker","appCarPicker","settingSearchPanel"]);function Kt(e){return!e||e.labelHtml?"":String(e.label??"").trim()}function He(e){return!e||typeof e!="object"?"":String(e.heading??"").trim()}function Yt(e){return!!e&&!He(e)&&(e.label!=null||!!e.labelHtml)}function Wt(e){let t=[];for(let n of Array.isArray(e)?e:[]){let r=He(n);if(r){t.push({heading:r,items:[]});continue}Yt(n)&&(t.length||t.push({heading:"",items:[]}),t[t.length-1].items.push(n))}return t.filter(n=>n.items.length>0)}function Xt(e,t={}){let n=String(t.choiceLayout||t.choiceKind||"").trim();return n==="grid"||n==="value-grid"||n==="values"?"value-grid":n==="list"||n==="action-list"||n==="actions"?"list":e.length>4&&e.every(i=>{let o=Kt(i);return o&&o.length<=5&&!i.danger&&/^[+-]?(?:\d+|\d+\.\d+|[A-Za-z]{1,4})$/.test(o)})?"value-grid":"list"}function Jt(e,t={}){let n=Array.isArray(e)?e.flatMap(r=>r.items||[]):[];return n.length?e.some(r=>!!r?.heading)?"list":Xt(n,t):""}function Zt(e,t={}){let n=Number(t.choiceColumns||t.columns);return Number.isInteger(n)&&n>=2&&n<=6?n:e<=4?Math.max(2,e):e<=25?5:4}function Qt(e){let t=e.target??globalThis,n=e.document??t.document,r=e.setTimeout??t.setTimeout?.bind(t)??globalThis.setTimeout.bind(globalThis),i=e.clearTimeout??t.clearTimeout?.bind(t)??globalThis.clearTimeout.bind(globalThis),o=e.requestAnimationFrame??t.requestAnimationFrame?.bind(t)??(f=>r(f,0)),a=e.cancelAnimationFrame??t.cancelAnimationFrame?.bind(t)??i;return{target:t,documentRoot:n,setTimer:r,requestFrame:o,cancelFrame:a}}function Ke(e={}){let{target:t,documentRoot:n,setTimer:r,requestFrame:i,cancelFrame:o}=Qt(e),a=c=>n?.getElementById?.(c)??null,f=a("appDialog"),A=a("appDialogBackdrop"),E=a("appDialogTitle"),b=a("appDialogBody"),l=a("appDialogChoices"),d=a("appDialogInputWrap"),s=a("appDialogInput"),g=a("appDialogInputError"),x=a("appDialogCopy"),p=a("appDialogDefault"),C=a("appDialogCancel"),L=a("appDialogConfirm"),_=null,N=0,D=null,I=e.createFocusTrap??((c,w)=>le(c,w,{document:n,window:t,requestAnimationFrame:i,cancelAnimationFrame:o}));function j(c,w){let M=t.getUIText;return typeof M=="function"?M.call(t,c,w):w}function h(){let c=Ht.some(w=>{let M=a(w);return!!(M&&!M.hidden)});n?.body?.classList?.toggle("dialog-open",c)}function v(){f?.classList?.remove(...Vt),l&&(l.className="app-dialog__choices",l.style.removeProperty("--app-dialog-choice-columns"))}function T(c){return c.mode==="prompt"||c.mode==="form"||c.mode==="choice"?null:!1}function y(c){c.lastFocus&&typeof c.lastFocus.focus=="function"&&c.lastFocus.focus()}function m(c,w){if(c.serial!==N){c.resolve(w);return}f.hidden=!0,h(),v(),l&&(l.hidden=!0,l.innerHTML=""),p&&(p.hidden=!0,p.onclick=null),d&&(d.hidden=!0),g&&(g.hidden=!0,g.textContent=""),s&&(s.disabled=!1,s.value="",s.placeholder="",s.type="text",s.removeAttribute("autocomplete"),s.removeAttribute("autocapitalize"),s.removeAttribute("inputmode"),s.removeAttribute("aria-labelledby"),s.spellcheck=!0),b&&(b.hidden=!1),L&&(L.disabled=!1),C&&(C.disabled=!1),y(c),D=null,c.resolve(w)}function u(c,w){!c||_!==c||c.closing||(c.closing=!0,_=null,D=c.lastFocus||D,c.openFrame!=null&&(o(c.openFrame),c.openFrame=null),c.focusTrap?.deactivate?.({restoreFocus:!1}),f?.classList?.remove("is-open"),c.closeTimer=r(()=>{c.closeTimer=null,m(c,w)},Gt))}function F(c){u(_,c)}function k(){!_||_.submitting||u(_,T(_))}async function S(){if(!_||_.submitting)return;let c=_,M=c.mode==="prompt"||c.mode==="form"?s?s.value:"":!0;if(c.mode!=="form"||typeof c.onSubmit!="function"){u(c,M);return}c.submitting=!0,g&&(g.hidden=!0,g.textContent=""),s&&(s.disabled=!0),L&&(L.disabled=!0,L.textContent=c.submittingLabel),C&&(C.disabled=!0);try{await c.onSubmit(M),_===c&&!c.closing&&u(c,M)}catch(B){if(_!==c||c.closing)return;c.submitting=!1,s&&(s.disabled=!1,s.focus()),L&&(L.disabled=!1,L.textContent=c.confirmLabel),C&&(C.disabled=!1),g&&(g.textContent=B?.message||String(B),g.hidden=!1)}}function R(c={}){if(!f||!E||!b||!L||!C)return c.mode==="prompt"||c.mode==="form"?Promise.resolve(null):Promise.resolve(c.mode==="alert");let w=D;_&&(w=_.lastFocus||w,u(_,T(_))),D=null;let M=c.mode||"alert",B=M==="form",Dt=c.title||(M==="confirm"?j("confirm_title","Confirm"):M==="prompt"||M==="form"?j("input_title","Input"):j("notice","Notice")),Ae=c.message||"",je=c.messageHtml||"",jt=!!c.html,ke=c.confirmLabel||j("ok","OK"),kt=c.cancelLabel||j("cancel","Cancel"),Me=c.defaultActionLabel||"",Re=M==="prompt"&&!!Me,Te=Wt(c.choices),ze=Te.flatMap(K=>K.items),ne=ze.length>0,Pe=M==="choice"||ne,re=Jt(Te,c),Ue=M!=="alert"&&c.showCancel!==!1;v(),B&&f.classList.add("app-dialog--form"),(M==="prompt"||B)&&f.classList.add("app-dialog--input"),ne&&(f.classList.add("app-dialog--choice"),f.classList.add(re==="value-grid"?"app-dialog--choice-grid":"app-dialog--choice-list"),re==="value-grid"&&f.classList.add("app-dialog--choice-value-grid")),E.textContent=Dt,jt?b.innerHTML=String(je||Ae):b.textContent=String(Ae),b.hidden=B&&!String(je||Ae).trim(),b.style.flex=ne?"0 0 auto":"1 1 auto",L.textContent=ke,C.textContent=kt,L.disabled=!1,C.disabled=!1,C.hidden=!Ue,C.setAttribute("aria-hidden",Ue?"false":"true"),L.hidden=Pe,L.setAttribute("aria-hidden",Pe?"true":"false"),p&&(p.hidden=!Re,p.textContent=Me,p.disabled=!1,p.onclick=Re?()=>F(c.defaultActionValue??""):null);let Ce=c.copyText||"";if(x&&(x.hidden=!Ce,x.textContent=c.copyLabel||j("copy","Copy"),x.onclick=Ce?()=>{let K=t.copyToClipboard;typeof K=="function"&&K.call(t,Ce);let O=t.alert;typeof O=="function"&&O.call(t,j("copied","Copied"))}:null),l){l.innerHTML="",l.hidden=!ne,l.className=`app-dialog__choices app-dialog__choices--${re||"list"}`,re==="value-grid"?l.style.setProperty("--app-dialog-choice-columns",String(Zt(ze.length,c))):l.style.removeProperty("--app-dialog-choice-columns");let K=O=>{let U=n.createElement("button");U.type="button";let z=O.danger?"btn btn--danger app-dialog__choiceBtn":"btn app-dialog__choiceBtn";return z+=re==="value-grid"?" app-dialog__choiceBtn--value":" app-dialog__choiceBtn--action",(O.current||O.selected)&&(z+=" is-current"),O.className&&(z+=` ${O.className}`),U.className=z,O.labelHtml?U.innerHTML=O.labelHtml:U.textContent=String(O.label),U.addEventListener("click",()=>F(O.value)),U};Te.forEach((O,U)=>{if(!O.heading){for(let Se of O.items)l.appendChild(K(Se));return}let z=n.createElement("div");z.className="app-dialog__choiceGroup",z.setAttribute("role","group");let Q=`appDialogChoiceHeading${U}`;z.setAttribute("aria-labelledby",Q);let ce=n.createElement("div");ce.id=Q,ce.className="app-dialog__choiceHeading",ce.textContent=O.heading,z.appendChild(ce);for(let Se of O.items)z.appendChild(K(Se));l.appendChild(z)})}if(d&&s){let K=M==="prompt"||B;d.hidden=!K,s.disabled=!1,s.value=c.defaultValue??"",s.placeholder=c.placeholder||"",s.type=c.inputType==="password"?"password":"text";let O=c.autocomplete??(B?"off":""),U=c.autocapitalize??(B?"none":"");O?s.autocomplete=O:s.removeAttribute("autocomplete"),U?s.autocapitalize=U:s.removeAttribute("autocapitalize"),s.spellcheck=c.spellcheck==null?!B:c.spellcheck===!0,B?s.setAttribute("aria-labelledby","appDialogTitle"):s.removeAttribute("aria-labelledby"),c.inputMode?s.inputMode=c.inputMode:s.removeAttribute("inputmode"),g&&(g.hidden=!0,g.textContent="")}return new Promise(K=>{let O={resolve:K,mode:M,serial:++N,onSubmit:c.onSubmit,submitting:!1,closing:!1,confirmLabel:ke,submittingLabel:c.submittingLabel||j("saving","Saving..."),lastFocus:w||n.activeElement||null,focusTrap:null,openFrame:null,closeTimer:null};_=O,f.hidden=!1,h();let U=f.querySelector?.(".app-dialog__sheet")||f;O.focusTrap=I(U,{focusInitial:!1,returnFocus:O.lastFocus}),O.focusTrap?.activate?.(),O.openFrame=i(()=>{if(O.openFrame=null,!(_!==O||O.serial!==N||O.closing))if(f.classList.add("is-open"),(M==="prompt"||B)&&s)s.focus(),s.select();else if(ne&&l){let z=l.querySelector(".is-current"),Q=z||l.querySelector("button");Q&&typeof Q.focus=="function"&&(Q.focus({preventScroll:!!z}),z&&typeof z.scrollIntoView=="function"&&z.scrollIntoView({block:"center",inline:"nearest"}))}else M==="choice"?C.focus():L.focus()})})}function V(c,w={}){return R({mode:"alert",title:w.title,message:c,messageHtml:w.messageHtml,html:w.html,confirmLabel:w.confirmLabel,copyText:w.copyText})}function Y(c,w={}){return R({mode:"confirm",title:w.title,message:c,confirmLabel:w.confirmLabel,cancelLabel:w.cancelLabel})}function H(c,w={}){return R({mode:"prompt",title:w.title,message:c,confirmLabel:w.confirmLabel,cancelLabel:w.cancelLabel,defaultValue:w.defaultValue,defaultActionLabel:w.defaultActionLabel,defaultActionValue:w.defaultActionValue,showCancel:w.showCancel,placeholder:w.placeholder})}function Nt(c,w={}){return R({mode:"form",title:w.title,message:c,defaultValue:w.defaultValue,placeholder:w.placeholder,inputType:w.inputType,autocomplete:w.autocomplete,autocapitalize:w.autocapitalize,inputMode:w.inputMode,spellcheck:w.spellcheck,confirmLabel:w.confirmLabel,cancelLabel:w.cancelLabel,submittingLabel:w.submittingLabel,onSubmit:w.onSubmit})}A&&(A.onclick=k),C&&(C.onclick=k),L&&(L.onclick=S);function Ft(c){if(!_)return;if(c.key==="Escape"){c.preventDefault(),_.mode==="alert"?u(_,!0):k();return}if(c.key!=="Enter"||c.shiftKey||c.isComposing||c.defaultPrevented)return;let w=c.target?.tagName;w==="BUTTON"||w==="SELECT"||w==="TEXTAREA"||w==="A"||w==="INPUT"&&c.target!==s||c.target?.isContentEditable||(c.preventDefault(),S())}return n?.addEventListener?.("keydown",Ft),Object.freeze({openAppDialog:R,appAlert:V,appConfirm:Y,appPrompt:H,appForm:Nt,syncModalBodyLock:h,cancelAppDialog:k,confirmAppDialog:S})}var en=Object.freeze(["openAppDialog","appAlert","appConfirm","appPrompt","appForm","syncModalBodyLock","createFocusTrap"]);function Ye(e=globalThis,t=e.document,n={}){let r=(A,E={})=>le(A,E,{document:t,window:e,requestAnimationFrame:n.requestAnimationFrame,cancelAnimationFrame:n.cancelAnimationFrame,getComputedStyle:n.getComputedStyle}),i=Ke({...n,target:e,document:t,createFocusTrap:r}),o=Object.freeze({openAppDialog:i.openAppDialog,appAlert:i.appAlert,appConfirm:i.appConfirm,appPrompt:i.appPrompt,appForm:i.appForm,syncModalBodyLock:i.syncModalBodyLock,createFocusTrap:r}),a=e.CarrotUI&&typeof e.CarrotUI=="object"?e.CarrotUI:null,f=a&&Object.isExtensible(a)?a:{...a||{}};f.dialog=o,e.CarrotUI=f;for(let A of en)e[A]=o[A];return o}function We(e,t,n){let r=String(t??"");if(n)for(let[i,o]of Object.entries(n))r=r.replaceAll(`{${i}}`,String(o));return r}function Xe(e){return String(e??"")}function Je(e){let t=Number(e?.summary?.selected||0);return Number.isFinite(t)&&t>0?t:(Array.isArray(e?.entries)?e.entries:[]).filter(r=>r?.apply).length}function Ee(e,t={}){let n=t.escape||Xe,r=t.text||We;return'
'+[["changed","settings_diff_changed","changed"],["same","settings_diff_same","same"],["skipped","settings_diff_skipped","skipped"],["invalid","settings_diff_invalid","invalid"]].map(([o,a,f])=>`
${n(r(a,f))}${n(e?.[o]??0)}
`).join("")+"
"}function tn(e,t,n){let r=e.filter(o=>o?.status==="invalid");if(!r.length)return"";let i=r.map(o=>`
  • ${t(o.key)}`+(o.reason?`${t(o.reason)}`:"")+"
  • ").join("");return`
    ${t(n("settings_diff_invalid_detail","{count} could not be applied",{count:r.length}))}
    `}function Ze(e,t={}){let n=t.escape||Xe,r=t.text||We,i=Array.isArray(e?.entries)?e.entries:[],o=i.filter(p=>p?.apply||p?.status==="changed"),a=Number.isFinite(t.limit)?Math.max(1,t.limit):80,f=o.slice(0,a),A=Math.max(0,o.length-f.length),E=t.currentLabel||r("settings_diff_current","Current"),b=t.nextLabel||r("settings_diff_apply","Apply"),l=t.changedLabel||r("settings_diff_changed_status","Changed"),d=tn(i,n,r),s=Ee(e?.summary,{escape:n,text:r});if(!o.length)return`${s}
    ${n(r("settings_diff_no_changes","No changes to apply."))}
    `+d;let g=f.map(p=>`
    ${n(p.key)}
    ${n(l)}
    ${n(E)}${n(p.current)}
    ${n(b)}${n(p.value)}
    `).join(""),x=A?`
    ${n(r("settings_diff_more","{count} more changes hidden",{count:A}))}
    `:"";return`${s}
    ${g}
    ${x}${d}`}function Qe(e){return typeof e=="object"&&e!==null||typeof e=="function"}function et(e=globalThis,t={}){if(!Qe(e))throw new TypeError("A facade target is required");let n=Object.freeze({renderTable:t.renderDiffTable||Ze,renderSummary:t.renderDiffSummary||Ee,selectedCount:t.getDiffSelectedCount||Je}),r=Qe(e.CarrotUI)?e.CarrotUI:null,i=r&&Object.isExtensible(r)?r:Object.assign({},r||{});return i.diffTable=n,e.CarrotUI=i,n}var rt=Object.freeze({default:"i",info:"i",success:"✓",error:"✕",warn:"!",offline:"!",hint:"i"}),nn=Object.freeze({default:"",info:"is-info",success:"is-success",error:"is-error",warn:"is-warn",offline:"is-warn",hint:"is-hint"}),rn=2600,on=220;function an(e){let t=typeof e=="string"?e:"default";return Object.hasOwn(rt,t)?t:"default"}function ve(e,t){if(e==null)return t;let n=Number(e);return Number.isFinite(n)&&n>=0?n:t}function tt(e,t){return!e||!t?!1:typeof t.contains=="function"?t.contains(e):e.parentNode===t}function nt(e){e&&(typeof e.remove=="function"?e.remove():e.parentNode?.removeChild?.(e))}function ue(e={},t={}){let n=e.environment||t,r=e.host||null,i=e.documentRoot||n.document||r?.ownerDocument||globalThis.document,o=n.setTimeout||e.setTimeout||globalThis.setTimeout?.bind(globalThis),a=n.clearTimeout||e.clearTimeout||globalThis.clearTimeout?.bind(globalThis),f=n.requestAnimationFrame||e.requestAnimationFrame||(y=>o(y,0)),A=n.cancelAnimationFrame||e.cancelAnimationFrame||a,E=ve(e.defaultDuration,rn),b=ve(e.removeDelay,on),l=0,d=null,s=null,g=null,x=null,p=null,C=!1;function L(){s!=null&&a?.(s),g!=null&&a?.(g),x!=null&&A?.(x),s=null,g=null,x=null}function _(y,m){C||y!==l||m!==d||(nt(m),d=null,g=null,p=null)}function N(y,m){if(b===0){_(y,m);return}g=o?.(()=>_(y,m),b)??null}function D(y,m){C||y!==l||m!==d||(m.classList?.remove?.("is-visible"),s=null,N(y,m))}function I(){let y=!!(d&&tt(d,r));return Object.freeze({active:y,visible:y&&!!d.classList?.contains?.("is-visible"),message:y?p?.message??"":"",tone:y?p?.tone??"default":null,duration:y?p?.duration??null:null,glyph:y?p?.glyph??"":"",className:y&&d.className||"",serial:l,destroyed:C})}function j(){if(d&&tt(d,r))return d;if(!r||typeof i?.createElement!="function")return null;let y=i.createElement("div");if(typeof r.replaceChildren=="function")r.replaceChildren(y);else{for(;r.firstChild;)r.removeChild(r.firstChild);r.appendChild(y)}return d=y,y}function h(y,m={}){if(C||y==null||String(y).length===0)return I();let u=j();if(!u)return I();L();let F=++l,k=an(m.tone),S=ve(m.duration,E),R=nn[k],V=rt[k];u.className=R?`app-toast ${R}`:"app-toast";let Y=i.createElement("span");Y.className="app-toast__icon",Y.setAttribute?.("aria-hidden","true"),Y.textContent=V;let H=i.createElement("div");return H.className="app-toast__msg",H.textContent=String(y),u.replaceChildren(Y,H),p={message:String(y),tone:k,duration:S,glyph:V},x=f?.(()=>{C||F!==l||u!==d||(x=null,u.classList?.add?.("is-visible"))})??null,s=o?.(()=>D(F,u),S)??null,I()}function v(y={}){if(C||!d)return I();let m=d;L();let u=++l;return m.classList?.remove?.("is-visible"),y.immediate===!0?_(u,m):N(u,m),I()}function T(){C||(L(),l+=1,nt(d),d=null,p=null,C=!0)}return Object.freeze({show:h,dismiss:v,destroy:T,snapshot:I})}var it=new WeakMap;function ot(e){return typeof e=="object"&&e!==null||typeof e=="function"}function sn(e,t,n){let r=ot(e.CarrotUI)?e.CarrotUI:null,i=r&&Object.isExtensible(r)?r:Object.assign({},r||{});i[t]=n,e.CarrotUI=i}function at(e=globalThis,t={}){if(!ot(e))throw new TypeError("A facade target is required");let n=it.get(e),r=t.documentRoot||e.document,i=t.host||r?.getElementById?.("appToastHost")||null,o={document:r,setTimeout:e.setTimeout?.bind?.(e),clearTimeout:e.clearTimeout?.bind?.(e),requestAnimationFrame:e.requestAnimationFrame?.bind?.(e),cancelAnimationFrame:e.cancelAnimationFrame?.bind?.(e),...t.environment||{}},a=t.controller||ue({host:i,documentRoot:r,defaultDuration:t.defaultDuration,removeDelay:t.removeDelay,environment:o});if(!a||["show","dismiss","destroy","snapshot"].some(E=>typeof a[E]!="function"))throw new TypeError("A toast controller is required");let f=Object.freeze({create:ue,createController:ue,show:a.show,dismiss:a.dismiss,destroy:a.destroy,snapshot:a.snapshot});function A(E,b,l){if(b&&typeof b=="object")return a.show(E,b);let d={};return b!=null&&b!==""&&(d.tone=b),l!=null&&(d.duration=l),a.show(E,d)}return sn(e,"toast",f),e.showAppToast=A,n&&n!==a&&n.destroy(),it.set(e,a),f}var cn='[role^="menuitem"]',fe=new WeakMap;function de(e,t){if(!e||typeof e.addEventListener!="function"||typeof e.removeEventListener!="function")throw new TypeError(`${t} must be an event target`);return e}function st(e){return!!(e?.disabled||e?.hidden||e?.getAttribute?.("disabled")!=null||e?.getAttribute?.("aria-disabled")==="true"||e?.getAttribute?.("aria-hidden")==="true"||e?.classList?.contains?.("is-disabled"))}function ln(e,t,n){let r=typeof e?.closest=="function"?e.closest(t):null;if(!r){for(let i=e;i&&i!==n;i=i.parentElement||i.parentNode)if(typeof i.matches=="function"&&i.matches(t)){r=i;break}}return r&&n.contains(r)?r:null}function we(e){typeof e?.focus=="function"&&e.focus()}function ee(e={},t={}){let{root:n,trigger:r,panel:i,itemSelector:o=cn,beforeOpen:a,onSelect:f}=e,A=e.environment||t,E=e.documentRoot||A.document||n?.ownerDocument||globalThis.document;if(de(n,"root"),de(r,"trigger"),de(i,"panel"),de(E,"documentRoot"),typeof n.contains!="function"||typeof i.contains!="function")throw new TypeError("root and panel must support contains()");if(typeof i.querySelectorAll!="function")throw new TypeError("panel must support querySelectorAll()");if(typeof o!="string"||!o.trim())throw new TypeError("itemSelector must be a non-empty string");let b=fe.get(n);b&&b.destroy();let l=r.getAttribute?.("aria-expanded")==="true"||!i.hidden&&n.classList?.contains?.("is-open"),d=!1,s;function g(){return Array.from(i.querySelectorAll(o)).filter(m=>!st(m))}function x(){return d?!1:(n.classList?.toggle?.("is-open",l),i.hidden=!l,r.setAttribute?.("aria-expanded",l?"true":"false"),i.setAttribute?.("aria-hidden",l?"false":"true"),l)}function p(m){let u=g();if(!u.length)return null;let F=m==="last"?u[u.length-1]:u[0];return we(F),F}function C(m={}){if(d)return!1;if(l)x();else{if(typeof a=="function"&&a(s)===!1)return!1;l=!0,x()}let u=m===!0||m.focusFirst===!0?"first":m.focusLast===!0?"last":m.focus;return(u==="first"||u==="last")&&p(u),!0}function L(m={}){if(d)return!1;let u=l;return l=!1,x(),m.restoreFocus===!0&&we(r),u}function _(m={}){return d?!1:l?(L({restoreFocus:m.restoreFocus===!0}),!1):(C(m),l)}function N(m){let u=g();if(!u.length)return!1;let F=A.getActiveElement?.()||E.activeElement,k=u.indexOf(F),S;if(m.key==="ArrowDown")S=k<0?0:(k+1)%u.length;else if(m.key==="ArrowUp")S=k<0?u.length-1:(k-1+u.length)%u.length;else if(m.key==="Home")S=0;else if(m.key==="End")S=u.length-1;else return!1;return m.preventDefault?.(),we(u[S]),!0}function D(){_()}function I(m){m.key!=="ArrowDown"&&m.key!=="ArrowUp"||(m.preventDefault?.(),C({focus:m.key==="ArrowUp"?"last":"first"}))}function j(m){if(!l)return;let u=ln(m.target,o,i);!u||st(u)||(L({restoreFocus:!0}),typeof f=="function"&&f(u,m,s))}function h(m){if(l){if(m.key==="Tab"){L();return}if(m.key==="Escape"){m.preventDefault?.(),m.stopPropagation?.(),L({restoreFocus:!0});return}N(m)}}function v(m){if(!l)return;let u=m.target;n.contains(u)||r.contains?.(u)||i.contains(u)||L()}function T(m){!l||m.key!=="Escape"||(m.preventDefault?.(),L({restoreFocus:!0}))}function y(){d||(l&&(l=!1,x()),r.removeEventListener("click",D),r.removeEventListener("keydown",I),i.removeEventListener("click",j),i.removeEventListener("keydown",h),E.removeEventListener("pointerdown",v),E.removeEventListener("keydown",T),d=!0,fe.get(n)===s&&fe.delete(n))}return s=Object.freeze({open:C,close:L,toggle:_,sync:x,destroy:y,get isOpen(){return!d&&l},get destroyed(){return d}}),r.setAttribute?.("aria-haspopup","menu"),i.id&&r.setAttribute?.("aria-controls",i.id),i.getAttribute?.("role")||i.setAttribute?.("role","menu"),x(),r.addEventListener("click",D),r.addEventListener("keydown",I),i.addEventListener("click",j),i.addEventListener("keydown",h),E.addEventListener("pointerdown",v),E.addEventListener("keydown",T),fe.set(n,s),s}function ct(e){return typeof e=="object"&&e!==null||typeof e=="function"}var un=Object.freeze({create:ee,createController:ee,mount:ee});function lt(e=globalThis,t={}){if(!ct(e))throw new TypeError("A facade target is required");let n=t.createMenuController||ee;if(typeof n!="function")throw new TypeError("A menu controller factory is required");let r=n===ee?un:Object.freeze({create:n,createController:n,mount:n}),i=ct(e.CarrotUI)?e.CarrotUI:null,o=i&&Object.isExtensible(i)?i:Object.assign({},i||{});return o.menu=r,e.CarrotUI=o,t.compatibility!==!1&&(e.CarrotDropdownMenu=r),r}function fn(e){if(!e||typeof e.addEventListener!="function")throw new TypeError("A commit gesture target element is required");return e}function pe(e,t={}){let n=fn(e),r=t.onCommit;if(typeof r!="function")throw new TypeError("onCommit must be a function");let i=typeof t.onHoldStep=="function"?t.onHoldStep:r,o=Number.isFinite(t.holdDelayMs)?t.holdDelayMs:0,a=Number.isFinite(t.holdIntervalMs)?t.holdIntervalMs:160,f=Number.isFinite(t.moveTolerance)?t.moveTolerance:10,A=Number.isFinite(t.clickSuppressMs)?t.clickSuppressMs:700,E=typeof t.pressedClass=="string"?t.pressedClass:"is-holding",b=typeof t.now=="function"?t.now:()=>Date.now(),l=t.scheduler||globalThis,d=l.setTimeout.bind(l),s=l.clearTimeout.bind(l),g=null,x=null,p=null,C=0;function L(){x!==null&&(s(x),x=null),p!==null&&(s(p),p=null)}function _(){if(!(!g||g.pointerId===void 0||g.pointerId===null)&&typeof n.releasePointerCapture=="function")try{n.releasePointerCapture(g.pointerId)}catch{}}function N(){L(),_(),n.classList?.remove?.(E),g=null}function D(){g&&(g.holdFired=!0,i(),p=d(D,a))}function I(){x=null,g&&D()}function j(u){if(!(u.button!==void 0&&u.button!==0)){if(N(),g={pointerId:u.pointerId,startX:u.clientX,startY:u.clientY,holdFired:!1,cancelled:!1},n.classList?.add?.(E),typeof n.setPointerCapture=="function"&&u.pointerId!==void 0)try{n.setPointerCapture(u.pointerId)}catch{}o>0&&(x=d(I,o))}}function h(u){if(!g||u.pointerId!==void 0&&u.pointerId!==g.pointerId)return;let F=Math.abs(u.clientX-g.startX),k=Math.abs(u.clientY-g.startY);F<=f&&k<=f||(g.cancelled=!0,C=b()+A,N())}function v(u){if(!g||u.pointerId!==void 0&&u.pointerId!==g.pointerId)return;let F=!g.cancelled&&!g.holdFired;C=b()+A,N(),F&&r()}function T(){g&&(g.cancelled=!0,C=b()+A,N())}function y(u){if(b()n.addEventListener(u,F)),Object.freeze({destroy(){N(),C=0,m.forEach(([u,F])=>n.removeEventListener?.(u,F))},get pressed(){return g!==null}})}function mn(e,t,n){let r=e;return Number.isFinite(t)&&(r=Math.max(r,t)),Number.isFinite(n)&&(r=Math.min(r,n)),r}function _e(e={}){let t=Number(e.step),n=Number(e.sign),r=Number(e.min),i=Number(e.max),o=Number(e.current);if(Number.isFinite(o)||(o=Number(e.fallback)),Number.isFinite(o)||(o=Number.isFinite(r)?r:0),!Number.isFinite(t)||!Number.isFinite(n))return o;let a=mn(o+n*t,r,i);return Number.isInteger(r)&&Number.isInteger(i)&&Number.isInteger(t)&&(a=Math.round(a)),a}function ut(e={}){let t=e.commit;if(typeof t!="function")throw new TypeError("commit must be a function");let n=typeof e.getValue=="function"?e.getValue:()=>NaN,r=typeof e.getStep=="function"?e.getStep:()=>1,i=typeof e.getRange=="function"?e.getRange:()=>({}),o=typeof e.getFallback=="function"?e.getFallback:()=>NaN,a=Number.isFinite(e.holdDelayMs)?e.holdDelayMs:900,f=Number.isFinite(e.holdIntervalMs)?e.holdIntervalMs:160,A=!1;async function E(d){if(!A){A=!0;try{let s=i()||{},g=_e({current:n(),fallback:o(),sign:d,step:r(),min:s.min,max:s.max});await t(g)}finally{A=!1}}}let b=[];function l(d,s){d&&b.push(pe(d,{onCommit:()=>{E(s)},holdDelayMs:a,holdIntervalMs:f,pressedClass:e.pressedClass,now:e.now,scheduler:e.scheduler}))}return l(e.minusButton,-1),l(e.plusButton,1),Object.freeze({applyDelta:E,destroy(){b.forEach(d=>d.destroy()),b.length=0}})}function ft(e){return typeof e=="object"&&e!==null||typeof e=="function"}function dt(e=globalThis,t={}){if(!ft(e))throw new TypeError("A facade target is required");let n=t.createNumericStepper||ut,r=t.createCommitGesture||pe;if(typeof n!="function")throw new TypeError("A numeric stepper factory is required");if(typeof r!="function")throw new TypeError("A commit gesture factory is required");let i=Object.freeze({create:n,createGesture:r,nextStepValue:t.nextStepValue||_e}),o=ft(e.CarrotUI)?e.CarrotUI:null,a=o&&Object.isExtensible(o)?o:Object.assign({},o||{});return a.numericStepper=i,e.CarrotUI=a,e.CarrotNumericStepper=i,i}var me=new WeakMap;function be(e,t){if(!e||typeof e.addEventListener!="function"||typeof e.removeEventListener!="function")throw new TypeError(`${t} must be an event target`);return e}function te(e={},t={}){let{root:n,trigger:r,panel:i,beforeOpen:o,onOpenChange:a}=e,f=e.environment||t,A=e.documentRoot||f.document||n?.ownerDocument||globalThis.document;if(be(n,"root"),be(r,"trigger"),be(i,"panel"),be(A,"documentRoot"),typeof n.contains!="function"||typeof i.contains!="function")throw new TypeError("root and panel must support contains()");let E=me.get(n);E&&E.destroy();let b=r.getAttribute?.("aria-expanded")==="true"&&i.hidden!==!0,l=!1,d;function s(){return l?!1:(n.classList?.toggle?.("is-open",b),i.hidden=!b,r.setAttribute?.("aria-expanded",b?"true":"false"),i.setAttribute?.("aria-hidden",b?"false":"true"),typeof a=="function"&&a(b,d),b)}function g(){return l||!b&&typeof o=="function"&&o(d)===!1?!1:(b=!0,s())}function x(I={}){if(l)return!1;let j=b;return b=!1,s(),I.restoreFocus===!0&&r.focus?.(),j}function p(){return b?(x(),!1):g()}function C(){p()}function L(I){I.key!=="Escape"||!b||(I.preventDefault?.(),x({restoreFocus:!0}))}function _(I){!b||n.contains(I.target)||i.contains(I.target)||x()}function N(I){!b||I.key!=="Escape"||(I.preventDefault?.(),x({restoreFocus:!0}))}function D(){l||(b&&(b=!1,s()),r.removeEventListener("click",C),r.removeEventListener("keydown",L),A.removeEventListener("pointerdown",_),A.removeEventListener("keydown",N),l=!0,me.get(n)===d&&me.delete(n))}return d=Object.freeze({open:g,close:x,toggle:p,sync:s,destroy:D,get isOpen(){return!l&&b}}),i.id&&(r.setAttribute?.("aria-controls",i.id),r.setAttribute?.("aria-describedby",i.id)),s(),r.addEventListener("click",C),r.addEventListener("keydown",L),A.addEventListener("pointerdown",_),A.addEventListener("keydown",N),me.set(n,d),d}function pt(e){return typeof e=="object"&&e!==null||typeof e=="function"}var bn=Object.freeze({create:te,createController:te,mount:te});function mt(e=globalThis,t={}){if(!pt(e))throw new TypeError("A facade target is required");let n=t.createPopoverController||te;if(typeof n!="function")throw new TypeError("A popover controller factory is required");let r=n===te?bn:Object.freeze({create:n,createController:n,mount:n}),i=pt(e.CarrotUI)?e.CarrotUI:null,o=i&&Object.isExtensible(i)?i:Object.assign({},i||{});return o.popover=r,e.CarrotUI=o,r}var ge=new WeakMap;function oe(e,t){return typeof e?.getAttribute=="function"?e.getAttribute(t):null}function he(e){return e?.disabled===!0||typeof e?.hasAttribute=="function"&&e.hasAttribute("disabled")?!0:String(oe(e,"aria-disabled")||"").toLowerCase()==="true"}function gn(e,t){let n=oe(e,t);return n!==null&&String(n).toLowerCase()!=="false"}function hn(e){if(e?.dataset&&Object.prototype.hasOwnProperty.call(e.dataset,"value"))return String(e.dataset.value);let t=oe(e,"data-value");if(t!==null)return t;let n=oe(e,"value");return n!==null?n:e?.value!==void 0&&e.value!==null?String(e.value):null}function yn(e,t){return e===t?!0:typeof e?.contains=="function"&&e.contains(t)}function An(e){let t=String(e?.itemSelector||"").trim();if(!t)throw new TypeError("A segmented control itemSelector is required");return Object.freeze({itemSelector:t,selectedAttribute:String(e?.selectedAttribute||"aria-selected"),orientation:e?.orientation==="vertical"?"vertical":"horizontal",activation:e?.activation==="manual"?"manual":"automatic",onActivate:typeof e?.onActivate=="function"?e.onActivate:null})}function Tn(e){if(!e||typeof e!="object"||typeof e.querySelectorAll!="function"||typeof e.addEventListener!="function"||typeof e.removeEventListener!="function")throw new TypeError("A segmented control root element is required")}function bt(e,t={}){Tn(e);let n=ge.get(e);if(n)return n.sync(),n;let r=An(t),i=new Map,o=[],a=null,f=!1,A=null;function E(){return Array.from(e.querySelectorAll(r.itemSelector))}function b(h){i.has(h)||i.set(h,oe(h,"tabindex"))}function l(h,v){b(h),h.setAttribute("tabindex",String(v))}function d(h=o){return h.find(v=>gn(v,r.selectedAttribute))||null}function s({preferSelected:h=!0}={}){o=E();let v=o.filter(m=>!he(m)),T=d(o),y=T&&v.includes(T)?T:null;h&&y?a=y:v.includes(a)||(a=y||v[0]||null);for(let m of o)l(m,m===a?0:-1);return o}function g(){let h=E(),v=d(h),T=v?h.indexOf(v):-1;return Object.freeze({itemCount:h.length,selectedIndex:T,selectedValue:v?hn(v):null,destroyed:f})}function x(){return f||s({preferSelected:!0}),g()}function p(h){return o.find(v=>yn(v,h?.target))||null}function C(h){a=h;for(let v of o)l(v,v===h?0:-1)}function L(h,v,T){if(!r.onActivate)return;let y=o.indexOf(h);try{r.onActivate(h,y,Object.freeze({source:T,event:v,controller:A}))}finally{f||s({preferSelected:!1})}}function _(h,v){h&&(v?.preventDefault?.(),C(h),typeof h.focus=="function"&&h.focus(),r.activation==="automatic"&&L(h,v,"keyboard"))}function N(h,v){s({preferSelected:!1});let T=o.filter(k=>!he(k));if(!T.length)return null;let y=p(h),m=T.includes(y)?y:T.includes(a)?a:T[0],u=T.indexOf(m);return v==="first"?T[0]:v==="last"?T[T.length-1]:T[(u+(v==="previous"?-1:1)+T.length)%T.length]}function D(h){if(f||h?.isComposing||h?.altKey||h?.ctrlKey||h?.metaKey)return;let v=null;if(h.key==="Home"?v="first":h.key==="End"?v="last":r.orientation==="horizontal"&&h.key==="ArrowLeft"?v="previous":r.orientation==="horizontal"&&h.key==="ArrowRight"?v="next":r.orientation==="vertical"&&h.key==="ArrowUp"?v="previous":r.orientation==="vertical"&&h.key==="ArrowDown"&&(v="next"),v){_(N(h,v),h);return}if(r.activation!=="manual"||h.key!=="Enter"&&h.key!==" "&&h.key!=="Spacebar")return;s({preferSelected:!1});let T=p(h);!T||he(T)||(h.preventDefault?.(),C(T),L(T,h,"keyboard"))}function I(h){if(f)return;s({preferSelected:!1});let v=p(h);v&&!he(v)&&C(v)}function j(){if(f)return!1;f=!0,e.removeEventListener("keydown",D),e.removeEventListener("focusin",I);for(let[h,v]of i)v===null?h.removeAttribute("tabindex"):h.setAttribute("tabindex",v);return i.clear(),o=[],a=null,ge.get(e)===A&&ge.delete(e),!0}return A=Object.freeze({sync:x,destroy:j,snapshot:g}),ge.set(e,A),e.addEventListener("keydown",D),e.addEventListener("focusin",I),x(),A}function gt(e){return typeof e=="object"&&e!==null||typeof e=="function"}function ht(e=globalThis,t={}){if(!gt(e))throw new TypeError("A facade target is required");let n=t.createSegmentedControl||bt;if(typeof n!="function")throw new TypeError("A segmented control factory is required");let r=Object.freeze({create:n}),i=gt(e.CarrotUI)?e.CarrotUI:null,o=i&&Object.isExtensible(i)?i:Object.assign({},i||{});return o.segmentedControl=r,e.CarrotUI=o,e.CarrotSegmentedControl=r,r}var Le=Object.freeze(["toggle","segmented","select","stepper","slider"]);function X(e,t,n){let r=e.createElement(t);return n&&(r.className=n),r}function W(e,t,{text:n,label:r}={}){let i=X(e,"button",t);return i.type="button",n!==void 0&&(i.textContent=n),r&&i.setAttribute("aria-label",r),i}function Cn(e,t,n,r){let i=X(e,"label","c-switch"),o=X(e,"input","c-switch__input");o.type="checkbox",o.setAttribute("aria-label",r.label),i.appendChild(o),i.appendChild(X(e,"span","c-switch__track")),t.appendChild(i),n.toggleInput=o}function Sn(e,t,n,r){let i=X(e,"div","setting-segments");r.optionValues.forEach(o=>{let a=W(e,"setting-segment",{text:r.optionLabel(o)});a.dataset.value=String(o),a.setAttribute("aria-pressed","false"),n.segmentButtons.push(a),i.appendChild(a)}),t.appendChild(i),n.segmentGroup=i}function En(e,t,n,r){let i=W(e,"setting-select setting-select--button",{label:r.label});i.setAttribute("aria-haspopup","dialog"),t.appendChild(i),n.selectInput=i}function vn(e,t,n,r){n.minusButton=W(e,"smallBtn setting-value-arrow setting-value-arrow--prev",{text:"-",label:r.previousLabel}),n.plusButton=W(e,"smallBtn setting-value-arrow setting-value-arrow--next",{text:"+",label:r.nextLabel}),n.unitButton=W(e,"setting-unit-cycle"),t.appendChild(n.minusButton),t.appendChild(n.value),t.appendChild(n.plusButton)}function wn(e,t,n,r){let i=X(e,"div","setting-slider"),o=X(e,"input","setting-slider__input");o.type="range",o.min=String(r.min),o.max=String(r.max),o.step=String(r.step),o.setAttribute("aria-label",r.label),i.appendChild(o),n.sliderInput=o,n.minusButton=W(e,"smallBtn setting-step setting-step--minus",{text:"-"}),n.plusButton=W(e,"smallBtn setting-step setting-step--plus",{text:"+"}),n.unitButton=W(e,"setting-unit-cycle"),t.appendChild(i),t.appendChild(n.minusButton),t.appendChild(n.value),t.appendChild(n.plusButton)}function yt(e={}){let t=e.document;if(!t||typeof t.createElement!="function")throw new TypeError("A document is required to build a setting control");let n=String(e.kind||"");if(!Le.includes(n))throw new TypeError(`Unknown setting control kind: ${n}`);let r=n==="stepper",i=X(t,"div",`ctrl ctrl--${r?"value":n}`),o=W(t,r?"value-surface val setting-value-compact":"value-surface val",{label:e.valueLabel}),a={ctrl:i,value:o,toggleInput:null,segmentGroup:null,segmentButtons:[],selectInput:null,sliderInput:null,minusButton:null,plusButton:null,unitButton:null},f={label:e.label||"",valueLabel:e.valueLabel||"",previousLabel:e.previousLabel||"",nextLabel:e.nextLabel||"",optionValues:Array.isArray(e.optionValues)?e.optionValues:[],optionLabel:typeof e.optionLabel=="function"?e.optionLabel:A=>String(A),min:e.min,max:e.max,step:e.step};return n==="toggle"?Cn(t,i,a,f):n==="segmented"?Sn(t,i,a,f):n==="select"?En(t,i,a,f):r?vn(t,i,a,f):wn(t,i,a,f),!r&&n!=="slider"&&i.appendChild(o),a}function At(e){return typeof e=="object"&&e!==null||typeof e=="function"}function Tt(e=globalThis,t={}){if(!At(e))throw new TypeError("A facade target is required");let n=t.createSettingControl||yt;if(typeof n!="function")throw new TypeError("A setting control factory is required");let r=Object.freeze({createControl:n,kinds:Le}),i=At(e.CarrotUI)?e.CarrotUI:null,o=i&&Object.isExtensible(i)?i:Object.assign({},i||{});return o.settingRow=r,e.CarrotUI=o,r}var _n=new Set(["primary","secondary","danger"]);function q(e){return String(e??"").trim()}function Ln(e){if(typeof e!="function")return q(e);try{return q(e())}catch{return""}}function xn(e,t){let n=t?.document??e.environment?.document??e.document??globalThis.document;if(!n||typeof n.createElement!="function")throw new TypeError("createActionGroup requires a document environment");return n}function In(e){let t=q(e);return _n.has(t)?t:"secondary"}function xe(e){if(!Array.isArray(e))return Object.freeze([]);if(e.length>2)throw new RangeError("State surface action groups support at most 2 actions");let t=new Set,n=[];for(let r of e){let i=r&&typeof r=="object"?r:{},o=q(i.id),a=Ln(i.label),f=i.onActivate??i.invoke;!o||t.has(o)||!a||typeof f!="function"||(t.add(o),n.push(Object.freeze({id:o,label:a,tone:In(i.tone??i.kind),disabled:!!i.disabled,onActivate:f})))}return Object.freeze(n)}function Ct(e){return Array.from(new Set(e.flatMap(t=>q(t).split(/\s+/)).filter(Boolean))).join(" ")}function St(e={},t={}){let n=e.host;if(!n||typeof n.append!="function")return null;let r=xn(e,t),i=r.createElement("div");i.setAttribute("role","group"),i.setAttribute("aria-busy","false"),i.dataset.layout="single",i.dataset.sizing="full",i.dataset.pending="false",i.hidden=!0,n.append(i);let o=new Map,a=Object.freeze([]),f=new Map,A=null,E=0,b=!1,l=q(e.ariaLabel)||"Status actions",d=q(e.className),s=q(e.actionClassName),g=q(e.actionToneClassPrefix),x=typeof e.onPendingChange=="function"?e.onPendingChange:null;function p(){return A?.id||""}function C(T){return Ct(["btn",T.tone==="primary"?"btn--filled":"",T.tone==="danger"?"btn--danger":"","c-action-group__action",`c-action-group__action--${T.tone}`,s,g?`${g}${T.tone}`:""])}function L(){let T=a.length<=1;return Ct(["c-action-group","c-action-group--nowrap",T?"c-action-group--single":"c-action-group--split",T?"c-action-group--full":"c-action-group--equal",d])}function _(){if(b)return;let T=!!A,y=a.length<=1;i.className=L(),i.hidden=a.length===0,i.dataset.layout=y?"single":"split",i.dataset.sizing=y?"full":"equal",i.dataset.pending=String(T),i.setAttribute("aria-label",l),i.setAttribute("aria-busy",String(T));for(let m of a){let u=o.get(m.id).button,F=T||m.disabled;u.className=C(m),u.dataset.actionId=m.id,u.dataset.tone=m.tone,u.textContent=m.label,u.disabled=F,u.tabIndex=F?-1:0,u.setAttribute("aria-disabled",String(F)),u.setAttribute("tabindex",String(u.tabIndex)),F?u.setAttribute("disabled",""):u.removeAttribute("disabled")}x?.(p())}async function N(T){let y=f.get(q(T));if(b||!y||y.disabled||A)return!1;let m=Object.freeze({id:y.id,generation:++E});A=m,_();try{await y.onActivate()}catch{}finally{if(b||A!==m)return!0;A=null,_()}return!0}function D(T){let y=o.get(T);y&&(y.button.removeEventListener("click",y.listener),y.button.remove(),o.delete(T))}function I(T,y={}){if(b)return!1;let m=xe(T),u=new Set(m.map(S=>S.id));A&&!u.has(A.id)&&(A=null,E+=1);for(let S of o.keys())u.has(S)||D(S);for(let S of m){if(o.has(S.id))continue;let R=r.createElement("button");R.type="button";let V=()=>{N(S.id)};R.addEventListener("click",V),o.set(S.id,{button:R,listener:V})}a=m,f=new Map(a.map(S=>[S.id,S])),y.ariaLabel!=null&&(l=q(y.ariaLabel)||"Status actions");let F=Array.from(i.children).map(S=>S.dataset.actionId),k=a.map(S=>S.id);if(F.join("\0")!==k.join("\0"))for(let S of a)i.append(o.get(S.id).button);return _(),!0}function j(T){return b?!1:(l=q(T)||"Status actions",_(),!0)}function h(){let T=a.map(y=>Object.freeze({id:y.id,label:y.label,tone:y.tone,disabled:y.disabled}));return Object.freeze({actions:Object.freeze(T),pendingActionId:p(),layout:a.length<=1?"single":"split",sizing:a.length<=1?"full":"equal"})}function v(){if(b)return!1;b=!0,A=null,E+=1;for(let T of Array.from(o.keys()))D(T);return a=Object.freeze([]),f=new Map,i.remove(),!0}return Object.freeze({element:i,setActions:I,setAriaLabel:j,activate:N,snapshot:h,destroy:v})}var P=Object.freeze({EMPTY:"empty",LOADING:"loading",DEGRADED:"degraded",ERROR:"error",OWNERSHIP:"ownership"}),$=Object.freeze({NEUTRAL:"neutral",INFO:"info",WARNING:"warning",ERROR:"error"}),ae=Object.freeze({PRIMARY:"primary",SECONDARY:"secondary",DANGER:"danger"}),ye=Object.freeze({COVER:"cover",COMPACT:"compact"}),On=new Set(Object.values(P)),Nn=new Set(Object.values($)),Fn=new Set(Object.values(ye)),Dn=Object.freeze({[P.EMPTY]:$.NEUTRAL,[P.LOADING]:$.INFO,[P.DEGRADED]:$.WARNING,[P.ERROR]:$.ERROR,[P.OWNERSHIP]:$.WARNING}),Ie=new WeakMap,Fe=new WeakSet,jn=0;function Z(e){return String(e??"").trim()}function Oe(e){if(typeof e!="function")return Z(e);try{return Z(e())}catch{return""}}function Ne(e){return Array.from(new Set(e.flatMap(t=>Z(t).split(/\s+/)).filter(Boolean))).join(" ")}function Et(e){let t=Z(e);return On.has(t)?t:P.EMPTY}function kn(e,t){let n=Z(e);return Nn.has(n)?n:Dn[t]}function vt(e){let t=Z(e);return Fn.has(t)?t:ye.COVER}function wt(e){return e&&typeof e=="object"?e.featureLabel??e.label??e.name:e}function Mn(e,t){let n=t?.document??e.environment?.document??e.document??globalThis.document;if(!n||typeof n.createElement!="function")throw new TypeError("createStateSurface requires a document environment");return n}function Rn(e,t){if(typeof e=="string")return{state:Et(e),descriptor:t&&typeof t=="object"?t:{}};let n=e&&typeof e=="object"?e:{};return{state:Et(n.state),descriptor:n}}function _t(e={},t={}){let n=e.host;if(!n||typeof n.append!="function")return null;let r=Ie.get(n);if(r){let S=e.featureLabel??e.feature;return S!=null&&r.setFeature(S),r}let i=Mn(e,t),o=`c-state-surface-${++jn}`,a=Z(e.className),f=vt(e.variant),A=wt(e.featureLabel??e.feature),E=null,b=null,l=P.EMPTY,d=$.NEUTRAL,s=f,g="",x=!1,p=i.createElement("section");p.className=Ne(["c-state-surface",`c-state-surface--${d}`,`c-state-surface--state-${l}`,`c-state-surface--${s}`,a]),p.hidden=!0,p.setAttribute("aria-hidden","true"),p.setAttribute("aria-busy","false"),p.dataset.state=l,p.dataset.tone=d,p.dataset.variant=s,p.dataset.pending="false";let C=i.createElement("div");C.className="c-state-surface__body";let L=i.createElement("div");L.className="c-state-surface__copy",L.setAttribute("role","status"),L.setAttribute("aria-live","polite"),L.setAttribute("aria-atomic","true");let _=i.createElement("h2");_.id=`${o}-feature`,_.className="c-state-surface__feature";let N=i.createElement("p");N.id=`${o}-title`,N.className="c-state-surface__title";let D=i.createElement("p");D.id=`${o}-description`,D.className="c-state-surface__description",L.append(_,N,D),C.append(L),p.append(C),n.append(p);function I(){let S=l===P.LOADING||!!g;p.dataset.pending=String(!!g),p.setAttribute("aria-busy",String(S))}let j=St({host:C,className:"c-state-surface__actions",actionClassName:"c-state-surface__action",actionToneClassPrefix:"c-state-surface__action--",onPendingChange(S){g=S,I()}},{document:i});function h(S,R){let V=R.featureLabel!=null?R.featureLabel:A;return Object.freeze({state:S,featureLabel:Oe(V),title:Oe(R.title),description:Oe(R.description),tone:kn(R.tone,S),variant:vt(R.variant??f),actions:xe(R.actions)})}function v(S){l=S.state,d=S.tone,s=S.variant,p.className=Ne(["c-state-surface",`c-state-surface--${d}`,`c-state-surface--state-${l}`,`c-state-surface--${s}`,a]),p.dataset.state=l,p.dataset.tone=d,p.dataset.variant=s,_.textContent=S.featureLabel,_.hidden=!S.featureLabel,N.textContent=S.title,N.hidden=!S.title,D.textContent=S.description,D.hidden=!S.description,S.title||S.featureLabel?(p.setAttribute("aria-labelledby",S.title?N.id:_.id),p.removeAttribute("aria-label")):(p.removeAttribute("aria-labelledby"),p.setAttribute("aria-label","Status")),S.description?p.setAttribute("aria-describedby",D.id):p.removeAttribute("aria-describedby")}function T(S={},R){if(x)return!1;let{state:V,descriptor:Y}=Rn(S,R),H=h(V,Y);return E={...Y},b=H,v(H),j.setActions(H.actions,{ariaLabel:`${H.featureLabel||H.title||"Status"} actions`}),p.hidden=!1,p.setAttribute("aria-hidden","false"),I(),!0}function y(){return x||p.hidden?!1:(E=null,b=null,l=P.EMPTY,d=$.NEUTRAL,s=f,j.setActions([]),g="",p.hidden=!0,p.setAttribute("aria-hidden","true"),p.dataset.state=l,p.dataset.tone=d,p.dataset.variant=s,p.className=Ne(["c-state-surface",`c-state-surface--${d}`,`c-state-surface--state-${l}`,`c-state-surface--${s}`,a]),I(),!0)}function m(S){return x?!1:(A=wt(S),p.hidden||!E?!0:T(l,E))}function u(){let S=j.snapshot();return Object.freeze({visible:!x&&!p.hidden,state:b?.state||P.EMPTY,featureLabel:b?.featureLabel||"",title:b?.title||"",description:b?.description||"",tone:b?.tone||$.NEUTRAL,variant:b?.variant||f,actions:S.actions,pendingActionId:S.pendingActionId})}function F(){return x?!1:(x=!0,E=null,b=null,g="",j.destroy(),Ie.delete(n),Fe.delete(k),p.remove(),!0)}let k=Object.freeze({show:T,hide:y,setFeature:m,snapshot:u,destroy:F});return Ie.set(n,k),Fe.add(k),k}function Lt(e){return!!(e&&typeof e=="object"&&Fe.has(e))}var xt=new WeakMap,zn=new Set(Object.values(ae));function De(e){return String(e??"").trim()}function Pn(e){if(typeof e!="function")return De(e);try{return De(e())}catch{return""}}function It(e){if(typeof e=="string"||typeof e=="function")return Object.freeze({id:"",label:e});let t=e&&typeof e=="object"?e:{};return Object.freeze({id:t.id,label:t.label??t.name})}function Un(e){let t=De(e);return zn.has(t)?t:ae.SECONDARY}function Bn(e){return Array.isArray(e)?e.map(t=>{let n=t&&typeof t=="object"?t:{};return{id:n.id,label:n.label,tone:Un(n.kind??n.tone),disabled:!!n.disabled,onActivate:n.invoke??n.onActivate}}):[]}function qn(e){let t=new WeakMap,n=new WeakSet;function r(o={}){let a=o.host;if(!a||typeof a.append!="function")return null;let f=t.get(a);if(f)return o.feature!=null&&f.setFeature(o.feature),f;let A=It(o.feature),E=e.create({host:a,className:o.className,featureLabel:A.label});if(!E)return null;let b=!1;function l(C={}){if(b)return!1;let L=C&&typeof C=="object"?C:{};return E.show(P.OWNERSHIP,{title:L.title,description:L.description,tone:$.NEUTRAL,actions:Bn(L.actions)})}function d(){return b?!1:E.hide()}function s(C){return b?!1:(A=It(C),E.setFeature(A.label))}function g(){let C=E.snapshot(),L=C.actions.map(_=>Object.freeze({id:_.id,label:_.label,kind:_.tone,disabled:_.disabled}));return Object.freeze({visible:C.visible,featureId:C.visible?Pn(A.id):"",featureLabel:C.featureLabel,title:C.title,description:C.description,actions:Object.freeze(L),pendingActionId:C.pendingActionId})}function x(){return b?!1:(b=!0,t.delete(a),n.delete(p),E.destroy(),!0)}let p=Object.freeze({show:l,hide:d,setFeature:s,snapshot:g,destroy:x});return t.set(a,p),n.add(p),p}function i(o){return!!(o&&typeof o=="object"&&n.has(o))}return Object.freeze({ACTION_KIND:ae,create:r,isNotice:i})}function $n(e={}){return Object.freeze({STATE:P,TONE:$,VARIANT:ye,ACTION_KIND:ae,create(t={}){return _t(t,e)},isSurface:Lt})}function Gn(e,t){let n=e.CarrotUI;if(n&&typeof n=="object"&&Object.isExtensible(n))return n.stateSurface=t,n;let r=n&&typeof n=="object"?{...n}:{};return r.stateSurface=t,e.CarrotUI=r,r}function Ot(e=globalThis,t={}){if(!e||typeof e!="object"&&typeof e!="function")throw new TypeError("installStateSurfaceFacade requires an object target");let n=xt.get(e);if(!n){let r={...t,document:t.document??e.document??globalThis.document},i=$n(r);n=Object.freeze({stateSurface:i,streamOwnershipNotice:qn(i)}),xt.set(e,n)}return Gn(e,n.stateSurface),e.StreamOwnershipNotice=n.streamOwnershipNotice,n.stateSurface}var G=globalThis,se=G.document;se.getElementById("carrotAssetManifest")&&Be(se,G);Ye(G,se);lt(G);mt(G);at(G,{documentRoot:se});Ot(G,{document:se});ht(G);dt(G);$e(G);Tt(G);et(G);})(); +(()=>{var Ut=/^[0-9a-f]{64}$/,Bt=/^(?!\/)(?!.*(?:^|\/)\.\.(?:\/|$))(?!.*\\).+$/,ee=class extends Error{constructor(t,n){super(t,n),this.name="AssetManifestError"}};function qt(e){if(e?.schemaVersion!==1||!Array.isArray(e.assets))throw new ee("Invalid asset manifest schema");let t=new Map,n=new Set;for(let r of e.assets){if(typeof r?.id!="string"||typeof r.path!="string"||!Bt.test(r.path)||typeof r.hash!="string"||!Ut.test(r.hash)||t.has(r.id)||n.has(r.path))throw new ee("Invalid asset manifest entry");t.set(r.id,r),n.add(r.path)}return t}function $t(e){let t=qt(e);return Object.freeze({resolve(n){let r=t.get(n);if(!r)throw new ee(`Unknown asset id: ${n}`);return`/${r.path}?v=${r.hash}`}})}function Ve(e=document,t=globalThis){let n=e.getElementById("carrotAssetManifest");if(!n)throw new ee("Asset manifest element is missing");let r;try{r=JSON.parse(n.textContent||"")}catch(o){throw new ee("Asset manifest JSON is invalid",{cause:o})}let i=$t(r);return t.CarrotAssetUrl=i,i}var ce=Object.freeze({webUi:"web_ui",profile:"profile",restore:"restore",resetDefaults:"reset_defaults",intro:"intro",undo:"undo",device:"device"});function Vt(e){let t=String(e||"").trim();return Object.values(ce).includes(t)?t:ce.webUi}function He(e={}){let t=e.postJson;if(typeof t!="function")throw new TypeError("postJson must be a function");let n=e.endpoint||"/api/param_set",r=typeof e.onCommitted=="function"?e.onCommitted:null,i=typeof e.dispatchEvent=="function"?e.dispatchEvent:null;async function o(s,u,h={}){let T=String(s||"").trim();if(!T)throw new TypeError("A parameter name is required");let d=Vt(h.source),l=await t(n,{name:T,value:u,source:d}),p=l&&Object.prototype.hasOwnProperty.call(l,"value")?l.value:u,c=String(p)!==String(u),y=Object.freeze({name:T,value:p,requested:u,source:d,adjusted:c});return r?.(y),i?.(y),y}return Object.freeze({commit:o,sources:ce})}function Ht(e){return typeof e=="object"&&e!==null||typeof e=="function"}function Ge(e=globalThis,t={}){if(!Ht(e))throw new TypeError("A facade target is required");let n=t.createSettingCommitter||He;if(typeof n!="function")throw new TypeError("A committer factory is required");let r=Object.freeze({create:n,sources:ce});return e.CarrotParamCommit=r,r}var Gt=["a[href]","area[href]","button:not([disabled])",'input:not([disabled]):not([type="hidden"])',"select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])','[contenteditable]:not([contenteditable="false"])',"audio[controls]","video[controls]","iframe","object","embed","summary"].join(",");function Ye(e,t){let n=t.document??e?.ownerDocument??globalThis.document,r=t.window??n?.defaultView??globalThis.window??globalThis,i=t.requestAnimationFrame??r?.requestAnimationFrame?.bind(r)??(u=>globalThis.setTimeout(u,0)),o=t.cancelAnimationFrame??r?.cancelAnimationFrame?.bind(r)??(u=>globalThis.clearTimeout(u)),s=t.getComputedStyle??r?.getComputedStyle?.bind(r)??(()=>({}));return{documentRoot:n,requestFrame:i,cancelFrame:o,getComputedStyle:s}}function Ke(e,t={}){if(!e?.querySelectorAll)return[];let{getComputedStyle:n}=Ye(e,t);return Array.from(e.querySelectorAll(Gt)).filter(r=>{if(r.disabled||r.hidden||r.hasAttribute?.("disabled"))return!1;let i=r.getAttribute?.("tabindex");if(i!=null&&Number(i)<0)return!1;let o=r;for(;o&&o!==e;){if(o.hasAttribute?.("inert")||o.getAttribute?.("aria-hidden")==="true")return!1;o=o.parentElement??o.parentNode}let s=r.getBoundingClientRect?.();if(s&&s.width===0&&s.height===0)return!1;let u=n(r)||{};return u.visibility!=="hidden"&&u.display!=="none"})}function Kt(e,t){let n=typeof t=="function"?t():t;return n?typeof n=="string"?e.querySelector?.(n)??null:typeof n.focus=="function"?n:null:null}function pe(e,t={},n={}){if(!e)return Object.freeze({activate(){},deactivate(){},isActive:()=>!1});let{documentRoot:r,requestFrame:i,cancelFrame:o}=Ye(e,n),s=!1,u=0,h=null,T=null;function d(p){if(!s)return;if(p.key==="Escape"&&typeof t.escape=="function"){t.escape(p);return}if(p.key!=="Tab")return;let c=Ke(e,n);if(!c.length){p.preventDefault(),e.focus?.();return}let y=c[0],x=c[c.length-1],m=r?.activeElement;p.shiftKey?(m===y||!e.contains?.(m))&&(p.preventDefault(),x.focus()):(m===x||!e.contains?.(m))&&(p.preventDefault(),y.focus())}function l(){T!=null&&(o(T),T=null)}return Object.freeze({activate(){if(s)return;s=!0;let p=++u;if(h=t.returnFocus||r?.activeElement||null,e.hasAttribute?.("tabindex")||e.setAttribute?.("tabindex","-1"),r?.addEventListener?.("keydown",d,!0),t.focusInitial===!1)return;let c=Kt(e,t.initialFocus),x=(!c||c===e||typeof e.contains!="function"||e.contains(c)?c:null)||Ke(e,n)[0]||e;T=i(()=>{T=null,!(!s||p!==u)&&x?.focus?.({preventScroll:t.initialFocusPreventScroll!==!1})})},deactivate(p={}){if(!s){u+=1,l();return}s=!1,u+=1,l(),r?.removeEventListener?.("keydown",d,!0);let c=h;if(h=null,p.restoreFocus===!1||typeof c?.focus!="function")return;(typeof r?.contains=="function"?r.contains(c):c.isConnected!==!1)&&c.focus({preventScroll:!0})},isActive(){return s}})}function We(e){if(e&&typeof e=="object"){let n=String(e.mode||"").trim().toLowerCase();if(n==="indeterminate")return{mode:n,value:null};if(n==="determinate"){let r=e.value==null?Number.NaN:Number(e.value);return Number.isFinite(r)?{mode:n,value:Math.max(0,Math.min(100,r))}:{mode:"indeterminate",value:null}}}let t=e==null?Number.NaN:Number(e);return Number.isFinite(t)?{mode:"determinate",value:Math.max(0,Math.min(100,t))}:{mode:"indeterminate",value:null}}function Yt(e,t,n){let r=We(n);if(!e)return r;let i=r.mode==="determinate";if(e.dataset.progressMode=r.mode,e.setAttribute("aria-busy",i?"false":"true"),!i)return e.removeAttribute("value"),e.removeAttribute("aria-valuenow"),e.removeAttribute("data-progress-value"),t&&(t.textContent="…",t.removeAttribute("data-progress-value")),r;let o=Math.round(r.value);return e.max=100,e.value=r.value,e.setAttribute("value",String(r.value)),e.setAttribute("aria-valuenow",String(o)),e.dataset.progressValue=String(r.value),t&&(t.textContent=`${o}%`,t.dataset.progressValue=String(r.value)),r}function re(e,t,n){let r=e.createElement(t);return r.className=n,r}function Xe(e,t={}){if(!e?.createElement)return null;let n=re(e,"div","app-progress"),r=re(e,"div","app-progress__header"),i=re(e,"div","app-progress__message"),o=re(e,"output","app-progress__value"),s=re(e,"progress","app-progress__track"),u=re(e,"div","app-progress__summary");i.setAttribute("aria-live","polite"),o.setAttribute("aria-live","polite"),s.max=100,s.setAttribute("max","100"),t.progressLabel&&s.setAttribute("aria-label",String(t.progressLabel)),r.append(i,o),n.append(r,s,u);let h={element:n,progressElement:s,valueElement:o,setMessage(T){i.textContent=String(T||"")},setProgressState(T){return Yt(s,o,T)},setSummary(T){u.textContent=String(T||"")}};return h.setMessage(t.message),h.setProgressState(t.progressState??t.progress),h.setSummary(t.summary),Object.freeze(h)}var Wt=180,Xt=Object.freeze(["app-dialog--choice","app-dialog--choice-list","app-dialog--choice-grid","app-dialog--choice-value-grid","app-dialog--form","app-dialog--input","app-dialog--progress"]),Jt=Object.freeze(["appDialog","appBranchPicker","appCarPicker","settingSearchPanel"]);function Zt(e){return!e||e.labelHtml?"":String(e.label??"").trim()}function Je(e){return!e||typeof e!="object"?"":String(e.heading??"").trim()}function Qt(e){return!!e&&!Je(e)&&(e.label!=null||!!e.labelHtml)}function en(e){let t=[];for(let n of Array.isArray(e)?e:[]){let r=Je(n);if(r){t.push({heading:r,items:[]});continue}Qt(n)&&(t.length||t.push({heading:"",items:[]}),t[t.length-1].items.push(n))}return t.filter(n=>n.items.length>0)}function tn(e,t={}){let n=String(t.choiceLayout||t.choiceKind||"").trim();return n==="grid"||n==="value-grid"||n==="values"?"value-grid":n==="list"||n==="action-list"||n==="actions"?"list":e.length>4&&e.every(i=>{let o=Zt(i);return o&&o.length<=5&&!i.danger&&/^[+-]?(?:\d+|\d+\.\d+|[A-Za-z]{1,4})$/.test(o)})?"value-grid":"list"}function nn(e,t={}){let n=Array.isArray(e)?e.flatMap(r=>r.items||[]):[];return n.length?e.some(r=>!!r?.heading)?"list":tn(n,t):""}function rn(e,t={}){let n=Number(t.choiceColumns||t.columns);return Number.isInteger(n)&&n>=2&&n<=6?n:e<=4?Math.max(2,e):e<=25?5:4}function on(e){let t=e.target??globalThis,n=e.document??t.document,r=e.setTimeout??t.setTimeout?.bind(t)??globalThis.setTimeout.bind(globalThis),i=e.clearTimeout??t.clearTimeout?.bind(t)??globalThis.clearTimeout.bind(globalThis),o=e.requestAnimationFrame??t.requestAnimationFrame?.bind(t)??(u=>r(u,0)),s=e.cancelAnimationFrame??t.cancelAnimationFrame?.bind(t)??i;return{target:t,documentRoot:n,setTimer:r,requestFrame:o,cancelFrame:s}}function Ze(e={}){let{target:t,documentRoot:n,setTimer:r,requestFrame:i,cancelFrame:o}=on(e),s=a=>n?.getElementById?.(a)??null,u=s("appDialog"),h=s("appDialogBackdrop"),T=s("appDialogTitle"),d=s("appDialogBody"),l=s("appDialogChoices"),p=s("appDialogInputWrap"),c=s("appDialogInput"),y=s("appDialogInputError"),x=s("appDialogCopy"),m=s("appDialogDefault"),g=s("appDialogCancel"),_=s("appDialogConfirm"),L=null,D=0,j=null,I=e.createFocusTrap??((a,v)=>pe(a,v,{document:n,window:t,requestAnimationFrame:i,cancelAnimationFrame:o}));function M(a,v){let O=t.getUIText;return typeof O=="function"?O.call(t,a,v):v}function A(){let a=Jt.some(v=>{let O=s(v);return!!(O&&!O.hidden)});n?.body?.classList?.toggle("dialog-open",a)}function w(){u?.classList?.remove(...Xt),l&&(l.className="app-dialog__choices",l.style.removeProperty("--app-dialog-choice-columns"))}function S(a){return a.mode==="prompt"||a.mode==="form"||a.mode==="choice"?null:!1}function C(a){a.lastFocus&&typeof a.lastFocus.focus=="function"&&a.lastFocus.focus()}function b(a,v){if(a.serial!==D){a.resolve(v);return}u.hidden=!0,A(),w(),l&&(l.hidden=!0,l.innerHTML=""),m&&(m.hidden=!0,m.onclick=null),p&&(p.hidden=!0),y&&(y.hidden=!0,y.textContent=""),c&&(c.disabled=!1,c.value="",c.placeholder="",c.type="text",c.removeAttribute("autocomplete"),c.removeAttribute("autocapitalize"),c.removeAttribute("inputmode"),c.removeAttribute("aria-labelledby"),c.spellcheck=!0),d&&(d.hidden=!1),_&&(_.disabled=!1),g&&(g.disabled=!1),C(a),j=null,a.resolve(v)}function f(a,v){!a||L!==a||a.closing||(a.closing=!0,L=null,j=a.lastFocus||j,a.openFrame!=null&&(o(a.openFrame),a.openFrame=null),a.focusTrap?.deactivate?.({restoreFocus:!1}),u?.classList?.remove("is-open"),a.closeTimer=r(()=>{a.closeTimer=null,b(a,v)},Wt))}function F(a){f(L,a)}async function k(){let a=L;if(!(!a||a.submitting||a.canceling)){if(typeof a.onCancel!="function"){f(a,S(a));return}a.canceling=!0,g&&(g.disabled=!0,g.textContent=a.cancelingLabel);try{await a.onCancel(),a.closeOnCancel&&L===a&&!a.closing&&f(a,S(a))}catch(v){if(L!==a||a.closing)return;a.canceling=!1,g&&(g.disabled=!1,g.textContent=a.cancelLabel),typeof t.showAppToast=="function"&&t.showAppToast(v?.message||String(v),{tone:"error",duration:3600})}}}async function E(){if(!L||L.submitting)return;let a=L,O=a.mode==="prompt"||a.mode==="form"?c?c.value:"":!0;if(a.mode!=="form"||typeof a.onSubmit!="function"){f(a,O);return}a.submitting=!0,y&&(y.hidden=!0,y.textContent=""),c&&(c.disabled=!0),_&&(_.disabled=!0,_.textContent=a.submittingLabel),g&&(g.disabled=!0);try{await a.onSubmit(O),L===a&&!a.closing&&f(a,O)}catch(R){if(L!==a||a.closing)return;a.submitting=!1,c&&(c.disabled=!1,c.focus()),_&&(_.disabled=!1,_.textContent=a.confirmLabel),g&&(g.disabled=!1),y&&(y.textContent=R?.message||String(R),y.hidden=!1)}}function P(a={}){if(!u||!T||!d||!_||!g)return a.mode==="prompt"||a.mode==="form"?Promise.resolve(null):Promise.resolve(a.mode==="alert");let v=j;L&&(v=L.lastFocus||v,f(L,S(L))),j=null;let O=a.mode||"alert",R=O==="form",Z=a.title||(O==="confirm"?M("confirm_title","Confirm"):O==="prompt"||O==="form"?M("input_title","Input"):M("notice","Notice")),W=a.message||"",U=a.messageHtml||"",zt=!!a.html,Pe=a.confirmLabel||M("ok","OK"),Re=a.cancelLabel||M("cancel","Cancel"),ze=a.defaultActionLabel||"",Ue=O==="prompt"&&!!ze,ve=en(a.choices),Be=ve.flatMap(Y=>Y.items),ae=Be.length>0,qe=O==="choice"||ae,se=nn(ve,a),$e=O!=="alert"&&a.showCancel!==!1;w(),R&&u.classList.add("app-dialog--form"),(O==="prompt"||R)&&u.classList.add("app-dialog--input"),ae&&(u.classList.add("app-dialog--choice"),u.classList.add(se==="value-grid"?"app-dialog--choice-grid":"app-dialog--choice-list"),se==="value-grid"&&u.classList.add("app-dialog--choice-value-grid")),T.textContent=Z,zt?d.innerHTML=String(U||W):d.textContent=String(W),d.hidden=R&&!String(U||W).trim(),d.style.flex=ae?"0 0 auto":"1 1 auto",_.textContent=Pe,g.textContent=Re,_.disabled=!1,g.disabled=!1,g.hidden=!$e,g.setAttribute("aria-hidden",$e?"false":"true"),_.hidden=qe,_.setAttribute("aria-hidden",qe?"true":"false"),m&&(m.hidden=!Ue,m.textContent=ze,m.disabled=!1,m.onclick=Ue?()=>F(a.defaultActionValue??""):null);let Ee=a.copyText||"";if(x&&(x.hidden=!Ee,x.textContent=a.copyLabel||M("copy","Copy"),x.onclick=Ee?()=>{let Y=t.copyToClipboard;typeof Y=="function"&&Y.call(t,Ee);let N=t.alert;typeof N=="function"&&N.call(t,M("copied","Copied"))}:null),l){l.innerHTML="",l.hidden=!ae,l.className=`app-dialog__choices app-dialog__choices--${se||"list"}`,se==="value-grid"?l.style.setProperty("--app-dialog-choice-columns",String(rn(Be.length,a))):l.style.removeProperty("--app-dialog-choice-columns");let Y=N=>{let q=n.createElement("button");q.type="button";let z=N.danger?"btn btn--danger app-dialog__choiceBtn":"btn app-dialog__choiceBtn";return z+=se==="value-grid"?" app-dialog__choiceBtn--value":" app-dialog__choiceBtn--action",(N.current||N.selected)&&(z+=" is-current"),N.className&&(z+=` ${N.className}`),q.className=z,N.labelHtml?q.innerHTML=N.labelHtml:q.textContent=String(N.label),q.addEventListener("click",()=>F(N.value)),q};ve.forEach((N,q)=>{if(!N.heading){for(let we of N.items)l.appendChild(Y(we));return}let z=n.createElement("div");z.className="app-dialog__choiceGroup",z.setAttribute("role","group");let ne=`appDialogChoiceHeading${q}`;z.setAttribute("aria-labelledby",ne);let de=n.createElement("div");de.id=ne,de.className="app-dialog__choiceHeading",de.textContent=N.heading,z.appendChild(de);for(let we of N.items)z.appendChild(Y(we));l.appendChild(z)})}if(p&&c){let Y=O==="prompt"||R;p.hidden=!Y,c.disabled=!1,c.value=a.defaultValue??"",c.placeholder=a.placeholder||"",c.type=a.inputType==="password"?"password":"text";let N=a.autocomplete??(R?"off":""),q=a.autocapitalize??(R?"none":"");N?c.autocomplete=N:c.removeAttribute("autocomplete"),q?c.autocapitalize=q:c.removeAttribute("autocapitalize"),c.spellcheck=a.spellcheck==null?!R:a.spellcheck===!0,R?c.setAttribute("aria-labelledby","appDialogTitle"):c.removeAttribute("aria-labelledby"),a.inputMode?c.inputMode=a.inputMode:c.removeAttribute("inputmode"),y&&(y.hidden=!0,y.textContent="")}return new Promise(Y=>{let N={resolve:Y,mode:O,serial:++D,onSubmit:a.onSubmit,onCancel:a.onCancel,closeOnCancel:a.closeOnCancel!==!1,submitting:!1,canceling:!1,closing:!1,confirmLabel:Pe,cancelLabel:Re,cancelingLabel:a.cancelingLabel||M("canceling","Canceling..."),submittingLabel:a.submittingLabel||M("saving","Saving..."),lastFocus:v||n.activeElement||null,focusTrap:null,openFrame:null,closeTimer:null};L=N,u.hidden=!1,A();let q=u.querySelector?.(".app-dialog__sheet")||u;N.focusTrap=I(q,{focusInitial:!1,returnFocus:N.lastFocus}),N.focusTrap?.activate?.(),N.openFrame=i(()=>{if(N.openFrame=null,!(L!==N||N.serial!==D||N.closing))if(u.classList.add("is-open"),(O==="prompt"||R)&&c)c.focus(),c.select();else if(ae&&l){let z=l.querySelector(".is-current"),ne=z||l.querySelector("button");ne&&typeof ne.focus=="function"&&(ne.focus({preventScroll:!!z}),z&&typeof z.scrollIntoView=="function"&&z.scrollIntoView({block:"center",inline:"nearest"}))}else O==="choice"?g.focus():_.focus()})})}function G(a,v={}){return P({mode:"alert",title:v.title,message:a,messageHtml:v.messageHtml,html:v.html,confirmLabel:v.confirmLabel,copyText:v.copyText})}function X(a={}){let v=P({mode:"choice",title:a.title,message:" ",cancelLabel:a.cancelLabel,cancelingLabel:a.cancelingLabel,onCancel:a.onCancel,closeOnCancel:!1}),O=L;u?.classList?.add("app-dialog--progress"),d&&(d.style.flex="0 0 auto");let R=Xe(n,a);d&&R&&d.replaceChildren(R.element);let Z=()=>!!(O&&L===O&&!O.closing),W={completion:v,setCancelHandler(U){Z()&&(O.onCancel=typeof U=="function"?U:null,g&&(g.hidden=!O.onCancel,g.setAttribute("aria-hidden",O.onCancel?"false":"true")))},setCanceling(U){!Z()||!g||(O.canceling=!!U,g.disabled=O.canceling,g.textContent=O.canceling?O.cancelingLabel:O.cancelLabel)},setMessage(U){Z()&&R?.setMessage(U)},setProgressState(U){Z()&&R?.setProgressState(U)},setProgress(U){W.setProgressState(U)},setSummary(U){Z()&&R?.setSummary(U)},close(){return Z()&&f(O,!0),v}};return W.setMessage(a.message),W.setProgressState(a.progressState??a.progress),W.setSummary(a.summary),W.setCancelHandler(a.onCancel),Object.freeze(W)}function K(a,v={}){return P({mode:"confirm",title:v.title,message:a,messageHtml:v.messageHtml,html:v.html,confirmLabel:v.confirmLabel,cancelLabel:v.cancelLabel})}function kt(a,v={}){return P({mode:"prompt",title:v.title,message:a,confirmLabel:v.confirmLabel,cancelLabel:v.cancelLabel,defaultValue:v.defaultValue,defaultActionLabel:v.defaultActionLabel,defaultActionValue:v.defaultActionValue,showCancel:v.showCancel,placeholder:v.placeholder})}function Pt(a,v={}){return P({mode:"form",title:v.title,message:a,defaultValue:v.defaultValue,placeholder:v.placeholder,inputType:v.inputType,autocomplete:v.autocomplete,autocapitalize:v.autocapitalize,inputMode:v.inputMode,spellcheck:v.spellcheck,confirmLabel:v.confirmLabel,cancelLabel:v.cancelLabel,submittingLabel:v.submittingLabel,onSubmit:v.onSubmit})}h&&(h.onclick=k),g&&(g.onclick=k),_&&(_.onclick=E);function Rt(a){if(!L)return;if(a.key==="Escape"){a.preventDefault(),L.mode==="alert"?f(L,!0):k();return}if(a.key!=="Enter"||a.shiftKey||a.isComposing||a.defaultPrevented)return;let v=a.target?.tagName;v==="BUTTON"||v==="SELECT"||v==="TEXTAREA"||v==="A"||v==="INPUT"&&a.target!==c||a.target?.isContentEditable||(a.preventDefault(),E())}return n?.addEventListener?.("keydown",Rt),Object.freeze({openAppDialog:P,openAppProgressDialog:X,appAlert:G,appConfirm:K,appPrompt:kt,appForm:Pt,syncModalBodyLock:A,cancelAppDialog:k,confirmAppDialog:E})}var an=Object.freeze(["openAppDialog","openAppProgressDialog","appAlert","appConfirm","appPrompt","appForm","syncModalBodyLock","createFocusTrap"]);function Qe(e=globalThis,t=e.document,n={}){let r=(h,T={})=>pe(h,T,{document:t,window:e,requestAnimationFrame:n.requestAnimationFrame,cancelAnimationFrame:n.cancelAnimationFrame,getComputedStyle:n.getComputedStyle}),i=Ze({...n,target:e,document:t,createFocusTrap:r}),o=Object.freeze({openAppDialog:i.openAppDialog,openAppProgressDialog:i.openAppProgressDialog,appAlert:i.appAlert,appConfirm:i.appConfirm,appPrompt:i.appPrompt,appForm:i.appForm,syncModalBodyLock:i.syncModalBodyLock,createFocusTrap:r}),s=e.CarrotUI&&typeof e.CarrotUI=="object"?e.CarrotUI:null,u=s&&Object.isExtensible(s)?s:{...s||{}};u.dialog=o,e.CarrotUI=u;for(let h of an)e[h]=o[h];return o}function et(e,t,n){let r=String(t??"");if(n)for(let[i,o]of Object.entries(n))r=r.replaceAll(`{${i}}`,String(o));return r}function tt(e){return String(e??"")}function nt(e){let t=Number(e?.summary?.selected||0);return Number.isFinite(t)&&t>0?t:(Array.isArray(e?.entries)?e.entries:[]).filter(r=>r?.apply).length}function Le(e,t={}){let n=t.escape||tt,r=t.text||et;return'
    '+[["changed","settings_diff_changed","changed"],["same","settings_diff_same","same"],["skipped","settings_diff_skipped","skipped"],["invalid","settings_diff_invalid","invalid"]].map(([o,s,u])=>`
    ${n(r(s,u))}${n(e?.[o]??0)}
    `).join("")+"
    "}function sn(e,t,n){let r=e.filter(o=>o?.status==="invalid");if(!r.length)return"";let i=r.map(o=>`
  • ${t(o.key)}`+(o.reason?`${t(o.reason)}`:"")+"
  • ").join("");return`
    ${t(n("settings_diff_invalid_detail","{count} could not be applied",{count:r.length}))}
      ${i}
    `}function rt(e,t={}){let n=t.escape||tt,r=t.text||et,i=Array.isArray(e?.entries)?e.entries:[],o=i.filter(m=>m?.apply||m?.status==="changed"),s=Number.isFinite(t.limit)?Math.max(1,t.limit):80,u=o.slice(0,s),h=Math.max(0,o.length-u.length),T=t.currentLabel||r("settings_diff_current","Current"),d=t.nextLabel||r("settings_diff_apply","Apply"),l=t.changedLabel||r("settings_diff_changed_status","Changed"),p=sn(i,n,r),c=Le(e?.summary,{escape:n,text:r});if(!o.length)return`${c}
    ${n(r("settings_diff_no_changes","No changes to apply."))}
    `+p;let y=u.map(m=>`
    ${n(m.key)}
    ${n(l)}
    ${n(T)}${n(m.current)}
    ${n(d)}${n(m.value)}
    `).join(""),x=h?`
    ${n(r("settings_diff_more","{count} more changes hidden",{count:h}))}
    `:"";return`${c}
    ${y}
    ${x}${p}`}function it(e){return typeof e=="object"&&e!==null||typeof e=="function"}function ot(e=globalThis,t={}){if(!it(e))throw new TypeError("A facade target is required");let n=Object.freeze({renderTable:t.renderDiffTable||rt,renderSummary:t.renderDiffSummary||Le,selectedCount:t.getDiffSelectedCount||nt}),r=it(e.CarrotUI)?e.CarrotUI:null,i=r&&Object.isExtensible(r)?r:Object.assign({},r||{});return i.diffTable=n,e.CarrotUI=i,n}var ct=Object.freeze({default:"i",info:"i",success:"✓",error:"✕",warn:"!",offline:"!",hint:"i"}),cn=Object.freeze({default:"",info:"is-info",success:"is-success",error:"is-error",warn:"is-warn",offline:"is-warn",hint:"is-hint"}),ln=2600,un=220;function fn(e){let t=typeof e=="string"?e:"default";return Object.hasOwn(ct,t)?t:"default"}function _e(e,t){if(e==null)return t;let n=Number(e);return Number.isFinite(n)&&n>=0?n:t}function at(e,t){return!e||!t?!1:typeof t.contains=="function"?t.contains(e):e.parentNode===t}function st(e){e&&(typeof e.remove=="function"?e.remove():e.parentNode?.removeChild?.(e))}function me(e={},t={}){let n=e.environment||t,r=e.host||null,i=e.documentRoot||n.document||r?.ownerDocument||globalThis.document,o=n.setTimeout||e.setTimeout||globalThis.setTimeout?.bind(globalThis),s=n.clearTimeout||e.clearTimeout||globalThis.clearTimeout?.bind(globalThis),u=n.requestAnimationFrame||e.requestAnimationFrame||(C=>o(C,0)),h=n.cancelAnimationFrame||e.cancelAnimationFrame||s,T=_e(e.defaultDuration,ln),d=_e(e.removeDelay,un),l=0,p=null,c=null,y=null,x=null,m=null,g=!1;function _(){c!=null&&s?.(c),y!=null&&s?.(y),x!=null&&h?.(x),c=null,y=null,x=null}function L(C,b){g||C!==l||b!==p||(st(b),p=null,y=null,m=null)}function D(C,b){if(d===0){L(C,b);return}y=o?.(()=>L(C,b),d)??null}function j(C,b){g||C!==l||b!==p||(b.classList?.remove?.("is-visible"),c=null,D(C,b))}function I(){let C=!!(p&&at(p,r));return Object.freeze({active:C,visible:C&&!!p.classList?.contains?.("is-visible"),message:C?m?.message??"":"",tone:C?m?.tone??"default":null,duration:C?m?.duration??null:null,glyph:C?m?.glyph??"":"",className:C&&p.className||"",serial:l,destroyed:g})}function M(){if(p&&at(p,r))return p;if(!r||typeof i?.createElement!="function")return null;let C=i.createElement("div");if(typeof r.replaceChildren=="function")r.replaceChildren(C);else{for(;r.firstChild;)r.removeChild(r.firstChild);r.appendChild(C)}return p=C,C}function A(C,b={}){if(g||C==null||String(C).length===0)return I();let f=M();if(!f)return I();_();let F=++l,k=fn(b.tone),E=_e(b.duration,T),P=cn[k],G=ct[k];f.className=P?`app-toast ${P}`:"app-toast";let X=i.createElement("span");X.className="app-toast__icon",X.setAttribute?.("aria-hidden","true"),X.textContent=G;let K=i.createElement("div");return K.className="app-toast__msg",K.textContent=String(C),f.replaceChildren(X,K),m={message:String(C),tone:k,duration:E,glyph:G},x=u?.(()=>{g||F!==l||f!==p||(x=null,f.classList?.add?.("is-visible"))})??null,c=o?.(()=>j(F,f),E)??null,I()}function w(C={}){if(g||!p)return I();let b=p;_();let f=++l;return b.classList?.remove?.("is-visible"),C.immediate===!0?L(f,b):D(f,b),I()}function S(){g||(_(),l+=1,st(p),p=null,m=null,g=!0)}return Object.freeze({show:A,dismiss:w,destroy:S,snapshot:I})}var lt=new WeakMap;function ut(e){return typeof e=="object"&&e!==null||typeof e=="function"}function dn(e,t,n){let r=ut(e.CarrotUI)?e.CarrotUI:null,i=r&&Object.isExtensible(r)?r:Object.assign({},r||{});i[t]=n,e.CarrotUI=i}function ft(e=globalThis,t={}){if(!ut(e))throw new TypeError("A facade target is required");let n=lt.get(e),r=t.documentRoot||e.document,i=t.host||r?.getElementById?.("appToastHost")||null,o={document:r,setTimeout:e.setTimeout?.bind?.(e),clearTimeout:e.clearTimeout?.bind?.(e),requestAnimationFrame:e.requestAnimationFrame?.bind?.(e),cancelAnimationFrame:e.cancelAnimationFrame?.bind?.(e),...t.environment||{}},s=t.controller||me({host:i,documentRoot:r,defaultDuration:t.defaultDuration,removeDelay:t.removeDelay,environment:o});if(!s||["show","dismiss","destroy","snapshot"].some(T=>typeof s[T]!="function"))throw new TypeError("A toast controller is required");let u=Object.freeze({create:me,createController:me,show:s.show,dismiss:s.dismiss,destroy:s.destroy,snapshot:s.snapshot});function h(T,d,l){if(d&&typeof d=="object")return s.show(T,d);let p={};return d!=null&&d!==""&&(p.tone=d),l!=null&&(p.duration=l),s.show(T,p)}return dn(e,"toast",u),e.showAppToast=h,n&&n!==s&&n.destroy(),lt.set(e,s),u}var pn='[role^="menuitem"]',be=new WeakMap;function ge(e,t){if(!e||typeof e.addEventListener!="function"||typeof e.removeEventListener!="function")throw new TypeError(`${t} must be an event target`);return e}function dt(e){return!!(e?.disabled||e?.hidden||e?.getAttribute?.("disabled")!=null||e?.getAttribute?.("aria-disabled")==="true"||e?.getAttribute?.("aria-hidden")==="true"||e?.classList?.contains?.("is-disabled"))}function mn(e,t,n){let r=typeof e?.closest=="function"?e.closest(t):null;if(!r){for(let i=e;i&&i!==n;i=i.parentElement||i.parentNode)if(typeof i.matches=="function"&&i.matches(t)){r=i;break}}return r&&n.contains(r)?r:null}function xe(e){typeof e?.focus=="function"&&e.focus()}function ie(e={},t={}){let{root:n,trigger:r,panel:i,itemSelector:o=pn,beforeOpen:s,onSelect:u}=e,h=e.environment||t,T=e.documentRoot||h.document||n?.ownerDocument||globalThis.document;if(ge(n,"root"),ge(r,"trigger"),ge(i,"panel"),ge(T,"documentRoot"),typeof n.contains!="function"||typeof i.contains!="function")throw new TypeError("root and panel must support contains()");if(typeof i.querySelectorAll!="function")throw new TypeError("panel must support querySelectorAll()");if(typeof o!="string"||!o.trim())throw new TypeError("itemSelector must be a non-empty string");let d=be.get(n);d&&d.destroy();let l=r.getAttribute?.("aria-expanded")==="true"||!i.hidden&&n.classList?.contains?.("is-open"),p=!1,c;function y(){return Array.from(i.querySelectorAll(o)).filter(b=>!dt(b))}function x(){return p?!1:(n.classList?.toggle?.("is-open",l),i.hidden=!l,r.setAttribute?.("aria-expanded",l?"true":"false"),i.setAttribute?.("aria-hidden",l?"false":"true"),l)}function m(b){let f=y();if(!f.length)return null;let F=b==="last"?f[f.length-1]:f[0];return xe(F),F}function g(b={}){if(p)return!1;if(l)x();else{if(typeof s=="function"&&s(c)===!1)return!1;l=!0,x()}let f=b===!0||b.focusFirst===!0?"first":b.focusLast===!0?"last":b.focus;return(f==="first"||f==="last")&&m(f),!0}function _(b={}){if(p)return!1;let f=l;return l=!1,x(),b.restoreFocus===!0&&xe(r),f}function L(b={}){return p?!1:l?(_({restoreFocus:b.restoreFocus===!0}),!1):(g(b),l)}function D(b){let f=y();if(!f.length)return!1;let F=h.getActiveElement?.()||T.activeElement,k=f.indexOf(F),E;if(b.key==="ArrowDown")E=k<0?0:(k+1)%f.length;else if(b.key==="ArrowUp")E=k<0?f.length-1:(k-1+f.length)%f.length;else if(b.key==="Home")E=0;else if(b.key==="End")E=f.length-1;else return!1;return b.preventDefault?.(),xe(f[E]),!0}function j(){L()}function I(b){b.key!=="ArrowDown"&&b.key!=="ArrowUp"||(b.preventDefault?.(),g({focus:b.key==="ArrowUp"?"last":"first"}))}function M(b){if(!l)return;let f=mn(b.target,o,i);!f||dt(f)||(_({restoreFocus:!0}),typeof u=="function"&&u(f,b,c))}function A(b){if(l){if(b.key==="Tab"){_();return}if(b.key==="Escape"){b.preventDefault?.(),b.stopPropagation?.(),_({restoreFocus:!0});return}D(b)}}function w(b){if(!l)return;let f=b.target;n.contains(f)||r.contains?.(f)||i.contains(f)||_()}function S(b){!l||b.key!=="Escape"||(b.preventDefault?.(),_({restoreFocus:!0}))}function C(){p||(l&&(l=!1,x()),r.removeEventListener("click",j),r.removeEventListener("keydown",I),i.removeEventListener("click",M),i.removeEventListener("keydown",A),T.removeEventListener("pointerdown",w),T.removeEventListener("keydown",S),p=!0,be.get(n)===c&&be.delete(n))}return c=Object.freeze({open:g,close:_,toggle:L,sync:x,destroy:C,get isOpen(){return!p&&l},get destroyed(){return p}}),r.setAttribute?.("aria-haspopup","menu"),i.id&&r.setAttribute?.("aria-controls",i.id),i.getAttribute?.("role")||i.setAttribute?.("role","menu"),x(),r.addEventListener("click",j),r.addEventListener("keydown",I),i.addEventListener("click",M),i.addEventListener("keydown",A),T.addEventListener("pointerdown",w),T.addEventListener("keydown",S),be.set(n,c),c}function pt(e){return typeof e=="object"&&e!==null||typeof e=="function"}var bn=Object.freeze({create:ie,createController:ie,mount:ie});function mt(e=globalThis,t={}){if(!pt(e))throw new TypeError("A facade target is required");let n=t.createMenuController||ie;if(typeof n!="function")throw new TypeError("A menu controller factory is required");let r=n===ie?bn:Object.freeze({create:n,createController:n,mount:n}),i=pt(e.CarrotUI)?e.CarrotUI:null,o=i&&Object.isExtensible(i)?i:Object.assign({},i||{});return o.menu=r,e.CarrotUI=o,t.compatibility!==!1&&(e.CarrotDropdownMenu=r),r}function gn(e){if(!e||typeof e.addEventListener!="function")throw new TypeError("A commit gesture target element is required");return e}function ye(e,t={}){let n=gn(e),r=t.onCommit;if(typeof r!="function")throw new TypeError("onCommit must be a function");let i=typeof t.onHoldStep=="function"?t.onHoldStep:r,o=Number.isFinite(t.holdDelayMs)?t.holdDelayMs:0,s=Number.isFinite(t.holdIntervalMs)?t.holdIntervalMs:160,u=Number.isFinite(t.moveTolerance)?t.moveTolerance:10,h=Number.isFinite(t.clickSuppressMs)?t.clickSuppressMs:700,T=typeof t.pressedClass=="string"?t.pressedClass:"is-holding",d=typeof t.now=="function"?t.now:()=>Date.now(),l=t.scheduler||globalThis,p=l.setTimeout.bind(l),c=l.clearTimeout.bind(l),y=null,x=null,m=null,g=0;function _(){x!==null&&(c(x),x=null),m!==null&&(c(m),m=null)}function L(){if(!(!y||y.pointerId===void 0||y.pointerId===null)&&typeof n.releasePointerCapture=="function")try{n.releasePointerCapture(y.pointerId)}catch{}}function D(){_(),L(),n.classList?.remove?.(T),y=null}function j(){y&&(y.holdFired=!0,i(),m=p(j,s))}function I(){x=null,y&&j()}function M(f){if(!(f.button!==void 0&&f.button!==0)){if(D(),y={pointerId:f.pointerId,startX:f.clientX,startY:f.clientY,holdFired:!1,cancelled:!1},n.classList?.add?.(T),typeof n.setPointerCapture=="function"&&f.pointerId!==void 0)try{n.setPointerCapture(f.pointerId)}catch{}o>0&&(x=p(I,o))}}function A(f){if(!y||f.pointerId!==void 0&&f.pointerId!==y.pointerId)return;let F=Math.abs(f.clientX-y.startX),k=Math.abs(f.clientY-y.startY);F<=u&&k<=u||(y.cancelled=!0,g=d()+h,D())}function w(f){if(!y||f.pointerId!==void 0&&f.pointerId!==y.pointerId)return;let F=!y.cancelled&&!y.holdFired;g=d()+h,D(),F&&r()}function S(){y&&(y.cancelled=!0,g=d()+h,D())}function C(f){if(d()n.addEventListener(f,F)),Object.freeze({destroy(){D(),g=0,b.forEach(([f,F])=>n.removeEventListener?.(f,F))},get pressed(){return y!==null}})}function An(e,t,n){let r=e;return Number.isFinite(t)&&(r=Math.max(r,t)),Number.isFinite(n)&&(r=Math.min(r,n)),r}function Oe(e={}){let t=Number(e.step),n=Number(e.sign),r=Number(e.min),i=Number(e.max),o=Number(e.current);if(Number.isFinite(o)||(o=Number(e.fallback)),Number.isFinite(o)||(o=Number.isFinite(r)?r:0),!Number.isFinite(t)||!Number.isFinite(n))return o;let s=An(o+n*t,r,i);return Number.isInteger(r)&&Number.isInteger(i)&&Number.isInteger(t)&&(s=Math.round(s)),s}function bt(e={}){let t=e.commit;if(typeof t!="function")throw new TypeError("commit must be a function");let n=typeof e.getValue=="function"?e.getValue:()=>NaN,r=typeof e.getStep=="function"?e.getStep:()=>1,i=typeof e.getRange=="function"?e.getRange:()=>({}),o=typeof e.getFallback=="function"?e.getFallback:()=>NaN,s=Number.isFinite(e.holdDelayMs)?e.holdDelayMs:900,u=Number.isFinite(e.holdIntervalMs)?e.holdIntervalMs:160,h=!1;async function T(p){if(!h){h=!0;try{let c=i()||{},y=Oe({current:n(),fallback:o(),sign:p,step:r(),min:c.min,max:c.max});await t(y)}finally{h=!1}}}let d=[];function l(p,c){p&&d.push(ye(p,{onCommit:()=>{T(c)},holdDelayMs:s,holdIntervalMs:u,pressedClass:e.pressedClass,now:e.now,scheduler:e.scheduler}))}return l(e.minusButton,-1),l(e.plusButton,1),Object.freeze({applyDelta:T,destroy(){d.forEach(p=>p.destroy()),d.length=0}})}function gt(e){return typeof e=="object"&&e!==null||typeof e=="function"}function yt(e=globalThis,t={}){if(!gt(e))throw new TypeError("A facade target is required");let n=t.createNumericStepper||bt,r=t.createCommitGesture||ye;if(typeof n!="function")throw new TypeError("A numeric stepper factory is required");if(typeof r!="function")throw new TypeError("A commit gesture factory is required");let i=Object.freeze({create:n,createGesture:r,nextStepValue:t.nextStepValue||Oe}),o=gt(e.CarrotUI)?e.CarrotUI:null,s=o&&Object.isExtensible(o)?o:Object.assign({},o||{});return s.numericStepper=i,e.CarrotUI=s,e.CarrotNumericStepper=i,i}var he=new WeakMap;function Ae(e,t){if(!e||typeof e.addEventListener!="function"||typeof e.removeEventListener!="function")throw new TypeError(`${t} must be an event target`);return e}function oe(e={},t={}){let{root:n,trigger:r,panel:i,beforeOpen:o,onOpenChange:s}=e,u=e.environment||t,h=e.documentRoot||u.document||n?.ownerDocument||globalThis.document;if(Ae(n,"root"),Ae(r,"trigger"),Ae(i,"panel"),Ae(h,"documentRoot"),typeof n.contains!="function"||typeof i.contains!="function")throw new TypeError("root and panel must support contains()");let T=he.get(n);T&&T.destroy();let d=r.getAttribute?.("aria-expanded")==="true"&&i.hidden!==!0,l=!1,p;function c(){return l?!1:(n.classList?.toggle?.("is-open",d),i.hidden=!d,r.setAttribute?.("aria-expanded",d?"true":"false"),i.setAttribute?.("aria-hidden",d?"false":"true"),typeof s=="function"&&s(d,p),d)}function y(){return l||!d&&typeof o=="function"&&o(p)===!1?!1:(d=!0,c())}function x(I={}){if(l)return!1;let M=d;return d=!1,c(),I.restoreFocus===!0&&r.focus?.(),M}function m(){return d?(x(),!1):y()}function g(){m()}function _(I){I.key!=="Escape"||!d||(I.preventDefault?.(),x({restoreFocus:!0}))}function L(I){!d||n.contains(I.target)||i.contains(I.target)||x()}function D(I){!d||I.key!=="Escape"||(I.preventDefault?.(),x({restoreFocus:!0}))}function j(){l||(d&&(d=!1,c()),r.removeEventListener("click",g),r.removeEventListener("keydown",_),h.removeEventListener("pointerdown",L),h.removeEventListener("keydown",D),l=!0,he.get(n)===p&&he.delete(n))}return p=Object.freeze({open:y,close:x,toggle:m,sync:c,destroy:j,get isOpen(){return!l&&d}}),i.id&&(r.setAttribute?.("aria-controls",i.id),r.setAttribute?.("aria-describedby",i.id)),c(),r.addEventListener("click",g),r.addEventListener("keydown",_),h.addEventListener("pointerdown",L),h.addEventListener("keydown",D),he.set(n,p),p}function ht(e){return typeof e=="object"&&e!==null||typeof e=="function"}var Cn=Object.freeze({create:oe,createController:oe,mount:oe});function At(e=globalThis,t={}){if(!ht(e))throw new TypeError("A facade target is required");let n=t.createPopoverController||oe;if(typeof n!="function")throw new TypeError("A popover controller factory is required");let r=n===oe?Cn:Object.freeze({create:n,createController:n,mount:n}),i=ht(e.CarrotUI)?e.CarrotUI:null,o=i&&Object.isExtensible(i)?i:Object.assign({},i||{});return o.popover=r,e.CarrotUI=o,r}var Ce=new WeakMap;function le(e,t){return typeof e?.getAttribute=="function"?e.getAttribute(t):null}function Te(e){return e?.disabled===!0||typeof e?.hasAttribute=="function"&&e.hasAttribute("disabled")?!0:String(le(e,"aria-disabled")||"").toLowerCase()==="true"}function Tn(e,t){let n=le(e,t);return n!==null&&String(n).toLowerCase()!=="false"}function Sn(e){if(e?.dataset&&Object.prototype.hasOwnProperty.call(e.dataset,"value"))return String(e.dataset.value);let t=le(e,"data-value");if(t!==null)return t;let n=le(e,"value");return n!==null?n:e?.value!==void 0&&e.value!==null?String(e.value):null}function vn(e,t){return e===t?!0:typeof e?.contains=="function"&&e.contains(t)}function En(e){let t=String(e?.itemSelector||"").trim();if(!t)throw new TypeError("A segmented control itemSelector is required");return Object.freeze({itemSelector:t,selectedAttribute:String(e?.selectedAttribute||"aria-selected"),orientation:e?.orientation==="vertical"?"vertical":"horizontal",activation:e?.activation==="manual"?"manual":"automatic",onActivate:typeof e?.onActivate=="function"?e.onActivate:null})}function wn(e){if(!e||typeof e!="object"||typeof e.querySelectorAll!="function"||typeof e.addEventListener!="function"||typeof e.removeEventListener!="function")throw new TypeError("A segmented control root element is required")}function Ct(e,t={}){wn(e);let n=Ce.get(e);if(n)return n.sync(),n;let r=En(t),i=new Map,o=[],s=null,u=!1,h=null;function T(){return Array.from(e.querySelectorAll(r.itemSelector))}function d(A){i.has(A)||i.set(A,le(A,"tabindex"))}function l(A,w){d(A),A.setAttribute("tabindex",String(w))}function p(A=o){return A.find(w=>Tn(w,r.selectedAttribute))||null}function c({preferSelected:A=!0}={}){o=T();let w=o.filter(b=>!Te(b)),S=p(o),C=S&&w.includes(S)?S:null;A&&C?s=C:w.includes(s)||(s=C||w[0]||null);for(let b of o)l(b,b===s?0:-1);return o}function y(){let A=T(),w=p(A),S=w?A.indexOf(w):-1;return Object.freeze({itemCount:A.length,selectedIndex:S,selectedValue:w?Sn(w):null,destroyed:u})}function x(){return u||c({preferSelected:!0}),y()}function m(A){return o.find(w=>vn(w,A?.target))||null}function g(A){s=A;for(let w of o)l(w,w===A?0:-1)}function _(A,w,S){if(!r.onActivate)return;let C=o.indexOf(A);try{r.onActivate(A,C,Object.freeze({source:S,event:w,controller:h}))}finally{u||c({preferSelected:!1})}}function L(A,w){A&&(w?.preventDefault?.(),g(A),typeof A.focus=="function"&&A.focus(),r.activation==="automatic"&&_(A,w,"keyboard"))}function D(A,w){c({preferSelected:!1});let S=o.filter(k=>!Te(k));if(!S.length)return null;let C=m(A),b=S.includes(C)?C:S.includes(s)?s:S[0],f=S.indexOf(b);return w==="first"?S[0]:w==="last"?S[S.length-1]:S[(f+(w==="previous"?-1:1)+S.length)%S.length]}function j(A){if(u||A?.isComposing||A?.altKey||A?.ctrlKey||A?.metaKey)return;let w=null;if(A.key==="Home"?w="first":A.key==="End"?w="last":r.orientation==="horizontal"&&A.key==="ArrowLeft"?w="previous":r.orientation==="horizontal"&&A.key==="ArrowRight"?w="next":r.orientation==="vertical"&&A.key==="ArrowUp"?w="previous":r.orientation==="vertical"&&A.key==="ArrowDown"&&(w="next"),w){L(D(A,w),A);return}if(r.activation!=="manual"||A.key!=="Enter"&&A.key!==" "&&A.key!=="Spacebar")return;c({preferSelected:!1});let S=m(A);!S||Te(S)||(A.preventDefault?.(),g(S),_(S,A,"keyboard"))}function I(A){if(u)return;c({preferSelected:!1});let w=m(A);w&&!Te(w)&&g(w)}function M(){if(u)return!1;u=!0,e.removeEventListener("keydown",j),e.removeEventListener("focusin",I);for(let[A,w]of i)w===null?A.removeAttribute("tabindex"):A.setAttribute("tabindex",w);return i.clear(),o=[],s=null,Ce.get(e)===h&&Ce.delete(e),!0}return h=Object.freeze({sync:x,destroy:M,snapshot:y}),Ce.set(e,h),e.addEventListener("keydown",j),e.addEventListener("focusin",I),x(),h}function Tt(e){return typeof e=="object"&&e!==null||typeof e=="function"}function St(e=globalThis,t={}){if(!Tt(e))throw new TypeError("A facade target is required");let n=t.createSegmentedControl||Ct;if(typeof n!="function")throw new TypeError("A segmented control factory is required");let r=Object.freeze({create:n}),i=Tt(e.CarrotUI)?e.CarrotUI:null,o=i&&Object.isExtensible(i)?i:Object.assign({},i||{});return o.segmentedControl=r,e.CarrotUI=o,e.CarrotSegmentedControl=r,r}var Ie=Object.freeze(["toggle","segmented","select","stepper","slider"]);function Q(e,t,n){let r=e.createElement(t);return n&&(r.className=n),r}function J(e,t,{text:n,label:r}={}){let i=Q(e,"button",t);return i.type="button",n!==void 0&&(i.textContent=n),r&&i.setAttribute("aria-label",r),i}function Ln(e,t,n,r){let i=Q(e,"label","c-switch"),o=Q(e,"input","c-switch__input");o.type="checkbox",o.setAttribute("aria-label",r.label),i.appendChild(o),i.appendChild(Q(e,"span","c-switch__track")),t.appendChild(i),n.toggleInput=o}function _n(e,t,n,r){let i=Q(e,"div","setting-segments");r.optionValues.forEach(o=>{let s=J(e,"setting-segment",{text:r.optionLabel(o)});s.dataset.value=String(o),s.setAttribute("aria-pressed","false"),n.segmentButtons.push(s),i.appendChild(s)}),t.appendChild(i),n.segmentGroup=i}function xn(e,t,n,r){let i=J(e,"setting-select setting-select--button",{label:r.label});i.setAttribute("aria-haspopup","dialog"),t.appendChild(i),n.selectInput=i}function On(e,t,n,r){n.minusButton=J(e,"smallBtn setting-value-arrow setting-value-arrow--prev",{text:"-",label:r.previousLabel}),n.plusButton=J(e,"smallBtn setting-value-arrow setting-value-arrow--next",{text:"+",label:r.nextLabel}),n.unitButton=J(e,"setting-unit-cycle"),t.appendChild(n.minusButton),t.appendChild(n.value),t.appendChild(n.plusButton)}function In(e,t,n,r){let i=Q(e,"div","setting-slider"),o=Q(e,"input","setting-slider__input");o.type="range",o.min=String(r.min),o.max=String(r.max),o.step=String(r.step),o.setAttribute("aria-label",r.label),i.appendChild(o),n.sliderInput=o,n.minusButton=J(e,"smallBtn setting-step setting-step--minus",{text:"-"}),n.plusButton=J(e,"smallBtn setting-step setting-step--plus",{text:"+"}),n.unitButton=J(e,"setting-unit-cycle"),t.appendChild(i),t.appendChild(n.minusButton),t.appendChild(n.value),t.appendChild(n.plusButton)}function vt(e={}){let t=e.document;if(!t||typeof t.createElement!="function")throw new TypeError("A document is required to build a setting control");let n=String(e.kind||"");if(!Ie.includes(n))throw new TypeError(`Unknown setting control kind: ${n}`);let r=n==="stepper",i=Q(t,"div",`ctrl ctrl--${r?"value":n}`),o=J(t,r?"value-surface val setting-value-compact":"value-surface val",{label:e.valueLabel}),s={ctrl:i,value:o,toggleInput:null,segmentGroup:null,segmentButtons:[],selectInput:null,sliderInput:null,minusButton:null,plusButton:null,unitButton:null},u={label:e.label||"",valueLabel:e.valueLabel||"",previousLabel:e.previousLabel||"",nextLabel:e.nextLabel||"",optionValues:Array.isArray(e.optionValues)?e.optionValues:[],optionLabel:typeof e.optionLabel=="function"?e.optionLabel:h=>String(h),min:e.min,max:e.max,step:e.step};return n==="toggle"?Ln(t,i,s,u):n==="segmented"?_n(t,i,s,u):n==="select"?xn(t,i,s,u):r?On(t,i,s,u):In(t,i,s,u),!r&&n!=="slider"&&i.appendChild(o),s}function Et(e){return typeof e=="object"&&e!==null||typeof e=="function"}function wt(e=globalThis,t={}){if(!Et(e))throw new TypeError("A facade target is required");let n=t.createSettingControl||vt;if(typeof n!="function")throw new TypeError("A setting control factory is required");let r=Object.freeze({createControl:n,kinds:Ie}),i=Et(e.CarrotUI)?e.CarrotUI:null,o=i&&Object.isExtensible(i)?i:Object.assign({},i||{});return o.settingRow=r,e.CarrotUI=o,r}var Nn=new Set(["primary","secondary","danger"]);function $(e){return String(e??"").trim()}function Dn(e){if(typeof e!="function")return $(e);try{return $(e())}catch{return""}}function Fn(e,t){let n=t?.document??e.environment?.document??e.document??globalThis.document;if(!n||typeof n.createElement!="function")throw new TypeError("createActionGroup requires a document environment");return n}function jn(e){let t=$(e);return Nn.has(t)?t:"secondary"}function Ne(e){if(!Array.isArray(e))return Object.freeze([]);if(e.length>2)throw new RangeError("State surface action groups support at most 2 actions");let t=new Set,n=[];for(let r of e){let i=r&&typeof r=="object"?r:{},o=$(i.id),s=Dn(i.label),u=i.onActivate??i.invoke;!o||t.has(o)||!s||typeof u!="function"||(t.add(o),n.push(Object.freeze({id:o,label:s,tone:jn(i.tone??i.kind),disabled:!!i.disabled,onActivate:u})))}return Object.freeze(n)}function Lt(e){return Array.from(new Set(e.flatMap(t=>$(t).split(/\s+/)).filter(Boolean))).join(" ")}function _t(e={},t={}){let n=e.host;if(!n||typeof n.append!="function")return null;let r=Fn(e,t),i=r.createElement("div");i.setAttribute("role","group"),i.setAttribute("aria-busy","false"),i.dataset.layout="single",i.dataset.sizing="full",i.dataset.pending="false",i.hidden=!0,n.append(i);let o=new Map,s=Object.freeze([]),u=new Map,h=null,T=0,d=!1,l=$(e.ariaLabel)||"Status actions",p=$(e.className),c=$(e.actionClassName),y=$(e.actionToneClassPrefix),x=typeof e.onPendingChange=="function"?e.onPendingChange:null;function m(){return h?.id||""}function g(S){return Lt(["btn",S.tone==="primary"?"btn--filled":"",S.tone==="danger"?"btn--danger":"","c-action-group__action",`c-action-group__action--${S.tone}`,c,y?`${y}${S.tone}`:""])}function _(){let S=s.length<=1;return Lt(["c-action-group","c-action-group--nowrap",S?"c-action-group--single":"c-action-group--split",S?"c-action-group--full":"c-action-group--equal",p])}function L(){if(d)return;let S=!!h,C=s.length<=1;i.className=_(),i.hidden=s.length===0,i.dataset.layout=C?"single":"split",i.dataset.sizing=C?"full":"equal",i.dataset.pending=String(S),i.setAttribute("aria-label",l),i.setAttribute("aria-busy",String(S));for(let b of s){let f=o.get(b.id).button,F=S||b.disabled;f.className=g(b),f.dataset.actionId=b.id,f.dataset.tone=b.tone,f.textContent=b.label,f.disabled=F,f.tabIndex=F?-1:0,f.setAttribute("aria-disabled",String(F)),f.setAttribute("tabindex",String(f.tabIndex)),F?f.setAttribute("disabled",""):f.removeAttribute("disabled")}x?.(m())}async function D(S){let C=u.get($(S));if(d||!C||C.disabled||h)return!1;let b=Object.freeze({id:C.id,generation:++T});h=b,L();try{await C.onActivate()}catch{}finally{if(d||h!==b)return!0;h=null,L()}return!0}function j(S){let C=o.get(S);C&&(C.button.removeEventListener("click",C.listener),C.button.remove(),o.delete(S))}function I(S,C={}){if(d)return!1;let b=Ne(S),f=new Set(b.map(E=>E.id));h&&!f.has(h.id)&&(h=null,T+=1);for(let E of o.keys())f.has(E)||j(E);for(let E of b){if(o.has(E.id))continue;let P=r.createElement("button");P.type="button";let G=()=>{D(E.id)};P.addEventListener("click",G),o.set(E.id,{button:P,listener:G})}s=b,u=new Map(s.map(E=>[E.id,E])),C.ariaLabel!=null&&(l=$(C.ariaLabel)||"Status actions");let F=Array.from(i.children).map(E=>E.dataset.actionId),k=s.map(E=>E.id);if(F.join("\0")!==k.join("\0"))for(let E of s)i.append(o.get(E.id).button);return L(),!0}function M(S){return d?!1:(l=$(S)||"Status actions",L(),!0)}function A(){let S=s.map(C=>Object.freeze({id:C.id,label:C.label,tone:C.tone,disabled:C.disabled}));return Object.freeze({actions:Object.freeze(S),pendingActionId:m(),layout:s.length<=1?"single":"split",sizing:s.length<=1?"full":"equal"})}function w(){if(d)return!1;d=!0,h=null,T+=1;for(let S of Array.from(o.keys()))j(S);return s=Object.freeze([]),u=new Map,i.remove(),!0}return Object.freeze({element:i,setActions:I,setAriaLabel:M,activate:D,snapshot:A,destroy:w})}var B=Object.freeze({EMPTY:"empty",LOADING:"loading",DEGRADED:"degraded",ERROR:"error",OWNERSHIP:"ownership"}),V=Object.freeze({NEUTRAL:"neutral",INFO:"info",WARNING:"warning",ERROR:"error"}),ue=Object.freeze({PRIMARY:"primary",SECONDARY:"secondary",DANGER:"danger"}),Se=Object.freeze({COVER:"cover",COMPACT:"compact"}),Mn=new Set(Object.values(B)),kn=new Set(Object.values(V)),Pn=new Set(Object.values(Se)),Rn=Object.freeze({[B.EMPTY]:V.NEUTRAL,[B.LOADING]:V.INFO,[B.DEGRADED]:V.WARNING,[B.ERROR]:V.ERROR,[B.OWNERSHIP]:V.WARNING}),De=new WeakMap,Me=new WeakSet,zn=0;function te(e){return String(e??"").trim()}function Fe(e){if(typeof e!="function")return te(e);try{return te(e())}catch{return""}}function je(e){return Array.from(new Set(e.flatMap(t=>te(t).split(/\s+/)).filter(Boolean))).join(" ")}function xt(e){let t=te(e);return Mn.has(t)?t:B.EMPTY}function Un(e,t){let n=te(e);return kn.has(n)?n:Rn[t]}function Ot(e){let t=te(e);return Pn.has(t)?t:Se.COVER}function It(e){return e&&typeof e=="object"?e.featureLabel??e.label??e.name:e}function Bn(e,t){let n=t?.document??e.environment?.document??e.document??globalThis.document;if(!n||typeof n.createElement!="function")throw new TypeError("createStateSurface requires a document environment");return n}function qn(e,t){if(typeof e=="string")return{state:xt(e),descriptor:t&&typeof t=="object"?t:{}};let n=e&&typeof e=="object"?e:{};return{state:xt(n.state),descriptor:n}}function Nt(e={},t={}){let n=e.host;if(!n||typeof n.append!="function")return null;let r=De.get(n);if(r){let E=e.featureLabel??e.feature;return E!=null&&r.setFeature(E),r}let i=Bn(e,t),o=`c-state-surface-${++zn}`,s=te(e.className),u=Ot(e.variant),h=It(e.featureLabel??e.feature),T=null,d=null,l=B.EMPTY,p=V.NEUTRAL,c=u,y="",x=!1,m=i.createElement("section");m.className=je(["c-state-surface",`c-state-surface--${p}`,`c-state-surface--state-${l}`,`c-state-surface--${c}`,s]),m.hidden=!0,m.setAttribute("aria-hidden","true"),m.setAttribute("aria-busy","false"),m.dataset.state=l,m.dataset.tone=p,m.dataset.variant=c,m.dataset.pending="false";let g=i.createElement("div");g.className="c-state-surface__body";let _=i.createElement("div");_.className="c-state-surface__copy",_.setAttribute("role","status"),_.setAttribute("aria-live","polite"),_.setAttribute("aria-atomic","true");let L=i.createElement("h2");L.id=`${o}-feature`,L.className="c-state-surface__feature";let D=i.createElement("p");D.id=`${o}-title`,D.className="c-state-surface__title";let j=i.createElement("p");j.id=`${o}-description`,j.className="c-state-surface__description",_.append(L,D,j),g.append(_),m.append(g),n.append(m);function I(){let E=l===B.LOADING||!!y;m.dataset.pending=String(!!y),m.setAttribute("aria-busy",String(E))}let M=_t({host:g,className:"c-state-surface__actions",actionClassName:"c-state-surface__action",actionToneClassPrefix:"c-state-surface__action--",onPendingChange(E){y=E,I()}},{document:i});function A(E,P){let G=P.featureLabel!=null?P.featureLabel:h;return Object.freeze({state:E,featureLabel:Fe(G),title:Fe(P.title),description:Fe(P.description),tone:Un(P.tone,E),variant:Ot(P.variant??u),actions:Ne(P.actions)})}function w(E){l=E.state,p=E.tone,c=E.variant,m.className=je(["c-state-surface",`c-state-surface--${p}`,`c-state-surface--state-${l}`,`c-state-surface--${c}`,s]),m.dataset.state=l,m.dataset.tone=p,m.dataset.variant=c,L.textContent=E.featureLabel,L.hidden=!E.featureLabel,D.textContent=E.title,D.hidden=!E.title,j.textContent=E.description,j.hidden=!E.description,E.title||E.featureLabel?(m.setAttribute("aria-labelledby",E.title?D.id:L.id),m.removeAttribute("aria-label")):(m.removeAttribute("aria-labelledby"),m.setAttribute("aria-label","Status")),E.description?m.setAttribute("aria-describedby",j.id):m.removeAttribute("aria-describedby")}function S(E={},P){if(x)return!1;let{state:G,descriptor:X}=qn(E,P),K=A(G,X);return T={...X},d=K,w(K),M.setActions(K.actions,{ariaLabel:`${K.featureLabel||K.title||"Status"} actions`}),m.hidden=!1,m.setAttribute("aria-hidden","false"),I(),!0}function C(){return x||m.hidden?!1:(T=null,d=null,l=B.EMPTY,p=V.NEUTRAL,c=u,M.setActions([]),y="",m.hidden=!0,m.setAttribute("aria-hidden","true"),m.dataset.state=l,m.dataset.tone=p,m.dataset.variant=c,m.className=je(["c-state-surface",`c-state-surface--${p}`,`c-state-surface--state-${l}`,`c-state-surface--${c}`,s]),I(),!0)}function b(E){return x?!1:(h=It(E),m.hidden||!T?!0:S(l,T))}function f(){let E=M.snapshot();return Object.freeze({visible:!x&&!m.hidden,state:d?.state||B.EMPTY,featureLabel:d?.featureLabel||"",title:d?.title||"",description:d?.description||"",tone:d?.tone||V.NEUTRAL,variant:d?.variant||u,actions:E.actions,pendingActionId:E.pendingActionId})}function F(){return x?!1:(x=!0,T=null,d=null,y="",M.destroy(),De.delete(n),Me.delete(k),m.remove(),!0)}let k=Object.freeze({show:S,hide:C,setFeature:b,snapshot:f,destroy:F});return De.set(n,k),Me.add(k),k}function Dt(e){return!!(e&&typeof e=="object"&&Me.has(e))}var Ft=new WeakMap,$n=new Set(Object.values(ue));function ke(e){return String(e??"").trim()}function Vn(e){if(typeof e!="function")return ke(e);try{return ke(e())}catch{return""}}function jt(e){if(typeof e=="string"||typeof e=="function")return Object.freeze({id:"",label:e});let t=e&&typeof e=="object"?e:{};return Object.freeze({id:t.id,label:t.label??t.name})}function Hn(e){let t=ke(e);return $n.has(t)?t:ue.SECONDARY}function Gn(e){return Array.isArray(e)?e.map(t=>{let n=t&&typeof t=="object"?t:{};return{id:n.id,label:n.label,tone:Hn(n.kind??n.tone),disabled:!!n.disabled,onActivate:n.invoke??n.onActivate}}):[]}function Kn(e){let t=new WeakMap,n=new WeakSet;function r(o={}){let s=o.host;if(!s||typeof s.append!="function")return null;let u=t.get(s);if(u)return o.feature!=null&&u.setFeature(o.feature),u;let h=jt(o.feature),T=e.create({host:s,className:o.className,featureLabel:h.label});if(!T)return null;let d=!1;function l(g={}){if(d)return!1;let _=g&&typeof g=="object"?g:{};return T.show(B.OWNERSHIP,{title:_.title,description:_.description,tone:V.NEUTRAL,actions:Gn(_.actions)})}function p(){return d?!1:T.hide()}function c(g){return d?!1:(h=jt(g),T.setFeature(h.label))}function y(){let g=T.snapshot(),_=g.actions.map(L=>Object.freeze({id:L.id,label:L.label,kind:L.tone,disabled:L.disabled}));return Object.freeze({visible:g.visible,featureId:g.visible?Vn(h.id):"",featureLabel:g.featureLabel,title:g.title,description:g.description,actions:Object.freeze(_),pendingActionId:g.pendingActionId})}function x(){return d?!1:(d=!0,t.delete(s),n.delete(m),T.destroy(),!0)}let m=Object.freeze({show:l,hide:p,setFeature:c,snapshot:y,destroy:x});return t.set(s,m),n.add(m),m}function i(o){return!!(o&&typeof o=="object"&&n.has(o))}return Object.freeze({ACTION_KIND:ue,create:r,isNotice:i})}function Yn(e={}){return Object.freeze({STATE:B,TONE:V,VARIANT:Se,ACTION_KIND:ue,create(t={}){return Nt(t,e)},isSurface:Dt})}function Wn(e,t){let n=e.CarrotUI;if(n&&typeof n=="object"&&Object.isExtensible(n))return n.stateSurface=t,n;let r=n&&typeof n=="object"?{...n}:{};return r.stateSurface=t,e.CarrotUI=r,r}function Mt(e=globalThis,t={}){if(!e||typeof e!="object"&&typeof e!="function")throw new TypeError("installStateSurfaceFacade requires an object target");let n=Ft.get(e);if(!n){let r={...t,document:t.document??e.document??globalThis.document},i=Yn(r);n=Object.freeze({stateSurface:i,streamOwnershipNotice:Kn(i)}),Ft.set(e,n)}return Wn(e,n.stateSurface),e.StreamOwnershipNotice=n.streamOwnershipNotice,n.stateSurface}var H=globalThis,fe=H.document;fe.getElementById("carrotAssetManifest")&&Ve(fe,H);Qe(H,fe);mt(H);At(H);ft(H,{documentRoot:fe});Mt(H,{document:fe});St(H);yt(H);Ge(H);wt(H);ot(H);})(); diff --git a/openpilot/selfdrive/carrot/web/js/generated/logs.js b/openpilot/selfdrive/carrot/web/js/generated/logs.js index c7f11a4b18..b93008b99a 100644 --- a/openpilot/selfdrive/carrot/web/js/generated/logs.js +++ b/openpilot/selfdrive/carrot/web/js/generated/logs.js @@ -1,8 +1,8 @@ /* @generated by web/build.mjs; edit web/src/entries/logs.js instead. */ -(()=>{(()=>{let t=document.querySelector(".logs-shell"),e=document.getElementById("logsDashcamPanel"),a=document.getElementById("logsScreenPanel"),n=document.getElementById("dashcamRoutes"),s=document.getElementById("screenrecordVideos"),r=[n,s].filter(Boolean);if(!t||!r.length)return;let o=0;function i(){return e&&!e.hidden?n:a&&!a.hidden?s:r.find(d=>d.offsetWidth>0)||null}function l(){o=0;let d=i();if(!d||d.offsetWidth<=0)return;let m=window.getComputedStyle(d),g=(Number.parseFloat(m.borderLeftWidth)||0)+(Number.parseFloat(m.borderRightWidth)||0),f=Math.max(0,Math.round(d.offsetWidth-d.clientWidth-g));t.style.setProperty("--logs-scrollbar-gutter",`${f}px`)}function u(){o||(o=window.requestAnimationFrame(l))}if(typeof ResizeObserver=="function"){let d=new ResizeObserver(u);d.observe(t),r.forEach(m=>d.observe(m))}if(typeof MutationObserver=="function"){let d=new MutationObserver(u);e&&d.observe(e,{attributes:!0,attributeFilter:["hidden"]}),a&&d.observe(a,{attributes:!0,attributeFilter:["hidden"]})}window.addEventListener("resize",u,{passive:!0}),window.addEventListener("carrot:pagechange",u),u()})();var Kt=Object.freeze({READING:"reading",RECENT:"recent"});function P(t){return String(t||"").trim()}function Yt(t){let e=new Set,a=[];for(let n of Array.isArray(t)?t:[]){let s=P(n);!s||e.has(s)||(e.add(s),a.push(s))}return a}function on(t,e){let a=t&&typeof t=="object"?t:{},n=Number(a.segmentCount),s=Number.isFinite(n)?Math.max(0,Math.trunc(n)):e;return Object.freeze({route:String(a.route||""),dateLabel:String(a.dateLabel||""),timeRange:String(a.timeRange||""),segmentCount:s})}function Xt(t,e){let a=P(e);return a?a===t?.activeSegment?Kt.READING:a===t?.previousSegment?Kt.RECENT:"":""}function cn(t={}){let e=P(t.activeSegment),a=P(t.previousSegment),n=new Set;a===e&&(a="");let s=()=>Object.freeze({activeSegment:e,previousSegment:a}),r=g=>Object.freeze({changed:g,state:s()}),o=()=>{let g=s();n.forEach(f=>f(g))};return Object.freeze({finish:()=>e?(a=e,e="",o(),r(!0)):r(!1),restoreRecent:g=>{let f=P(g);return!f||f===e||f===a?r(!1):(a=f,o(),r(!0))},select:g=>{let f=P(g);return!f||f===e?r(!1):(e?a=e:a===f&&(a=""),e=f,o(),r(!0))},snapshot:s,statusFor:g=>Xt({activeSegment:e,previousSegment:a},g),subscribe:g=>typeof g!="function"?()=>{}:(n.add(g),()=>n.delete(g))})}var ee=cn();function Zt(t={}){let e=Yt(t.segments),a=on(t.group,e.length),n=e.includes(P(t.activeSegment))?P(t.activeSegment):e[0]||"",s=e.includes(P(t.previousSegment))?P(t.previousSegment):"";s===n&&(s="");let r=()=>{let m=e.indexOf(n);return Object.freeze({group:a,segments:Object.freeze([...e]),activeSegment:n,previousSegment:s,activeIndex:m,previousIndex:e.indexOf(s),canMovePrevious:m>0,canMoveNext:m>=0&&mObject.freeze({changed:m,state:r()}),i=m=>{let g=P(m);return!e.includes(g)||g===n?o(!1):(s=n,n=g,o(!0))};return Object.freeze({move:m=>{let g=Math.sign(Number(m)||0),f=e.indexOf(n);if(!g||f<0)return o(!1);let h=f+g;return h<0||h>=e.length?o(!1):i(e[h])},replaceSegments:(m,g={})=>{let f=Yt(m),h=P(g.activeSegment??n),p=P(g.previousSegment??s),x=f.includes(h)?h:f[0]||"",$=f.includes(p)&&p!==x?p:"",S=f.length!==e.length||f.some((T,k)=>T!==e[k])||x!==n||$!==s;return e=f,n=x,s=$,o(S)},select:i,snapshot:r,statusFor:m=>Xt({activeSegment:n,previousSegment:s},m)})}function I(t,e="",a=""){let n=document.createElement(t);return e&&(n.className=e),a!==""&&(n.textContent=String(a)),n}function ln(t,e={}){let a=String(t||"");for(let[n,s]of Object.entries(e))a=a.replaceAll(`{${n}}`,String(s));return a}var dn=new Set(["reading","recent"]);function un(t){if(!t||typeof t!="object")return null;let e=Number(t.segmentCount);return Object.freeze({route:String(t.route||"").trim(),title:String(t.title||t.route||"").trim(),dateLabel:String(t.dateLabel||"").trim(),timeRange:String(t.timeRange||"").trim(),segmentCount:Number.isFinite(e)?Math.max(0,Math.trunc(e)):0})}function Ge(t){let e=new Set,a=[];for(let n of Array.isArray(t)?t:[]){if(!n||typeof n!="object")continue;let s=String(n.id||"").trim();!s||e.has(s)||(e.add(s),a.push(Object.freeze({id:s,name:String(n.name||s).trim(),timeLabel:String(n.timeLabel||"").trim(),title:String(n.title||n.name||s).trim(),subtitle:String(n.subtitle||"").trim(),src:String(n.src||"").trim(),thumbnailSrc:String(n.thumbnailSrc||"").trim()})))}return Object.freeze(a)}function _t(t,e={}){let a=String(t||"").trim().toLowerCase();if(!dn.has(a))return null;let n=I("span","dashcam-player-status-tag",a==="reading"?e.reading:e.recent);return n.dataset.state=a,n}function mn(t,e={}){let a=un(t);if(!a)return null;let n=I("section","dashcam-player-group-summary"),s=I("h2","dashcam-player-section-title",e.currentGroup),r=I("div","dashcam-player-group-summary__body"),o=I("div","dashcam-player-group-summary__main"),i=I("strong","dashcam-player-group-summary__title",a.title||a.route),l=I("span","dashcam-player-group-summary__count",ln(e.segmentCount,{count:a.segmentCount})),u=I("span","dashcam-player-group-summary__time",[a.dateLabel,a.timeRange].filter(Boolean).join(" · "));return s.id="dashcamPlayerCurrentGroupTitle",n.setAttribute("aria-labelledby",s.id),o.append(i,l),r.append(o,u),n.append(s,r),Object.freeze({element:n,group:a})}function Qt(t,e={}){let a=I("div","dashcam-player-segment-browser"),n=I("h2","dashcam-player-section-title",e.labels?.segments),s=I("div","dashcam-player-segment-list"),r=Object.freeze([]),o=typeof e.statusFor=="function"?e.statusFor:null;n.id="dashcamPlayerSegmentListTitle",s.setAttribute("aria-labelledby",n.id),a.append(n,s);let i=()=>{s.querySelectorAll(".dashcam-player-segment-item").forEach(m=>{let g=m.querySelector(".dashcam-player-segment-status"),f=o?.(m.dataset.segmentId)||"",h=_t(f,e.labels);g?.replaceChildren(...h?[h]:[]),g&&(g.hidden=!h),m.setAttribute("aria-label",[m.dataset.segmentLabel,h?.textContent].filter(Boolean).join(" · "))})},l=(m,g=!1)=>{let f=String(m||""),h=null;return s.querySelectorAll(".dashcam-player-segment-item").forEach(p=>{let x=p.dataset.segmentId===f;p.classList.toggle("is-active",x),x?(p.setAttribute("aria-current","true"),h=p):p.removeAttribute("aria-current")}),i(),g&&h&&h.scrollIntoView({block:"nearest"}),h},u=m=>{o=typeof m=="function"?m:null,i()},d=(m,g="")=>{r=Ge(m);let f=document.createDocumentFragment();for(let h of r){let p=I("button","dashcam-player-segment-item"),x=I("span","dashcam-player-segment-thumb"),$=I("span","dashcam-player-segment-copy"),S=I("span","dashcam-player-segment-meta"),T=I("span","dashcam-player-segment-time",h.timeLabel),k=I("span","dashcam-player-segment-status"),w=I("span","dashcam-player-segment-name",h.name),H=[h.timeLabel,h.name].filter(Boolean).join(" · ");if(p.type="button",p.dataset.segmentId=h.id,p.dataset.segmentLabel=H,p.setAttribute("aria-label",H),h.thumbnailSrc){let O=I("img");O.src=h.thumbnailSrc,O.alt="",O.loading="lazy",O.decoding="async",x.append(O)}else x.classList.add("is-empty");k.hidden=!0,S.append(w,k),$.append(T,S),p.append(x,$),p.addEventListener("click",()=>e.onSelect?.(h.id)),f.append(p)}return s.replaceChildren(f),l(g),r};return d(t,e.activeSegmentId),Object.freeze({element:a,get segments(){return r},render:d,setActive:l,setStatusFor:u})}function ea(t={}){let e=I("div","dashcam-player-dialog");e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true");let a=I("div","dashcam-player-layout"),n=I("div","dashcam-player-frame"),s=mn(t.currentGroup,t.labels),r=null,o=null;return a.append(n),s&&(e.classList.add("dashcam-player-dialog--browsable"),r=I("aside","dashcam-player-browser"),r.setAttribute("aria-label",String(t.labels?.browser||"")),o=I("section","dashcam-player-segment-region"),o.hidden=!0,r.append(s.element,o),a.append(r)),e.append(a),Object.freeze({browser:r,dialog:e,frame:n,group:s?.group||null,layout:a,segmentHost:o})}var ta=40,gn=720,aa=8,b={initialized:!1,loading:!1,loadingMore:!1,loadSeq:0,videos:[],rowHeight:80,windowStart:0,windowEnd:0,total:0,nextOffset:0,hasMore:!1,signature:"",renderFrame:0};function Ve(t,e=""){let a=document.getElementById("screenrecordStatus");a&&(a.textContent=t||"",a.hidden=!t,a.classList.toggle("is-error",e==="error"))}function Je(t,e){return`/api/screenrecord/${t}/${encodeURIComponent(e)}`}function sa(t){return(t||[]).map(e=>[e.id||"",e.name||"",e.modifiedLabel||"",e.size||0].join("|")).join(` -`)+"|"+(typeof LANG<"u"?LANG:"")}function ra(t){return!t||!b.hasMore||b.loading||b.loadingMore?!1:t.scrollHeight-t.scrollTop-t.clientHeight<=gn}function pn(t,e){let a=Math.max(48,Number(b.rowHeight)||80),n=Math.max(1,t?.clientHeight||a*8),s=Math.max(0,t?.scrollTop||0),r=Math.ceil(n/a),o=Math.max(0,Math.floor(s/a)-aa),i=Math.min(e,o+r+aa*2);return{start:o,end:i,rowHeight:a}}function fn(t){let e=t?.querySelector?.(".screenrecord-row");if(!e)return;let a=window.getComputedStyle?.(t),n=Number.parseFloat(a?.rowGap||a?.gap||"0")||0,s=Math.max(48,e.getBoundingClientRect().height+n);Math.abs(s-b.rowHeight)<1||(b.rowHeight=s)}function na(t,e){if(t<=0)return null;let a=document.createElement("div");return a.className="screenrecord-virtual-spacer",a.dataset.spacer=e,a.style.height=`${Math.round(t)}px`,a}function hn(t,e,a){let n=String(t?.id||""),s=n?a.get(n):null;if(s)return s.style.setProperty("--i",String(e)),s.classList.remove("ui-stagger-item"),s;let r=document.createElement("template");return r.innerHTML=bn(t,e),r.content.firstElementChild}function yn(t,e,a){let n=new Map(Array.from(t.querySelectorAll(".screenrecord-row")).map(i=>[i.dataset.id||"",i]).filter(([i])=>!!i)),s=document.createDocumentFragment(),r=na(a.start*a.rowHeight,"top"),o=na((e.length-a.end)*a.rowHeight,"bottom");r&&s.appendChild(r),e.slice(a.start,a.end).forEach((i,l)=>{let u=hn(i,a.start+l,n);u&&s.appendChild(u)}),o&&s.appendChild(o),Qe(t),t.replaceChildren(s)}function ge(t){let e=document.getElementById("screenrecordVideos");e&&e.classList.toggle("is-loading-more",!!t)}function Ke(){b.renderFrame||(b.renderFrame=requestAnimationFrame(()=>{b.renderFrame=0,q({preserve:!0})}))}function We(t,e){if(!t)return;let a=b.videos||[],n=a.findIndex(o=>String(o?.id||"")===String(t)),s=n>0?a[n-1]:null,r=n>=0&&n{o?.(),We(s.id||"",s.name||"")}:null,onNext:r?({close:o}={})=>{o?.(),We(r.id||"",r.name||"")}:null})}function bn(t,e=0){let a=escapeHtml(t.id||""),n=escapeHtml(t.name||"-"),s=escapeHtml(Ye(t.modifiedEpoch)||Xe(t.modifiedLabel||t.relativeModifiedLabel)||"-"),r=escapeHtml(Ze(t.size)),o=escapeHtml((t.ext||"video").toUpperCase());return`
    +(()=>{(()=>{let t=document.querySelector(".logs-shell"),e=document.getElementById("logsDashcamPanel"),a=document.getElementById("logsScreenPanel"),n=document.getElementById("dashcamRoutes"),s=document.getElementById("screenrecordVideos"),r=[n,s].filter(Boolean);if(!t||!r.length)return;let o=0;function c(){return e&&!e.hidden?n:a&&!a.hidden?s:r.find(u=>u.offsetWidth>0)||null}function l(){o=0;let u=c();if(!u||u.offsetWidth<=0)return;let g=window.getComputedStyle(u),m=(Number.parseFloat(g.borderLeftWidth)||0)+(Number.parseFloat(g.borderRightWidth)||0),f=Math.max(0,Math.round(u.offsetWidth-u.clientWidth-m));t.style.setProperty("--logs-scrollbar-gutter",`${f}px`)}function d(){o||(o=window.requestAnimationFrame(l))}if(typeof ResizeObserver=="function"){let u=new ResizeObserver(d);u.observe(t),r.forEach(g=>u.observe(g))}if(typeof MutationObserver=="function"){let u=new MutationObserver(d);e&&u.observe(e,{attributes:!0,attributeFilter:["hidden"]}),a&&u.observe(a,{attributes:!0,attributeFilter:["hidden"]})}window.addEventListener("resize",d,{passive:!0}),window.addEventListener("carrot:pagechange",d),d()})();var Zt=Object.freeze({READING:"reading",RECENT:"recent"});function P(t){return String(t||"").trim()}function Qt(t){let e=new Set,a=[];for(let n of Array.isArray(t)?t:[]){let s=P(n);!s||e.has(s)||(e.add(s),a.push(s))}return a}function hn(t,e){let a=t&&typeof t=="object"?t:{},n=Number(a.segmentCount),s=Number.isFinite(n)?Math.max(0,Math.trunc(n)):e;return Object.freeze({route:String(a.route||""),dateLabel:String(a.dateLabel||""),timeRange:String(a.timeRange||""),segmentCount:s})}function ea(t,e){let a=P(e);return a?a===t?.activeSegment?Zt.READING:a===t?.previousSegment?Zt.RECENT:"":""}function yn(t={}){let e=P(t.activeSegment),a=P(t.previousSegment),n=new Set;a===e&&(a="");let s=()=>Object.freeze({activeSegment:e,previousSegment:a}),r=m=>Object.freeze({changed:m,state:s()}),o=()=>{let m=s();n.forEach(f=>f(m))};return Object.freeze({finish:()=>e?(a=e,e="",o(),r(!0)):r(!1),restoreRecent:m=>{let f=P(m);return!f||f===e||f===a?r(!1):(a=f,o(),r(!0))},select:m=>{let f=P(m);return!f||f===e?r(!1):(e?a=e:a===f&&(a=""),e=f,o(),r(!0))},snapshot:s,statusFor:m=>ea({activeSegment:e,previousSegment:a},m),subscribe:m=>typeof m!="function"?()=>{}:(n.add(m),()=>n.delete(m))})}var ee=yn();function ta(t={}){let e=Qt(t.segments),a=hn(t.group,e.length),n=e.includes(P(t.activeSegment))?P(t.activeSegment):e[0]||"",s=e.includes(P(t.previousSegment))?P(t.previousSegment):"";s===n&&(s="");let r=()=>{let g=e.indexOf(n);return Object.freeze({group:a,segments:Object.freeze([...e]),activeSegment:n,previousSegment:s,activeIndex:g,previousIndex:e.indexOf(s),canMovePrevious:g>0,canMoveNext:g>=0&&gObject.freeze({changed:g,state:r()}),c=g=>{let m=P(g);return!e.includes(m)||m===n?o(!1):(s=n,n=m,o(!0))};return Object.freeze({move:g=>{let m=Math.sign(Number(g)||0),f=e.indexOf(n);if(!m||f<0)return o(!1);let h=f+m;return h<0||h>=e.length?o(!1):c(e[h])},replaceSegments:(g,m={})=>{let f=Qt(g),h=P(m.activeSegment??n),p=P(m.previousSegment??s),x=f.includes(h)?h:f[0]||"",$=f.includes(p)&&p!==x?p:"",b=f.length!==e.length||f.some((T,k)=>T!==e[k])||x!==n||$!==s;return e=f,n=x,s=$,o(b)},select:c,snapshot:r,statusFor:g=>ea({activeSegment:n,previousSegment:s},g)})}function I(t,e="",a=""){let n=document.createElement(t);return e&&(n.className=e),a!==""&&(n.textContent=String(a)),n}function Sn(t,e={}){let a=String(t||"");for(let[n,s]of Object.entries(e))a=a.replaceAll(`{${n}}`,String(s));return a}var bn=new Set(["reading","recent"]);function _n(t){if(!t||typeof t!="object")return null;let e=Number(t.segmentCount);return Object.freeze({route:String(t.route||"").trim(),title:String(t.title||t.route||"").trim(),dateLabel:String(t.dateLabel||"").trim(),timeRange:String(t.timeRange||"").trim(),segmentCount:Number.isFinite(e)?Math.max(0,Math.trunc(e)):0})}function Ve(t){let e=new Set,a=[];for(let n of Array.isArray(t)?t:[]){if(!n||typeof n!="object")continue;let s=String(n.id||"").trim();!s||e.has(s)||(e.add(s),a.push(Object.freeze({id:s,name:String(n.name||s).trim(),timeLabel:String(n.timeLabel||"").trim(),title:String(n.title||n.name||s).trim(),subtitle:String(n.subtitle||"").trim(),src:String(n.src||"").trim(),thumbnailSrc:String(n.thumbnailSrc||"").trim()})))}return Object.freeze(a)}function vt(t,e={}){let a=String(t||"").trim().toLowerCase();if(!bn.has(a))return null;let n=I("span","dashcam-player-status-tag",a==="reading"?e.reading:e.recent);return n.dataset.state=a,n}function vn(t,e={}){let a=_n(t);if(!a)return null;let n=I("section","dashcam-player-group-summary"),s=I("h2","dashcam-player-section-title",e.currentGroup),r=I("div","dashcam-player-group-summary__body"),o=I("div","dashcam-player-group-summary__main"),c=I("strong","dashcam-player-group-summary__title",a.title||a.route),l=I("span","dashcam-player-group-summary__count",Sn(e.segmentCount,{count:a.segmentCount})),d=I("span","dashcam-player-group-summary__time",[a.dateLabel,a.timeRange].filter(Boolean).join(" · "));return s.id="dashcamPlayerCurrentGroupTitle",n.setAttribute("aria-labelledby",s.id),o.append(c,l),r.append(o,d),n.append(s,r),Object.freeze({element:n,group:a})}function aa(t,e={}){let a=I("div","dashcam-player-segment-browser"),n=I("h2","dashcam-player-section-title",e.labels?.segments),s=I("div","dashcam-player-segment-list"),r=Object.freeze([]),o=typeof e.statusFor=="function"?e.statusFor:null;n.id="dashcamPlayerSegmentListTitle",s.setAttribute("aria-labelledby",n.id),a.append(n,s);let c=()=>{s.querySelectorAll(".dashcam-player-segment-item").forEach(g=>{let m=g.querySelector(".dashcam-player-segment-status"),f=o?.(g.dataset.segmentId)||"",h=vt(f,e.labels);m?.replaceChildren(...h?[h]:[]),m&&(m.hidden=!h),g.setAttribute("aria-label",[g.dataset.segmentLabel,h?.textContent].filter(Boolean).join(" · "))})},l=(g,m=!1)=>{let f=String(g||""),h=null;return s.querySelectorAll(".dashcam-player-segment-item").forEach(p=>{let x=p.dataset.segmentId===f;p.classList.toggle("is-active",x),x?(p.setAttribute("aria-current","true"),h=p):p.removeAttribute("aria-current")}),c(),m&&h&&h.scrollIntoView({block:"nearest"}),h},d=g=>{o=typeof g=="function"?g:null,c()},u=(g,m="")=>{r=Ve(g);let f=document.createDocumentFragment();for(let h of r){let p=I("button","dashcam-player-segment-item"),x=I("span","dashcam-player-segment-thumb"),$=I("span","dashcam-player-segment-copy"),b=I("span","dashcam-player-segment-meta"),T=I("span","dashcam-player-segment-time",h.timeLabel),k=I("span","dashcam-player-segment-status"),w=I("span","dashcam-player-segment-name",h.name),O=[h.timeLabel,h.name].filter(Boolean).join(" · ");if(p.type="button",p.dataset.segmentId=h.id,p.dataset.segmentLabel=O,p.setAttribute("aria-label",O),h.thumbnailSrc){let H=I("img");H.src=h.thumbnailSrc,H.alt="",H.loading="lazy",H.decoding="async",x.append(H)}else x.classList.add("is-empty");k.hidden=!0,b.append(w,k),$.append(T,b),p.append(x,$),p.addEventListener("click",()=>e.onSelect?.(h.id)),f.append(p)}return s.replaceChildren(f),l(m),r};return u(t,e.activeSegmentId),Object.freeze({element:a,get segments(){return r},render:u,setActive:l,setStatusFor:d})}function na(t={}){let e=I("div","dashcam-player-dialog");e.setAttribute("role","dialog"),e.setAttribute("aria-modal","true");let a=I("div","dashcam-player-layout"),n=I("div","dashcam-player-frame"),s=vn(t.currentGroup,t.labels),r=null,o=null;return a.append(n),s&&(e.classList.add("dashcam-player-dialog--browsable"),r=I("aside","dashcam-player-browser"),r.setAttribute("aria-label",String(t.labels?.browser||"")),o=I("section","dashcam-player-segment-region"),o.hidden=!0,r.append(s.element,o),a.append(r)),e.append(a),Object.freeze({browser:r,dialog:e,frame:n,group:s?.group||null,layout:a,segmentHost:o})}var sa=40,wn=720,ra=8,S={initialized:!1,loading:!1,loadingMore:!1,loadSeq:0,videos:[],rowHeight:80,windowStart:0,windowEnd:0,total:0,nextOffset:0,hasMore:!1,signature:"",renderFrame:0};function We(t,e=""){let a=document.getElementById("screenrecordStatus");a&&(a.textContent=t||"",a.hidden=!t,a.classList.toggle("is-error",e==="error"))}function Ke(t,e){return`/api/screenrecord/${t}/${encodeURIComponent(e)}`}function ca(t){return(t||[]).map(e=>[e.id||"",e.name||"",e.modifiedLabel||"",e.size||0].join("|")).join(` +`)+"|"+(typeof LANG<"u"?LANG:"")}function ia(t){return!t||!S.hasMore||S.loading||S.loadingMore?!1:t.scrollHeight-t.scrollTop-t.clientHeight<=wn}function xn(t,e){let a=Math.max(48,Number(S.rowHeight)||80),n=Math.max(1,t?.clientHeight||a*8),s=Math.max(0,t?.scrollTop||0),r=Math.ceil(n/a),o=Math.max(0,Math.floor(s/a)-ra),c=Math.min(e,o+r+ra*2);return{start:o,end:c,rowHeight:a}}function Tn(t){let e=t?.querySelector?.(".screenrecord-row");if(!e)return;let a=window.getComputedStyle?.(t),n=Number.parseFloat(a?.rowGap||a?.gap||"0")||0,s=Math.max(48,e.getBoundingClientRect().height+n);Math.abs(s-S.rowHeight)<1||(S.rowHeight=s)}function oa(t,e){if(t<=0)return null;let a=document.createElement("div");return a.className="screenrecord-virtual-spacer",a.dataset.spacer=e,a.style.height=`${Math.round(t)}px`,a}function Mn(t,e,a){let n=String(t?.id||""),s=n?a.get(n):null;if(s)return s.style.setProperty("--i",String(e)),s.classList.remove("ui-stagger-item"),s;let r=document.createElement("template");return r.innerHTML=Ln(t,e),r.content.firstElementChild}function An(t,e,a){let n=new Map(Array.from(t.querySelectorAll(".screenrecord-row")).map(c=>[c.dataset.id||"",c]).filter(([c])=>!!c)),s=document.createDocumentFragment(),r=oa(a.start*a.rowHeight,"top"),o=oa((e.length-a.end)*a.rowHeight,"bottom");r&&s.appendChild(r),e.slice(a.start,a.end).forEach((c,l)=>{let d=Mn(c,a.start+l,n);d&&s.appendChild(d)}),o&&s.appendChild(o),Qe(t),t.replaceChildren(s)}function ge(t){let e=document.getElementById("screenrecordVideos");e&&e.classList.toggle("is-loading-more",!!t)}function Ye(){S.renderFrame||(S.renderFrame=requestAnimationFrame(()=>{S.renderFrame=0,q({preserve:!0})}))}function Je(t,e){if(!t)return;let a=S.videos||[],n=a.findIndex(o=>String(o?.id||"")===String(t)),s=n>0?a[n-1]:null,r=n>=0&&n{o?.(),Je(s.id||"",s.name||"")}:null,onNext:r?({close:o}={})=>{o?.(),Je(r.id||"",r.name||"")}:null})}function Ln(t,e=0){let a=escapeHtml(t.id||""),n=escapeHtml(t.name||"-"),s=escapeHtml(Xe(t.modifiedEpoch)||Ze(t.modifiedLabel||t.relativeModifiedLabel)||"-"),r=escapeHtml(le(t.size)),o=escapeHtml((t.ext||"video").toUpperCase());return`
    ${n}
    @@ -15,37 +15,37 @@ -
    `}function q(t={}){let e=document.getElementById("screenrecordVideos");if(!e||!C())return;let a=t.preserve===!0,n=b.videos||[];if(b.loading&&!n.length){Ve(""),e.innerHTML=et("screen"),e.dataset.signature="",e.dataset.renderCount="0";return}if(!n.length){e.innerHTML=tt("screen"),e.dataset.signature="",e.dataset.renderCount="0",Ve("");return}Ve("");let s=pn(e,n.length),r=`${b.signature||sa(n)}|${s.start}:${s.end}|${b.loadingMore?"more":""}`;if(a&&e.dataset.signature===r){ie(e);return}yn(e,n,s),e.dataset.signature=r,e.dataset.renderCount=String(s.end-s.start),b.windowStart=s.start,b.windowEnd=s.end,ge(b.loadingMore),ie(e),requestAnimationFrame(()=>fn(e))}async function te({silent:t=!1,append:e=!1}={}){if(e&&(!b.hasMore||b.loading||b.loadingMore))return;let a=++b.loadSeq;e?(b.loadingMore=!0,ge(!0)):t||(b.loading=!0,b.loadingMore=!1,ge(!1),q());try{let n=e&&(b.nextOffset||b.videos.length)||0,s=e?ta:Math.max(ta,b.videos.length||0),r=await getJson(`/api/screenrecord/videos?offset=${n}&limit=${s}`);if(a!==b.loadSeq)return;if(!C()){b.loading=!1,b.loadingMore=!1,ge(!1);return}let o=Array.isArray(r.videos)?r.videos:[],i=e?b.videos.concat(o):o,l=sa(i);if(t&&l===b.signature){b.loading=!1,b.loadingMore=!1,ge(!1);return}b.videos=i,b.signature=l,b.total=Number.isFinite(Number(r.total))?Number(r.total):i.length,b.nextOffset=r.nextOffset==null?i.length:Number(r.nextOffset)||i.length,b.hasMore=!!r.hasMore,b.loading=!1,b.loadingMore=!1,ge(!1),q({animate:!t}),!t&&we.screen===0&&Re("screen",{reset:!0})}catch(n){if(a!==b.loadSeq)return;b.loading=!1,b.loadingMore=!1,ge(!1),!t&&C()&&(Ve(`${getUIText("screenrecord_load_failed","Failed to load screen recordings")}: ${n.message||n}`,"error"),showAppToast(n.message||getUIText("screenrecord_load_failed","Failed to load screen recordings"),{tone:"error"}))}}function wt(t){let e=new Error(t);return e.code=t,e}async function oa(t,e={}){let a=await fetch(`/api/dashcam/summary-source/${encodeURIComponent(t)}`,{cache:"no-store",signal:e.signal}),n=null;try{n=await a.json()}catch{}if(!a.ok||n?.ok===!1)throw wt("summary-source-unavailable");if(n?.mode!=="client-worker"||!Array.isArray(n.segments))throw wt("summary-source-invalid");let s=n.segments.filter(r=>r&&typeof r.url=="string"&&["zstd","bzip2","none"].includes(r.compression));if(!s.length)throw wt("summary-source-empty");return Object.freeze({route:String(n.route||t),schemaVersion:Number(n.schemaVersion)||1,segmentCount:Math.max(s.length,Number(n.segmentCount)||0),skippedSegments:Math.max(0,Number(n.skippedSegments)||0),segments:Object.freeze(s.map(r=>Object.freeze({segment:String(r.segment||""),index:Number(r.index)||0,kind:r.kind==="qlog"?"qlog":"rlog",name:String(r.name||""),url:r.url,size:Math.max(0,Number(r.size)||0),modifiedMs:Math.max(0,Number(r.modifiedMs)||0),compression:r.compression})))})}var ae=new Map;function ca(t){return!t||!Array.isArray(t.segments)||!t.segments.length?"":JSON.stringify([String(t.route||""),Number(t.schemaVersion)||0,Number(t.segmentCount)||0,Number(t.skippedSegments)||0,t.segments.map(e=>[String(e.segment||""),Number(e.index)||0,String(e.kind||""),String(e.name||""),Number(e.size)||0,Number(e.modifiedMs)||0,String(e.compression||"")])])}function ia(t){let e=ca(t);if(!e||!ae.has(e))return null;let a=ae.get(e);return ae.delete(e),ae.set(e,a),a}function la(t,e){let a=ca(t);if(!a||!e?.ok)return!1;for(ae.delete(a),ae.set(a,e);ae.size>6;)ae.delete(ae.keys().next().value);return!0}function A(t,e="",a=""){let n=document.createElement(t);return e&&(n.className=e),a!==""&&(n.textContent=String(a)),n}function Ue(t){let e=A("section","route-summary__section"),a=A("h3","route-summary__section-title",t);return e.append(a),Object.freeze({section:e,heading:a})}function xt(t,e,a={}){let n=A("div","route-summary-metric");a.variant&&(n.dataset.variant=a.variant);let s=A("dt","route-summary-metric__label",t);a.hideLabel&&s.classList.add("visually-hidden");let r=A("dd","route-summary-metric__value");return r.append(A("span","route-summary-metric__number",e.value)),e.unit&&r.append(A("span","route-summary-metric__unit",e.unit)),n.append(s,r),n}function da(t,e){let a=A("div","route-summary-composition__stat");return a.dataset.kind=e.key,e.emphasis&&(a.dataset.emphasis="true"),a.append(A("span","route-summary-composition__label",t),A("span","route-summary-composition__duration",e.duration),A("span","route-summary-composition__ratio",`${e.ratio.toFixed(1)}%`)),a}function Sn(t){let e=Object.fromEntries(t.map(o=>[o.key,Math.max(0,Number(o.seconds)||0)])),a=e.auto||0,n=e.manual||0,s=e.gas||0,r=e.brake||0;if(s+r>n&&s+r>0){let o=n/(s+r);s*=o,r*=o}return Object.freeze({auto:a,manual:n,gas:s,brake:r,other:Math.max(0,n-s-r)})}function nt(t,e){let a=A("span","route-summary-distribution__segment");return a.dataset.kind=t,a.style.setProperty("--route-summary-weight",String(e)),a}function ua(t){let e=A("div","route-summary-distribution");e.setAttribute("role","img"),e.setAttribute("aria-label",t.map(n=>`${n.label} ${n.ratio.toFixed(1)}%`).join(", "));let a=Sn(t);if(a.auto>0&&e.append(nt("auto",a.auto)),a.manual>0){let n=A("span","route-summary-distribution__manual");n.style.setProperty("--route-summary-weight",String(a.manual)),a.gas>0&&n.append(nt("gas",a.gas)),a.brake>0&&n.append(nt("brake",a.brake)),a.other>0&&n.append(nt("other",a.other)),e.append(n)}return e}function ma(t,e,a={}){let n=A("div","route-summary-event");a.kind&&(n.dataset.kind=a.kind),a.critical&&e.count>0&&(n.dataset.tone="danger");let s=A("div","route-summary-event__head");if(s.append(A("span","route-summary-event__label",t),A("span","route-summary-event__count",String(e.count))),n.append(s),e.items.length){let r=A("ul","route-summary-event__times");for(let o of e.items){let i=A("li","c-chip route-summary-event__time");i.append(A("time","",o.time)),o.peak&&i.append(A("span","route-summary-event__peak",o.peak)),r.append(i)}n.append(r)}return n}function ga(t,e,a){let n=A("div","route-summary-detail"),s=e.unit==="count"?`${e.value}${a}`:`${e.value} ${e.unit}`.trim();return n.append(A("dt","route-summary-detail__label",t),A("dd","route-summary-detail__value",s)),n}function pa(t,e=""){let a=A("div","route-summary-state");a.setAttribute("role","status"),a.setAttribute("aria-live","polite"),a.append(A("strong","route-summary-state__title",t));let n=A("span","route-summary-state__description",e);n.hidden=!e;let s=A("div","route-summary-state__progress");s.setAttribute("role","progressbar"),s.setAttribute("aria-label",t),s.setAttribute("aria-valuemin","0"),s.setAttribute("aria-valuemax","100"),s.dataset.mode="indeterminate";let r=A("span","route-summary-state__progress-fill");return s.append(r),a.append(n,s),Object.freeze({element:a,update(o,i){let l=Number(o);if(Number.isFinite(l)){let u=Math.max(0,Math.min(1,l)),d=Math.round(u*100);s.dataset.mode="determinate",s.setAttribute("aria-valuenow",String(d)),r.style.setProperty("--route-summary-progress",String(d))}else s.dataset.mode="indeterminate",s.removeAttribute("aria-valuenow"),r.style.removeProperty("--route-summary-progress");n.textContent=String(i||""),n.hidden=!n.textContent}})}function _(t,e=0){let a=Number(t);return Number.isFinite(a)?a:e}function W(t,e=1){return _(t).toLocaleString(void 0,{minimumFractionDigits:e,maximumFractionDigits:e})}function xe(t){let e=Math.max(0,Math.round(_(t))),a=Math.floor(e/3600),n=Math.floor(e%3600/60),s=e%60;return`${String(a).padStart(2,"0")}:${String(n).padStart(2,"0")}:${String(s).padStart(2,"0")}`}function Tt(t){return _(t)<=0?"":new Intl.DateTimeFormat(void 0,{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).format(new Date(_(t)*1e3))}function vn(t){return _(t)<=0?"":new Intl.DateTimeFormat(void 0,{year:"numeric",month:"2-digit",day:"2-digit"}).format(new Date(_(t)*1e3))}function _n(t,e){if(_(t?.wallTimeSec)>0)return Tt(t.wallTimeSec);let a=Math.max(0,_(t?.monoSec)-_(e)),n=Math.floor(a/60),s=Math.floor(a%60);return`+${String(n).padStart(2,"0")}:${String(s).padStart(2,"0")}`}function st(t,e){let a=Array.isArray(t?.items)?t.items:[];return Object.freeze({count:Math.max(0,_(t?.count)),items:Object.freeze(a.map(n=>Object.freeze({time:_n(n,e),peak:n?.peak==null?"":W(n.peak,2)})))})}function fa(t){if(!t?.ok)throw new TypeError("Invalid route summary result");let e=t.time||{},a=t.distance||{},n=t.extras||{},s=t.diagnostics||{},r=Math.max(0,_(e.totalSec)),o=Math.max(0,_(e.autoEnabledSec)),i=Math.max(0,_(e.manualSec)),l=x=>r>0?Math.max(0,Math.min(100,x/r*100)):0,u=Tt(e.startWallSec),d=Tt(e.endWallSec),m=vn(e.startWallSec),g=_(e.firstMonoSec),f=Math.max(0,_(s.processedSegments)),h=Math.max(f,_(s.requestedSegments)),p=Math.max(0,_(s.partialSegments));return Object.freeze({route:String(t.route||""),hasData:!!t.hasData,date:m,timeRange:u&&d?`${u}–${d}`:u,metrics:Object.freeze([Object.freeze({key:"duration",value:xe(r),unit:""}),Object.freeze({key:"distance",value:W(_(a.totalM)/1e3,2),unit:"km"}),Object.freeze({key:"averageSpeed",value:W(_(a.averageSpeedMs)*3.6,1),unit:"km/h"}),Object.freeze({key:"autoRatio",value:W(l(o),1),unit:"%"})]),distanceMetrics:Object.freeze([Object.freeze({key:"totalDistance",value:W(_(a.totalM)/1e3,2),unit:"km"}),Object.freeze({key:"autoDistance",value:W(_(a.autoM)/1e3,2),unit:"km"}),Object.freeze({key:"manualDistance",value:W(_(a.manualM)/1e3,2),unit:"km"}),Object.freeze({key:"averageSpeed",value:W(_(a.averageSpeedMs)*3.6,1),unit:"km/h"}),Object.freeze({key:"maxSpeed",value:W(_(a.maxSpeedMs)*3.6,1),unit:"km/h"})]),composition:Object.freeze([Object.freeze({key:"auto",seconds:o,duration:xe(o),ratio:l(o),emphasis:!0}),Object.freeze({key:"manual",seconds:i,duration:xe(i),ratio:l(i),emphasis:!1}),Object.freeze({key:"gas",seconds:_(e.manualGasSec),duration:xe(e.manualGasSec),ratio:l(_(e.manualGasSec)),emphasis:!1}),Object.freeze({key:"brake",seconds:_(e.manualBrakeSec),duration:xe(e.manualBrakeSec),ratio:l(_(e.manualBrakeSec)),emphasis:!1})]),events:Object.freeze({hardAccel:st(t.events?.hardAccel,g),overAccel:st(t.events?.overAccel,g),hardDecel:st(t.events?.hardDecel,g),overDecel:st(t.events?.overDecel,g)}),details:Object.freeze([Object.freeze({key:"stops",value:String(Math.max(0,_(n.stopCount))),unit:"count"}),Object.freeze({key:"disengagements",value:String(Math.max(0,_(n.disengageCount))),unit:"count"}),Object.freeze({key:"steerOverrides",value:String(Math.max(0,_(n.steerOverrideCount))),unit:"count"}),Object.freeze({key:"hardCorners",value:String(Math.max(0,_(n.cornerCount))),unit:"count"}),Object.freeze({key:"maxLateral",value:W(n.maxLatAccel,2),unit:"m/s²"}),Object.freeze({key:"stopTime",value:xe(e.stopSec),unit:""})]),warnings:Object.freeze({fcw:Math.max(0,_(n.warningCounts?.fcw)),ldw:Math.max(0,_(n.warningCounts?.ldw)),driverDistracted:Math.max(0,_(n.warningCounts?.driverDistracted))}),metadata:Object.freeze({source:String(t.source||"-").toUpperCase(),processed:f,requested:h,partial:p,policyVersion:_(t.policyVersion,1),elapsedMs:Math.max(0,_(s.elapsedMs))})})}var wn=Object.freeze({duration:["summary_duration","주행시간"],distance:["summary_distance","주행거리"],averageSpeed:["summary_average_speed","평균속도"],autoRatio:["summary_auto_ratio","자동주행 비율"]}),xn=Object.freeze({totalDistance:["summary_total_distance","총 거리"],autoDistance:["summary_auto_distance","자동"],manualDistance:["summary_manual_distance","수동"],averageSpeed:["summary_average_speed","평균속도"],maxSpeed:["summary_max_speed","최고속도"]}),ha=Object.freeze({auto:["report_auto","자동주행"],manual:["report_manual","수동주행"],gas:["summary_gas","가속 페달 사용"],brake:["summary_brake","브레이크 사용"]}),Tn=Object.freeze({hardAccel:["report_hard_accel","급가속"],overAccel:["report_over_accel","과가속"],hardDecel:["report_hard_decel","급감속"],overDecel:["report_over_decel","과감속"]}),Mn=Object.freeze({stops:["report_stops","정차"],disengagements:["summary_disengagements","운전자 개입"],steerOverrides:["report_steer_ovr","조향 개입"],hardCorners:["report_corner","하드코너링"],maxLateral:["report_max_lat","최대 횡가속"],stopTime:["summary_stop_time","정차 시간"]});function D(t,e){return typeof getUIText=="function"?getUIText(t,e):e}function Te(t,e){let a=t[e]||[e,e];return D(a[0],a[1])}function R(t,e="",a=""){let n=document.createElement(t);return e&&(n.className=e),a!==""&&(n.textContent=String(a)),n}function ya(t,e){t.replaceChildren();let a=R("article","route-summary");if(!e.hasData){let w=R("div","route-summary__empty",D("report_no_data","분석할 주행 데이터가 없습니다."));w.setAttribute("role","status"),a.append(w),t.append(a);return}let n=e.metrics.find(w=>w.key==="duration")||e.metrics[0],s=Ue(D("summary_duration","주행시간"));s.section.classList.add("route-summary__section--hero");let r=R("div","route-summary-hero"),o=R("div","route-summary-hero__time");if(o.append(xt(Te(wn,"duration"),n,{variant:"hero",hideLabel:!0})),e.date||e.timeRange){let w=R("p","route-summary-hero__subtitle");e.date&&w.append(R("span","route-summary-hero__date",e.date)),e.timeRange&&w.append(R("span","route-summary-hero__range",e.timeRange)),o.append(w)}let i=R("div","route-summary-composition");for(let w of e.composition)i.append(da(Te(ha,w.key),w));r.append(o,i);let l=e.composition.map(w=>({...w,label:Te(ha,w.key)}));s.section.append(r,ua(l));let u=R("div","route-summary__dashboard"),d=Ue(D("summary_distance_speed","주행 거리 / 속도"));d.section.classList.add("route-summary__section--distance");let m=R("dl","route-summary__metrics route-summary__metrics--distance");for(let w of e.distanceMetrics)m.append(xt(Te(xn,w.key),w,{variant:"compact"}));d.section.append(m);let g=Ue(D("summary_events","주행 이벤트"));g.section.classList.add("route-summary__section--events");let f=R("div","route-summary-events");for(let[w,H]of Object.entries(e.events))f.append(ma(Te(Tn,w),H,{critical:w.startsWith("hard"),kind:w}));g.section.append(f);let h=Ue(D("summary_additional_metrics","추가 지표"));h.section.classList.add("route-summary__section--details");let p=R("dl","route-summary-details"),x=D("report_unit_times","회");for(let w of e.details)p.append(ga(Te(Mn,w.key),w,x));if(h.section.append(p),e.warnings.fcw+e.warnings.ldw+e.warnings.driverDistracted>0){let w=R("p","route-summary__warning");w.textContent=`${D("report_warns","경고")} · FCW ${e.warnings.fcw} · LDW ${e.warnings.ldw} · ${D("report_warn_dm","운전자 주의")} ${e.warnings.driverDistracted}`,h.section.append(w)}let S=document.createElement("details");S.className="route-summary-metadata",S.append(R("summary","route-summary-metadata__summary",D("summary_analysis_info","분석 정보")));let T=R("dl","route-summary-metadata__list"),k=[[D("report_source","분석 소스"),e.metadata.source],[D("report_segments","세그먼트"),`${e.metadata.processed}/${e.metadata.requested}`],[D("summary_partial_segments","부분 분석"),String(e.metadata.partial)],[D("summary_policy_version","정책 버전"),String(e.metadata.policyVersion)],[D("summary_route_id","경로 ID"),e.route]];for(let[w,H]of k){let O=R("div","route-summary-metadata__row");O.append(R("dt","",w),R("dd","",H)),T.append(O)}S.append(T),u.append(d.section,h.section,g.section),a.append(s.section,u,S),t.append(a)}var An="/js/realtime/route_summary_worker.js?v=2607-02";function ba(t,e={}){return typeof Worker!="function"?Promise.reject(Object.assign(new Error("summary-worker-unsupported"),{code:"summary-worker-unsupported"})):new Promise((a,n)=>{let s=new Worker(An),r=e.signal,o=!1,i=(u,d)=>{o||(o=!0,r?.removeEventListener?.("abort",l),s.terminate(),u(d))},l=()=>{try{s.postMessage({type:"abort"})}catch{}i(n,new DOMException("Aborted","AbortError"))};if(s.onmessage=u=>{let d=u?.data||{};if(d.type==="progress")e.onProgress?.(d);else if(d.type==="warning")e.onWarning?.(d);else if(d.type==="complete")i(a,d.result);else if(d.type==="error"){let m=Object.assign(new Error(String(d.code||"summary-worker-failed")),{code:String(d.code||"summary-worker-failed")});i(n,m)}},s.onerror=()=>{i(n,Object.assign(new Error("summary-worker-failed"),{code:"summary-worker-failed"}))},r?.aborted){l();return}r?.addEventListener?.("abort",l,{once:!0}),s.postMessage({type:"analyze-route",route:t.route,schemaVersion:t.schemaVersion,segmentCount:t.segmentCount,skippedSegments:t.skippedSegments,segments:t.segments})})}var Me=null,En=0;function z(t,e,a){return typeof getUIText=="function"?getUIText(t,e,a):e}function Sa(t,e){return`routeSummary${e}${t}`}function va(t){return`
    `}function Ln(t){return t?.code==="summary-worker-unsupported"?z("summary_worker_unsupported","이 브라우저에서는 주행요약 분석을 지원하지 않습니다."):t?.code==="summary-source-empty"?z("report_no_data","분석할 주행 데이터가 없습니다."):z("report_failed","주행요약을 불러오지 못했습니다.")}async function Mt(t){if(!t)return;Me?.abort();let e=++En,a=new AbortController;Me=a;let n="loading",s=Sa(e,"Loading"),r=openAppDialog({mode:"choice",html:!0,title:z("route_summary","주행요약"),messageHtml:va(s),cancelLabel:z("cancel","취소")}),o=pa(z("report_analyzing","주행 데이터를 분석하는 중…"));document.getElementById(s)?.append(o.element),r.then(()=>{n==="loading"&&Me===a&&a.abort()});try{let i=await oa(t,{signal:a.signal}),l=ia(i);if(l?o.update(1,z("summary_cached","저장된 주행요약을 불러오는 중…")):(l=await ba(i,{signal:a.signal,onProgress(g){let f=Math.max(0,Number(g.total)||0),h=Math.max(0,Number(g.completed)||0),p=Math.max(0,Number(g.totalBytes)||0),x=Math.max(0,Number(g.loadedBytes)||0),$=p>0?x/p:f>0?h/f:NaN,S=Number.isFinite($)?Math.round(Math.max(0,Math.min(1,$))*100):0,T=p>0?z("summary_progress_percent","세그먼트 {completed}/{total} · {percent}%",{completed:h,total:f,percent:S}):z("summary_progress","세그먼트 {completed}/{total}",{completed:h,total:f});o.update($,T)}}),la(i,l)),a.signal.aborted||Me!==a)return;let u=fa(l),d=Sa(e,"Content");n="result",openAppDialog({mode:"alert",html:!0,title:z("route_summary","주행요약"),messageHtml:va(d),confirmLabel:z("close","닫기")});let m=document.getElementById(d);m&&ya(m,u)}catch(i){if(i?.name==="AbortError"||a.signal.aborted)return;n="error",openAppDialog({mode:"alert",title:z("route_summary","주행요약"),message:Ln(i),confirmLabel:z("close","닫기")})}finally{Me===a&&(Me=null)}}var ct="carrot_dashcam_upload_job_id",La="carrot_dashcam_segment_sort",In=2e3,$n=10,Cn=40,Rn=3,Ia=10,_a=60,wa=120,Un=1.5,Dn=1.25,ne=null,pe=null,rt=null,xa=Promise.resolve(),$a=!1,c={initialized:!1,loading:!1,routes:[],expanded:new Set,selected:new Set,refreshTimer:null,loadingMore:!1,loadingSegments:new Set,segmentScrollTops:Object.create(null),scrollBusy:!1,scrollTimer:null,renderFrame:0,loadSeq:0,replayLoadSeq:0,layoutBound:!1,layoutTimer:null,landscape:null,layoutKey:"",total:0,nextOffset:0,hasMore:!1,routeHeight:300,routeHeights:Object.create(null),windowStart:0,windowEnd:0,signature:"",sort:"asc"};function Nn(){try{return localStorage.getItem(La)==="desc"?"desc":"asc"}catch{return"asc"}}c.sort=Nn();function se(){return c.sort==="desc"?"desc":"asc"}function Y(t){let e=String(t||"").split("--"),a=Number.parseInt(e[e.length-1]||"0",10);return Number.isFinite(a)?a:0}function He(t){return String(t||"").replace(/^0+(?=\d{3})/,"")}function he(t,e){return`/api/dashcam/${t}/${encodeURIComponent(e)}`}function ot(t,e=""){let a=document.getElementById("dashcamStatus");a&&(a.textContent=t||"",a.hidden=!t,a.classList.toggle("is-error",e==="error"))}function ye(t){return(t||[]).map(e=>[e.route||"",e.segmentCount||0,e.routeStartEpoch||0,e.routeEndEpoch||0,e.segmentsNextOffset??"",e.segmentsHasMore?"1":"0",...e.segmentFolders||[]].join("|")).join(` -`)+"|"+(typeof LANG<"u"?LANG:"")}function ut(){return isCompactLandscapeMode()?210:310}function mt(){let t=window.matchMedia?.("(min-width: 900px)")?.matches?"wide":"narrow";return`${isCompactLandscapeMode()?"landscape":"portrait"}:${t}:${window.innerWidth}x${window.innerHeight}`}function fe(t){let e=String(t||""),a=Number(c.routeHeights?.[e]);if(Number.isFinite(a)&&a>0)return a;let n=Number(c.routeHeight)||ut();return e&&c.expanded.has(e)&&!isCompactLandscapeMode()?Math.max(560,n):Math.max(120,n)}function Hn(t=document.getElementById("dashcamRoutes")){let e=Math.max(120,fe("")),a=Math.max(e,t?.clientHeight||window.innerHeight||e),n=Math.max(1,Math.ceil(a/e));return Math.max($n,Math.min(Cn,n*Rn))}function Ca(t){let e=window.getComputedStyle?.(t);return Number.parseFloat(e?.rowGap||e?.gap||"0")||0}function On(t,e){let a=Array.isArray(e)?e:[],n=a.length,s=Math.max(1,t?.clientHeight||ut()*2),r=Math.max(0,t?.scrollTop||0),o=Math.max(120,fe("")),i=Math.max(o*2,s*Dn),l=Math.max(0,r-i),u=r+s+i,d=Ca(t),m=0,g=0,f=0,h=0;for(;m0?d:0);if(h+T>=l)break;h+=T,f=h,m+=1}g=m;let p=f;for(;g0?d:0),g+=1;let x=Math.min(n,Math.max(g+Math.ceil(i/o),m+1));for(;g0?d:0),g+=1;let $=f;for(let T=m;T0?d:0);let S=Math.max(0,$-p);return{start:m,end:g,topHeight:f,bottomHeight:S}}function kn(t){return!t||!c.hasMore||c.loading||c.loadingMore?!1:t.scrollHeight-t.scrollTop-t.clientHeight<=Math.max(360,t.clientHeight*Un)}function Ut(){c.renderFrame&&(window.cancelAnimationFrame(c.renderFrame),c.renderFrame=0)}function J(t){let e=document.getElementById("dashcamRoutes");e&&e.classList.toggle("is-loading-more",!!t)}function Ta(t,e){if(t<=0)return null;let a=document.createElement("div");return a.className="dashcam-virtual-spacer",a.dataset.spacer=e,a.style.height=`${Math.round(t)}px`,a}function Ra(t){return t?.closest?.("[data-route-card]")?.dataset.routeCard||""}function De(t){let e=Ra(t);!e||!c.segmentScrollTops||(c.segmentScrollTops[e]=Math.max(0,t.scrollTop||0))}function Pn(t=document.getElementById("dashcamRoutes")){t&&t.querySelectorAll(".dashcam-segment-list").forEach(e=>De(e))}function Ma(t){let e=Ra(t);if(!e||!c.segmentScrollTops)return;let a=Number(c.segmentScrollTops[e]);!Number.isFinite(a)||a<=0||(t.scrollTop=a)}function zn(t=document.getElementById("dashcamRoutes")){t&&(t.querySelectorAll(".dashcam-segment-list").forEach(e=>Ma(e)),requestAnimationFrame(()=>{t.isConnected&&t.querySelectorAll(".dashcam-segment-list").forEach(e=>Ma(e))}))}function Oe(t){let e=String(t?.route||""),a=Ee(t||{segmentFolders:[]}).join(","),n=L(t).join(",");return[isCompactLandscapeMode()?"landscape":"portrait",c.expanded.has(e)?"expanded":"collapsed",typeof LANG<"u"?LANG:"",t?.title||"",t?.dateLabel||"",t?.latestModifiedEpoch||"",t?.latestModifiedLabel||"",t?.routeStartEpoch||"",t?.routeEndEpoch||"",V(t),t?.segmentsNextOffset??"",t?.segmentsHasMore?"more":"done",c.loadingSegments?.has(e)?"loading":"idle",n,a].join("|")}function Bn(t,e,a,n={}){let s=String(t?.route||""),r=Oe(t),o=s?a.get(s):null;if(o&&o.dataset.renderKey===r)return o.style.setProperty("--i",String(e)),o.dataset.routeIndex=String(e),o.classList.remove("ui-stagger-item"),o.querySelectorAll(".ui-stagger-item").forEach(l=>l.classList.remove("ui-stagger-item")),o;let i=document.createElement("template");return i.innerHTML=Pa(t,e,{animate:n.animate,animateIndex:e}),i.content.firstElementChild}function Fn(t,e,a,n={}){Pn(t);let s=new Map(Array.from(t.querySelectorAll("[data-route-card]")).map(l=>[l.dataset.routeCard||"",l]).filter(([l])=>!!l)),r=document.createDocumentFragment(),o=Ta(a.topHeight,"top"),i=Ta(a.bottomHeight,"bottom");o&&r.appendChild(o),e.slice(a.start,a.end).forEach((l,u)=>{let d=Bn(l,a.start+u,s,n);d&&r.appendChild(d)}),i&&r.appendChild(i),Qe(t),jn(t),t.replaceChildren(r),zn(t)}function jn(t){!K||!t||t.querySelectorAll?.("[data-segment-loader]").forEach(e=>{K.unobserve(e),delete e.dataset.observed})}function Ua(t){if(!t)return!1;let e=Ca(t),a=Array.from(t.querySelectorAll("[data-route-card]")),n=!1,s=0,r=0;if(a.forEach(o=>{let i=o.dataset.routeCard||"",l=Number.parseInt(o.dataset.routeIndex||"0",10)||0,u=Math.max(120,o.getBoundingClientRect().height+(l>0?e:0));!i||!Number.isFinite(u)||((!c.expanded.has(i)||isCompactLandscapeMode())&&(s+=u,r+=1),Math.abs((Number(c.routeHeights[i])||0)-u)>1&&(c.routeHeights[i]=u,n=!0))}),r){let o=s/r;Number.isFinite(o)&&Math.abs(o-c.routeHeight)>1&&(c.routeHeight=o,n=!0)}return n}function Se(){c.renderFrame||(c.renderFrame=requestAnimationFrame(()=>{c.renderFrame=0,N({preserve:!0,animate:!1})}))}function Dt(t){if(!t||!(c.routes||[]).length)return!1;let e=Array.from(t.querySelectorAll("[data-route-card]"));if(!e.length)return!0;let a=t.getBoundingClientRect(),n=e[0].getBoundingClientRect(),s=e[e.length-1].getBoundingClientRect(),r=fe("")*2;return n.top>a.top-r||s.bottom{})}function L(t){return Array.isArray(t?.segmentFolders)?t.segmentFolders:[]}function V(t){let e=Number(t?.segmentCount),a=L(t).length;return Number.isFinite(e)&&e>=a?e:a}function gt(t){return!!t?.segmentsHasMore||L(t).length=0?e:L(t).length}function G(t,e,a=se()){let n=[],s=new Set;[...t||[],...e||[]].forEach(o=>{!o||s.has(o)||(s.add(o),n.push(o))});let r=a==="desc"?-1:1;return n.sort((o,i)=>r*(Y(o)-Y(i)))}function qn(t,e){if(!t||!e)return t;let a=String(t.route||"");if(!a||a!==e.route)return t;let n=L(t),s=L(e);if(s.length<=n.length)return t;let r=G(n,s),o=Math.max(V(t),r.length);return{...t,segmentFolders:r,segmentTimes:{...e.segmentTimes||{},...t.segmentTimes||{}},segmentCount:o,segmentsNextOffset:r.lengthString(s).padStart(2,"0");return{date:`${a.getFullYear()}.${n(a.getMonth()+1)}.${n(a.getDate())}`,time:`${n(a.getHours())}:${n(a.getMinutes())}:${n(a.getSeconds())}`}}function Ae(t,e,a={}){let n=re(t),s=re(e);return!n||!s?"":n.date===s.date?a.multiline?`${n.date} +
    `}function q(t={}){let e=document.getElementById("screenrecordVideos");if(!e||!C())return;let a=t.preserve===!0,n=S.videos||[];if(S.loading&&!n.length){We(""),e.innerHTML=et("screen"),e.dataset.signature="",e.dataset.renderCount="0";return}if(!n.length){e.innerHTML=tt("screen"),e.dataset.signature="",e.dataset.renderCount="0",We("");return}We("");let s=xn(e,n.length),r=`${S.signature||ca(n)}|${s.start}:${s.end}|${S.loadingMore?"more":""}`;if(a&&e.dataset.signature===r){ie(e);return}An(e,n,s),e.dataset.signature=r,e.dataset.renderCount=String(s.end-s.start),S.windowStart=s.start,S.windowEnd=s.end,ge(S.loadingMore),ie(e),requestAnimationFrame(()=>Tn(e))}async function te({silent:t=!1,append:e=!1}={}){if(e&&(!S.hasMore||S.loading||S.loadingMore))return;let a=++S.loadSeq;e?(S.loadingMore=!0,ge(!0)):t||(S.loading=!0,S.loadingMore=!1,ge(!1),q());try{let n=e&&(S.nextOffset||S.videos.length)||0,s=e?sa:Math.max(sa,S.videos.length||0),r=await getJson(`/api/screenrecord/videos?offset=${n}&limit=${s}`);if(a!==S.loadSeq)return;if(!C()){S.loading=!1,S.loadingMore=!1,ge(!1);return}let o=Array.isArray(r.videos)?r.videos:[],c=e?S.videos.concat(o):o,l=ca(c);if(t&&l===S.signature){S.loading=!1,S.loadingMore=!1,ge(!1);return}S.videos=c,S.signature=l,S.total=Number.isFinite(Number(r.total))?Number(r.total):c.length,S.nextOffset=r.nextOffset==null?c.length:Number(r.nextOffset)||c.length,S.hasMore=!!r.hasMore,S.loading=!1,S.loadingMore=!1,ge(!1),q({animate:!t}),!t&&we.screen===0&&Ne("screen",{reset:!0})}catch(n){if(a!==S.loadSeq)return;S.loading=!1,S.loadingMore=!1,ge(!1),!t&&C()&&(We(`${getUIText("screenrecord_load_failed","Failed to load screen recordings")}: ${n.message||n}`,"error"),showAppToast(n.message||getUIText("screenrecord_load_failed","Failed to load screen recordings"),{tone:"error"}))}}function wt(t){let e=new Error(t);return e.code=t,e}async function la(t,e={}){let a=await fetch(`/api/dashcam/summary-source/${encodeURIComponent(t)}`,{cache:"no-store",signal:e.signal}),n=null;try{n=await a.json()}catch{}if(!a.ok||n?.ok===!1)throw wt("summary-source-unavailable");if(n?.mode!=="client-worker"||!Array.isArray(n.segments))throw wt("summary-source-invalid");let s=n.segments.filter(r=>r&&typeof r.url=="string"&&["zstd","bzip2","none"].includes(r.compression));if(!s.length)throw wt("summary-source-empty");return Object.freeze({route:String(n.route||t),schemaVersion:Number(n.schemaVersion)||1,segmentCount:Math.max(s.length,Number(n.segmentCount)||0),skippedSegments:Math.max(0,Number(n.skippedSegments)||0),segments:Object.freeze(s.map(r=>Object.freeze({segment:String(r.segment||""),index:Number(r.index)||0,kind:r.kind==="qlog"?"qlog":"rlog",name:String(r.name||""),url:r.url,size:Math.max(0,Number(r.size)||0),modifiedMs:Math.max(0,Number(r.modifiedMs)||0),compression:r.compression})))})}var ae=new Map;function ua(t){return!t||!Array.isArray(t.segments)||!t.segments.length?"":JSON.stringify([String(t.route||""),Number(t.schemaVersion)||0,Number(t.segmentCount)||0,Number(t.skippedSegments)||0,t.segments.map(e=>[String(e.segment||""),Number(e.index)||0,String(e.kind||""),String(e.name||""),Number(e.size)||0,Number(e.modifiedMs)||0,String(e.compression||"")])])}function da(t){let e=ua(t);if(!e||!ae.has(e))return null;let a=ae.get(e);return ae.delete(e),ae.set(e,a),a}function ma(t,e){let a=ua(t);if(!a||!e?.ok)return!1;for(ae.delete(a),ae.set(a,e);ae.size>6;)ae.delete(ae.keys().next().value);return!0}function A(t,e="",a=""){let n=document.createElement(t);return e&&(n.className=e),a!==""&&(n.textContent=String(a)),n}function De(t){let e=A("section","route-summary__section"),a=A("h3","route-summary__section-title",t);return e.append(a),Object.freeze({section:e,heading:a})}function xt(t,e,a={}){let n=A("div","route-summary-metric");a.variant&&(n.dataset.variant=a.variant);let s=A("dt","route-summary-metric__label",t);a.hideLabel&&s.classList.add("visually-hidden");let r=A("dd","route-summary-metric__value");return r.append(A("span","route-summary-metric__number",e.value)),e.unit&&r.append(A("span","route-summary-metric__unit",e.unit)),n.append(s,r),n}function ga(t,e){let a=A("div","route-summary-composition__stat");return a.dataset.kind=e.key,e.emphasis&&(a.dataset.emphasis="true"),a.append(A("span","route-summary-composition__label",t),A("span","route-summary-composition__duration",e.duration),A("span","route-summary-composition__ratio",`${e.ratio.toFixed(1)}%`)),a}function En(t){let e=Object.fromEntries(t.map(o=>[o.key,Math.max(0,Number(o.seconds)||0)])),a=e.auto||0,n=e.manual||0,s=e.gas||0,r=e.brake||0;if(s+r>n&&s+r>0){let o=n/(s+r);s*=o,r*=o}return Object.freeze({auto:a,manual:n,gas:s,brake:r,other:Math.max(0,n-s-r)})}function nt(t,e){let a=A("span","route-summary-distribution__segment");return a.dataset.kind=t,a.style.setProperty("--route-summary-weight",String(e)),a}function pa(t){let e=A("div","route-summary-distribution");e.setAttribute("role","img"),e.setAttribute("aria-label",t.map(n=>`${n.label} ${n.ratio.toFixed(1)}%`).join(", "));let a=En(t);if(a.auto>0&&e.append(nt("auto",a.auto)),a.manual>0){let n=A("span","route-summary-distribution__manual");n.style.setProperty("--route-summary-weight",String(a.manual)),a.gas>0&&n.append(nt("gas",a.gas)),a.brake>0&&n.append(nt("brake",a.brake)),a.other>0&&n.append(nt("other",a.other)),e.append(n)}return e}function fa(t,e,a={}){let n=A("div","route-summary-event");a.kind&&(n.dataset.kind=a.kind),a.critical&&e.count>0&&(n.dataset.tone="danger");let s=A("div","route-summary-event__head");if(s.append(A("span","route-summary-event__label",t),A("span","route-summary-event__count",String(e.count))),n.append(s),e.items.length){let r=A("ul","route-summary-event__times");for(let o of e.items){let c=A("li","c-chip route-summary-event__time");c.append(A("time","",o.time)),o.peak&&c.append(A("span","route-summary-event__peak",o.peak)),r.append(c)}n.append(r)}return n}function ha(t,e,a){let n=A("div","route-summary-detail"),s=e.unit==="count"?`${e.value}${a}`:`${e.value} ${e.unit}`.trim();return n.append(A("dt","route-summary-detail__label",t),A("dd","route-summary-detail__value",s)),n}function ya(t,e=""){let a=A("div","route-summary-state");a.setAttribute("role","status"),a.setAttribute("aria-live","polite"),a.append(A("strong","route-summary-state__title",t));let n=A("span","route-summary-state__description",e);n.hidden=!e;let s=A("div","route-summary-state__progress");s.setAttribute("role","progressbar"),s.setAttribute("aria-label",t),s.setAttribute("aria-valuemin","0"),s.setAttribute("aria-valuemax","100"),s.dataset.mode="indeterminate";let r=A("span","route-summary-state__progress-fill");return s.append(r),a.append(n,s),Object.freeze({element:a,update(o,c){let l=Number(o);if(Number.isFinite(l)){let d=Math.max(0,Math.min(1,l)),u=Math.round(d*100);s.dataset.mode="determinate",s.setAttribute("aria-valuenow",String(u)),r.style.setProperty("--route-summary-progress",String(u))}else s.dataset.mode="indeterminate",s.removeAttribute("aria-valuenow"),r.style.removeProperty("--route-summary-progress");n.textContent=String(c||""),n.hidden=!n.textContent}})}function v(t,e=0){let a=Number(t);return Number.isFinite(a)?a:e}function W(t,e=1){return v(t).toLocaleString(void 0,{minimumFractionDigits:e,maximumFractionDigits:e})}function xe(t){let e=Math.max(0,Math.round(v(t))),a=Math.floor(e/3600),n=Math.floor(e%3600/60),s=e%60;return`${String(a).padStart(2,"0")}:${String(n).padStart(2,"0")}:${String(s).padStart(2,"0")}`}function Tt(t){return v(t)<=0?"":new Intl.DateTimeFormat(void 0,{hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1}).format(new Date(v(t)*1e3))}function In(t){return v(t)<=0?"":new Intl.DateTimeFormat(void 0,{year:"numeric",month:"2-digit",day:"2-digit"}).format(new Date(v(t)*1e3))}function $n(t,e){if(v(t?.wallTimeSec)>0)return Tt(t.wallTimeSec);let a=Math.max(0,v(t?.monoSec)-v(e)),n=Math.floor(a/60),s=Math.floor(a%60);return`+${String(n).padStart(2,"0")}:${String(s).padStart(2,"0")}`}function st(t,e){let a=Array.isArray(t?.items)?t.items:[];return Object.freeze({count:Math.max(0,v(t?.count)),items:Object.freeze(a.map(n=>Object.freeze({time:$n(n,e),peak:n?.peak==null?"":W(n.peak,2)})))})}function Sa(t){if(!t?.ok)throw new TypeError("Invalid route summary result");let e=t.time||{},a=t.distance||{},n=t.extras||{},s=t.diagnostics||{},r=Math.max(0,v(e.totalSec)),o=Math.max(0,v(e.autoEnabledSec)),c=Math.max(0,v(e.manualSec)),l=x=>r>0?Math.max(0,Math.min(100,x/r*100)):0,d=Tt(e.startWallSec),u=Tt(e.endWallSec),g=In(e.startWallSec),m=v(e.firstMonoSec),f=Math.max(0,v(s.processedSegments)),h=Math.max(f,v(s.requestedSegments)),p=Math.max(0,v(s.partialSegments));return Object.freeze({route:String(t.route||""),hasData:!!t.hasData,date:g,timeRange:d&&u?`${d}–${u}`:d,metrics:Object.freeze([Object.freeze({key:"duration",value:xe(r),unit:""}),Object.freeze({key:"distance",value:W(v(a.totalM)/1e3,2),unit:"km"}),Object.freeze({key:"averageSpeed",value:W(v(a.averageSpeedMs)*3.6,1),unit:"km/h"}),Object.freeze({key:"autoRatio",value:W(l(o),1),unit:"%"})]),distanceMetrics:Object.freeze([Object.freeze({key:"totalDistance",value:W(v(a.totalM)/1e3,2),unit:"km"}),Object.freeze({key:"autoDistance",value:W(v(a.autoM)/1e3,2),unit:"km"}),Object.freeze({key:"manualDistance",value:W(v(a.manualM)/1e3,2),unit:"km"}),Object.freeze({key:"averageSpeed",value:W(v(a.averageSpeedMs)*3.6,1),unit:"km/h"}),Object.freeze({key:"maxSpeed",value:W(v(a.maxSpeedMs)*3.6,1),unit:"km/h"})]),composition:Object.freeze([Object.freeze({key:"auto",seconds:o,duration:xe(o),ratio:l(o),emphasis:!0}),Object.freeze({key:"manual",seconds:c,duration:xe(c),ratio:l(c),emphasis:!1}),Object.freeze({key:"gas",seconds:v(e.manualGasSec),duration:xe(e.manualGasSec),ratio:l(v(e.manualGasSec)),emphasis:!1}),Object.freeze({key:"brake",seconds:v(e.manualBrakeSec),duration:xe(e.manualBrakeSec),ratio:l(v(e.manualBrakeSec)),emphasis:!1})]),events:Object.freeze({hardAccel:st(t.events?.hardAccel,m),overAccel:st(t.events?.overAccel,m),hardDecel:st(t.events?.hardDecel,m),overDecel:st(t.events?.overDecel,m)}),details:Object.freeze([Object.freeze({key:"stops",value:String(Math.max(0,v(n.stopCount))),unit:"count"}),Object.freeze({key:"disengagements",value:String(Math.max(0,v(n.disengageCount))),unit:"count"}),Object.freeze({key:"steerOverrides",value:String(Math.max(0,v(n.steerOverrideCount))),unit:"count"}),Object.freeze({key:"hardCorners",value:String(Math.max(0,v(n.cornerCount))),unit:"count"}),Object.freeze({key:"maxLateral",value:W(n.maxLatAccel,2),unit:"m/s²"}),Object.freeze({key:"stopTime",value:xe(e.stopSec),unit:""})]),warnings:Object.freeze({fcw:Math.max(0,v(n.warningCounts?.fcw)),ldw:Math.max(0,v(n.warningCounts?.ldw)),driverDistracted:Math.max(0,v(n.warningCounts?.driverDistracted))}),metadata:Object.freeze({source:String(t.source||"-").toUpperCase(),processed:f,requested:h,partial:p,policyVersion:v(t.policyVersion,1),elapsedMs:Math.max(0,v(s.elapsedMs))})})}var Cn=Object.freeze({duration:["summary_duration","주행시간"],distance:["summary_distance","주행거리"],averageSpeed:["summary_average_speed","평균속도"],autoRatio:["summary_auto_ratio","자동주행 비율"]}),Rn=Object.freeze({totalDistance:["summary_total_distance","총 거리"],autoDistance:["summary_auto_distance","자동"],manualDistance:["summary_manual_distance","수동"],averageSpeed:["summary_average_speed","평균속도"],maxSpeed:["summary_max_speed","최고속도"]}),ba=Object.freeze({auto:["report_auto","자동주행"],manual:["report_manual","수동주행"],gas:["summary_gas","가속 페달 사용"],brake:["summary_brake","브레이크 사용"]}),Un=Object.freeze({hardAccel:["report_hard_accel","급가속"],overAccel:["report_over_accel","과가속"],hardDecel:["report_hard_decel","급감속"],overDecel:["report_over_decel","과감속"]}),Nn=Object.freeze({stops:["report_stops","정차"],disengagements:["summary_disengagements","운전자 개입"],steerOverrides:["report_steer_ovr","조향 개입"],hardCorners:["report_corner","하드코너링"],maxLateral:["report_max_lat","최대 횡가속"],stopTime:["summary_stop_time","정차 시간"]});function N(t,e){return typeof getUIText=="function"?getUIText(t,e):e}function Te(t,e){let a=t[e]||[e,e];return N(a[0],a[1])}function R(t,e="",a=""){let n=document.createElement(t);return e&&(n.className=e),a!==""&&(n.textContent=String(a)),n}function _a(t,e){t.replaceChildren();let a=R("article","route-summary");if(!e.hasData){let w=R("div","route-summary__empty",N("report_no_data","분석할 주행 데이터가 없습니다."));w.setAttribute("role","status"),a.append(w),t.append(a);return}let n=e.metrics.find(w=>w.key==="duration")||e.metrics[0],s=De(N("summary_duration","주행시간"));s.section.classList.add("route-summary__section--hero");let r=R("div","route-summary-hero"),o=R("div","route-summary-hero__time");if(o.append(xt(Te(Cn,"duration"),n,{variant:"hero",hideLabel:!0})),e.date||e.timeRange){let w=R("p","route-summary-hero__subtitle");e.date&&w.append(R("span","route-summary-hero__date",e.date)),e.timeRange&&w.append(R("span","route-summary-hero__range",e.timeRange)),o.append(w)}let c=R("div","route-summary-composition");for(let w of e.composition)c.append(ga(Te(ba,w.key),w));r.append(o,c);let l=e.composition.map(w=>({...w,label:Te(ba,w.key)}));s.section.append(r,pa(l));let d=R("div","route-summary__dashboard"),u=De(N("summary_distance_speed","주행 거리 / 속도"));u.section.classList.add("route-summary__section--distance");let g=R("dl","route-summary__metrics route-summary__metrics--distance");for(let w of e.distanceMetrics)g.append(xt(Te(Rn,w.key),w,{variant:"compact"}));u.section.append(g);let m=De(N("summary_events","주행 이벤트"));m.section.classList.add("route-summary__section--events");let f=R("div","route-summary-events");for(let[w,O]of Object.entries(e.events))f.append(fa(Te(Un,w),O,{critical:w.startsWith("hard"),kind:w}));m.section.append(f);let h=De(N("summary_additional_metrics","추가 지표"));h.section.classList.add("route-summary__section--details");let p=R("dl","route-summary-details"),x=N("report_unit_times","회");for(let w of e.details)p.append(ha(Te(Nn,w.key),w,x));if(h.section.append(p),e.warnings.fcw+e.warnings.ldw+e.warnings.driverDistracted>0){let w=R("p","route-summary__warning");w.textContent=`${N("report_warns","경고")} · FCW ${e.warnings.fcw} · LDW ${e.warnings.ldw} · ${N("report_warn_dm","운전자 주의")} ${e.warnings.driverDistracted}`,h.section.append(w)}let b=document.createElement("details");b.className="route-summary-metadata",b.append(R("summary","route-summary-metadata__summary",N("summary_analysis_info","분석 정보")));let T=R("dl","route-summary-metadata__list"),k=[[N("report_source","분석 소스"),e.metadata.source],[N("report_segments","세그먼트"),`${e.metadata.processed}/${e.metadata.requested}`],[N("summary_partial_segments","부분 분석"),String(e.metadata.partial)],[N("summary_policy_version","정책 버전"),String(e.metadata.policyVersion)],[N("summary_route_id","경로 ID"),e.route]];for(let[w,O]of k){let H=R("div","route-summary-metadata__row");H.append(R("dt","",w),R("dd","",O)),T.append(H)}b.append(T),d.append(u.section,h.section,m.section),a.append(s.section,d,b),t.append(a)}var Dn="/js/realtime/route_summary_worker.js?v=2607-02";function va(t,e={}){return typeof Worker!="function"?Promise.reject(Object.assign(new Error("summary-worker-unsupported"),{code:"summary-worker-unsupported"})):new Promise((a,n)=>{let s=new Worker(Dn),r=e.signal,o=!1,c=(d,u)=>{o||(o=!0,r?.removeEventListener?.("abort",l),s.terminate(),d(u))},l=()=>{try{s.postMessage({type:"abort"})}catch{}c(n,new DOMException("Aborted","AbortError"))};if(s.onmessage=d=>{let u=d?.data||{};if(u.type==="progress")e.onProgress?.(u);else if(u.type==="warning")e.onWarning?.(u);else if(u.type==="complete")c(a,u.result);else if(u.type==="error"){let g=Object.assign(new Error(String(u.code||"summary-worker-failed")),{code:String(u.code||"summary-worker-failed")});c(n,g)}},s.onerror=()=>{c(n,Object.assign(new Error("summary-worker-failed"),{code:"summary-worker-failed"}))},r?.aborted){l();return}r?.addEventListener?.("abort",l,{once:!0}),s.postMessage({type:"analyze-route",route:t.route,schemaVersion:t.schemaVersion,segmentCount:t.segmentCount,skippedSegments:t.skippedSegments,segments:t.segments})})}var Me=null,On=0;function z(t,e,a){return typeof getUIText=="function"?getUIText(t,e,a):e}function wa(t,e){return`routeSummary${e}${t}`}function xa(t){return`
    `}function Hn(t){return t?.code==="summary-worker-unsupported"?z("summary_worker_unsupported","이 브라우저에서는 주행요약 분석을 지원하지 않습니다."):t?.code==="summary-source-empty"?z("report_no_data","분석할 주행 데이터가 없습니다."):z("report_failed","주행요약을 불러오지 못했습니다.")}async function Mt(t){if(!t)return;Me?.abort();let e=++On,a=new AbortController;Me=a;let n="loading",s=wa(e,"Loading"),r=openAppDialog({mode:"choice",html:!0,title:z("route_summary","주행요약"),messageHtml:xa(s),cancelLabel:z("cancel","취소")}),o=ya(z("report_analyzing","주행 데이터를 분석하는 중…"));document.getElementById(s)?.append(o.element),r.then(()=>{n==="loading"&&Me===a&&a.abort()});try{let c=await la(t,{signal:a.signal}),l=da(c);if(l?o.update(1,z("summary_cached","저장된 주행요약을 불러오는 중…")):(l=await va(c,{signal:a.signal,onProgress(m){let f=Math.max(0,Number(m.total)||0),h=Math.max(0,Number(m.completed)||0),p=Math.max(0,Number(m.totalBytes)||0),x=Math.max(0,Number(m.loadedBytes)||0),$=p>0?x/p:f>0?h/f:NaN,b=Number.isFinite($)?Math.round(Math.max(0,Math.min(1,$))*100):0,T=p>0?z("summary_progress_percent","세그먼트 {completed}/{total} · {percent}%",{completed:h,total:f,percent:b}):z("summary_progress","세그먼트 {completed}/{total}",{completed:h,total:f});o.update($,T)}}),ma(c,l)),a.signal.aborted||Me!==a)return;let d=Sa(l),u=wa(e,"Content");n="result",openAppDialog({mode:"alert",html:!0,title:z("route_summary","주행요약"),messageHtml:xa(u),confirmLabel:z("close","닫기")});let g=document.getElementById(u);g&&_a(g,d)}catch(c){if(c?.name==="AbortError"||a.signal.aborted)return;n="error",openAppDialog({mode:"alert",title:z("route_summary","주행요약"),message:Hn(c),confirmLabel:z("close","닫기")})}finally{Me===a&&(Me=null)}}var kn=new Set(["complete","failed","canceled"]),Pn=Object.freeze({queued:0,preparing:1,uploading:8,notifying:98,canceling:98,complete:100,canceled:0,failed:0});function rt(t){if(t==null||t==="")return Number.NaN;let e=Number(t);return Number.isFinite(e)&&e>=0?e:Number.NaN}function Ta(t){return Math.max(0,Math.min(100,t))}function Ma(t){let e=String(t?.phase||"").trim().toLowerCase();if(e)return e;let a=String(t?.status||"").trim().toLowerCase();return a==="done"?"complete":a==="failed"||a==="canceled"?a:"queued"}function At(t={}){let e=Ma(t),a=String(t.status||"").trim().toLowerCase(),n=t.done===!0||a==="done"||a==="failed"||a==="canceled"||kn.has(e);if(a==="done"||e==="complete")return{mode:"determinate",value:100};let r=rt(t.progress);if(Number.isFinite(r))return{mode:"determinate",value:Ta(r)};let o=rt(t.bytes_current),c=rt(t.bytes_total);if(Number.isFinite(o)&&Number.isFinite(c)&&c>0){let d=8+Math.min(o,c)/c*89;return{mode:"determinate",value:Ta(Math.min(97,d))}}let l=Pn[e];return Number.isFinite(l)?{mode:"determinate",value:n?Math.max(0,l):l}:{mode:"indeterminate",value:null}}function Aa(){let t="",e=-1,a=0,n=Object.freeze({accepted:!0,phase:"queued",progressState:Object.freeze({mode:"determinate",value:0})});function s(o=""){t=String(o||""),e=-1,a=0}function r(o={}){let c=String(o.id||"");c&&c!==t&&s(c);let l=rt(o.revision);if(Number.isFinite(l)&&e>=0&&l{let o=String(r?.segment||"").trim();o&&!n.has(o)&&(n.add(o),a.push(o));let c=Array.isArray(r?.files)?r.files:[],l=Number(r?.totalSize)||c.reduce((d,u)=>d+(Number(u?.size)||0),0);s.segments+=1,s.files+=c.length,s.bytes+=l,c.forEach(d=>{let u=Fn(d);u&&(s[u]+=1)})}),s}var it="carrot_dashcam_upload_job_id",Ha="carrot_dashcam_segment_sort",Bn=2e3,jn=10,qn=40,Gn=3,ka=10,La=60,Ea=120,Vn=1.5,Wn=1.25,Jn=160,Kn=800,Yn=240,Ia=5,ne=null,pe=null,ot=null,$a=Promise.resolve(),Pa=!1,i={initialized:!1,loading:!1,routes:[],expanded:new Set,selected:new Set,refreshTimer:null,loadingMore:!1,loadingSegments:new Set,segmentScrollTops:Object.create(null),scrollBusy:!1,scrollTimer:null,renderFrame:0,loadSeq:0,replayLoadSeq:0,layoutBound:!1,layoutTimer:null,landscape:null,layoutKey:"",total:0,nextOffset:0,hasMore:!1,routeHeight:300,routeHeights:Object.create(null),windowStart:0,windowEnd:0,signature:"",sort:"asc"};function Xn(){try{return localStorage.getItem(Ha)==="desc"?"desc":"asc"}catch{return"asc"}}i.sort=Xn();function se(){return i.sort==="desc"?"desc":"asc"}function Y(t){let e=String(t||"").split("--"),a=Number.parseInt(e[e.length-1]||"0",10);return Number.isFinite(a)?a:0}function ke(t){return String(t||"").replace(/^0+(?=\d{3})/,"")}function he(t,e){return`/api/dashcam/${t}/${encodeURIComponent(e)}`}function ct(t,e=""){let a=document.getElementById("dashcamStatus");a&&(a.textContent=t||"",a.hidden=!t,a.classList.toggle("is-error",e==="error"))}function ye(t){return(t||[]).map(e=>[e.route||"",e.segmentCount||0,e.routeStartEpoch||0,e.routeEndEpoch||0,e.segmentsNextOffset??"",e.segmentsHasMore?"1":"0",...e.segmentFolders||[]].join("|")).join(` +`)+"|"+(typeof LANG<"u"?LANG:"")}function dt(){return isCompactLandscapeMode()?210:310}function mt(){let t=window.matchMedia?.("(min-width: 900px)")?.matches?"wide":"narrow";return`${isCompactLandscapeMode()?"landscape":"portrait"}:${t}:${window.innerWidth}x${window.innerHeight}`}function fe(t){let e=String(t||""),a=Number(i.routeHeights?.[e]);if(Number.isFinite(a)&&a>0)return a;let n=Number(i.routeHeight)||dt();return e&&i.expanded.has(e)&&!isCompactLandscapeMode()?Math.max(560,n):Math.max(120,n)}function Zn(t=document.getElementById("dashcamRoutes")){let e=Math.max(120,fe("")),a=Math.max(e,t?.clientHeight||window.innerHeight||e),n=Math.max(1,Math.ceil(a/e));return Math.max(jn,Math.min(qn,n*Gn))}function za(t){let e=window.getComputedStyle?.(t);return Number.parseFloat(e?.rowGap||e?.gap||"0")||0}function Qn(t,e){let a=Array.isArray(e)?e:[],n=a.length,s=Math.max(1,t?.clientHeight||dt()*2),r=Math.max(0,t?.scrollTop||0),o=Math.max(120,fe("")),c=Math.max(o*2,s*Wn),l=Math.max(0,r-c),d=r+s+c,u=za(t),g=0,m=0,f=0,h=0;for(;g0?u:0);if(h+T>=l)break;h+=T,f=h,g+=1}m=g;let p=f;for(;m0?u:0),m+=1;let x=Math.min(n,Math.max(m+Math.ceil(c/o),g+1));for(;m0?u:0),m+=1;let $=f;for(let T=g;T0?u:0);let b=Math.max(0,$-p);return{start:g,end:m,topHeight:f,bottomHeight:b}}function es(t){return!t||!i.hasMore||i.loading||i.loadingMore?!1:t.scrollHeight-t.scrollTop-t.clientHeight<=Math.max(360,t.clientHeight*Vn)}function Nt(){i.renderFrame&&(window.cancelAnimationFrame(i.renderFrame),i.renderFrame=0)}function J(t){let e=document.getElementById("dashcamRoutes");e&&e.classList.toggle("is-loading-more",!!t)}function Ca(t,e){if(t<=0)return null;let a=document.createElement("div");return a.className="dashcam-virtual-spacer",a.dataset.spacer=e,a.style.height=`${Math.round(t)}px`,a}function Fa(t){return t?.closest?.("[data-route-card]")?.dataset.routeCard||""}function He(t){let e=Fa(t);!e||!i.segmentScrollTops||(i.segmentScrollTops[e]=Math.max(0,t.scrollTop||0))}function ts(t=document.getElementById("dashcamRoutes")){t&&t.querySelectorAll(".dashcam-segment-list").forEach(e=>He(e))}function Ra(t){let e=Fa(t);if(!e||!i.segmentScrollTops)return;let a=Number(i.segmentScrollTops[e]);!Number.isFinite(a)||a<=0||(t.scrollTop=a)}function as(t=document.getElementById("dashcamRoutes")){t&&(t.querySelectorAll(".dashcam-segment-list").forEach(e=>Ra(e)),requestAnimationFrame(()=>{t.isConnected&&t.querySelectorAll(".dashcam-segment-list").forEach(e=>Ra(e))}))}function Pe(t){let e=String(t?.route||""),a=Ee(t||{segmentFolders:[]}).join(","),n=E(t).join(",");return[isCompactLandscapeMode()?"landscape":"portrait",i.expanded.has(e)?"expanded":"collapsed",typeof LANG<"u"?LANG:"",t?.title||"",t?.dateLabel||"",t?.latestModifiedEpoch||"",t?.latestModifiedLabel||"",t?.routeStartEpoch||"",t?.routeEndEpoch||"",V(t),t?.segmentsNextOffset??"",t?.segmentsHasMore?"more":"done",i.loadingSegments?.has(e)?"loading":"idle",n,a].join("|")}function ns(t,e,a,n={}){let s=String(t?.route||""),r=Pe(t),o=s?a.get(s):null;if(o&&o.dataset.renderKey===r)return o.style.setProperty("--i",String(e)),o.dataset.routeIndex=String(e),o.classList.remove("ui-stagger-item"),o.querySelectorAll(".ui-stagger-item").forEach(l=>l.classList.remove("ui-stagger-item")),o;let c=document.createElement("template");return c.innerHTML=Ja(t,e,{animate:n.animate,animateIndex:e}),c.content.firstElementChild}function ss(t,e,a,n={}){ts(t);let s=new Map(Array.from(t.querySelectorAll("[data-route-card]")).map(l=>[l.dataset.routeCard||"",l]).filter(([l])=>!!l)),r=document.createDocumentFragment(),o=Ca(a.topHeight,"top"),c=Ca(a.bottomHeight,"bottom");o&&r.appendChild(o),e.slice(a.start,a.end).forEach((l,d)=>{let u=ns(l,a.start+d,s,n);u&&r.appendChild(u)}),c&&r.appendChild(c),Qe(t),rs(t),t.replaceChildren(r),as(t)}function rs(t){!K||!t||t.querySelectorAll?.("[data-segment-loader]").forEach(e=>{K.unobserve(e),delete e.dataset.observed})}function Ba(t){if(!t)return!1;let e=za(t),a=Array.from(t.querySelectorAll("[data-route-card]")),n=!1,s=0,r=0;if(a.forEach(o=>{let c=o.dataset.routeCard||"",l=Number.parseInt(o.dataset.routeIndex||"0",10)||0,d=Math.max(120,o.getBoundingClientRect().height+(l>0?e:0));!c||!Number.isFinite(d)||((!i.expanded.has(c)||isCompactLandscapeMode())&&(s+=d,r+=1),Math.abs((Number(i.routeHeights[c])||0)-d)>1&&(i.routeHeights[c]=d,n=!0))}),r){let o=s/r;Number.isFinite(o)&&Math.abs(o-i.routeHeight)>1&&(i.routeHeight=o,n=!0)}return n}function be(){i.renderFrame||(i.renderFrame=requestAnimationFrame(()=>{i.renderFrame=0,D({preserve:!0,animate:!1})}))}function Dt(t){if(!t||!(i.routes||[]).length)return!1;let e=Array.from(t.querySelectorAll("[data-route-card]"));if(!e.length)return!0;let a=t.getBoundingClientRect(),n=e[0].getBoundingClientRect(),s=e[e.length-1].getBoundingClientRect(),r=fe("")*2;return n.top>a.top-r||s.bottom{})}function E(t){return Array.isArray(t?.segmentFolders)?t.segmentFolders:[]}function V(t){let e=Number(t?.segmentCount),a=E(t).length;return Number.isFinite(e)&&e>=a?e:a}function gt(t){return!!t?.segmentsHasMore||E(t).length=0?e:E(t).length}function G(t,e,a=se()){let n=[],s=new Set;[...t||[],...e||[]].forEach(o=>{!o||s.has(o)||(s.add(o),n.push(o))});let r=a==="desc"?-1:1;return n.sort((o,c)=>r*(Y(o)-Y(c)))}function os(t,e){if(!t||!e)return t;let a=String(t.route||"");if(!a||a!==e.route)return t;let n=E(t),s=E(e);if(s.length<=n.length)return t;let r=G(n,s),o=Math.max(V(t),r.length);return{...t,segmentFolders:r,segmentTimes:{...e.segmentTimes||{},...t.segmentTimes||{}},segmentCount:o,segmentsNextOffset:r.lengthString(s).padStart(2,"0");return{date:`${a.getFullYear()}.${n(a.getMonth()+1)}.${n(a.getDate())}`,time:`${n(a.getHours())}:${n(a.getMinutes())}:${n(a.getSeconds())}`}}function Le(t,e,a={}){let n=re(t),s=re(e);return!n||!s?"":n.date===s.date?a.multiline?`${n.date} ${n.time} – ${s.time}`:`${n.date} ${n.time} – ${s.time}`:a.multiline?`${n.date} ${n.time} -– ${s.date} ${s.time}`:`${n.date} ${n.time} – ${s.date} ${s.time}`}function Gn(t,e){let a=re(t?.routeStartEpoch),n=re(t?.routeEndEpoch),s=a&&n&&a.date===n.date;return{route:e,title:t?.title||He(e),dateLabel:s?a.date:"",timeRange:a&&n?s?`${a.time} – ${n.time}`:`${a.date} ${a.time} – ${n.date} ${n.time}`:String(t?.dateLabel||""),segmentCount:V(t)}}function Vn(t,e){let a=be(t,e),n=re(a?.startEpoch),s=re(a?.endEpoch);return n&&s?n.date===s.date?`${n.time}–${s.time}`:`${n.date} ${n.time}–${s.date} ${s.time}`:Ht(t,e)}function Wn(t,e,a){let n=Y(a),s=be(t,a);return Object.freeze({id:a,name:`${getUIText("segment_label","Segment")} ${n}`,timeLabel:Vn(t,a),title:`${He(e)} · ${getUIText("segment_label","Segment")} ${n}`,subtitle:Ae(s?.startEpoch,s?.endEpoch),src:he("video",a),thumbnailSrc:he("thumbnail",a)})}function Aa(t,e,a){return(a||[]).map(n=>Wn(t,e,n))}function be(t,e){let a=t?.segmentTimes?.[e];return a&&typeof a=="object"?a:null}function Jn(t){let e=re(t);return e?e.time.slice(0,5):""}function Ht(t,e){let a=be(t,e);return Jn(a?.startEpoch)||`SEG ${Y(e)}`}function Kn(t,e){let a=be(t,e);return a?Ae(a.startEpoch,a.endEpoch):""}function Yn(t){let e=String(t||"").trim();if(e.length<=22)return e;let a=e.split("--");if(a.length>=3){let n=a[0].slice(0,8),s=a[a.length-2].slice(-10),r=a[a.length-1];return`${n}…${s}--${r}`}return`${e.slice(0,10)}…${e.slice(-9)}`}function Xn(t,e){let a=be(t,e),n=Number(a?.startEpoch||0),s=Number(a?.endEpoch||0),o=Object.entries(t?.segmentTimes||{}).map(([f,h])=>({index:Y(f),startEpoch:Number(h?.startEpoch||0),endEpoch:Number(h?.endEpoch||0)})).filter(f=>f.startEpoch>0&&f.endEpoch>=f.startEpoch).sort((f,h)=>f.index-h.index)[0];if(!o)return null;let i=Y(e),l=o.startEpoch+(i-o.index)*_a,u=s-n,d=u>0&&u<=wa?u:_a,g=n>0&&Math.abs(n-l)<=wa?n:l;return{startEpoch:g,endEpoch:g+d}}function Zn(t,e){let a=Yn(e),n=Xn(t,e),s=re(n?.startEpoch),r=re(n?.endEpoch);if(s&&r){let o=`${s.time}–${r.time}`;return a?`${a} · ${o}`:o}return a||`#${Y(e)}`}var K=null;function Qn(t){return!t||!("IntersectionObserver"in window)?null:(K&&K._root===t||(K&&K.disconnect(),K=new IntersectionObserver(e=>{C()&&e.forEach(a=>{if(!a.isIntersecting)return;let n=a.target.dataset?.route||"";!n||c.loadingSegments?.has(n)||za(n).catch(()=>{})})},{root:t,rootMargin:"240px 0px",threshold:.01}),K._root=t),K)}function ke(t=document.getElementById("dashcamRoutes")){if(!t||!C())return;let e=Qn(t);if(e){t.querySelectorAll("[data-segment-loader]").forEach(n=>{n.dataset.observed!=="1"&&(n.dataset.observed="1",e.observe(n))});return}let a=t.getBoundingClientRect();t.querySelectorAll("[data-segment-loader]").forEach(n=>{let s=n.dataset.route||"";if(!s||c.loadingSegments?.has(s))return;let r=n.getBoundingClientRect();r.top<=a.bottom+160&&r.bottom>=a.top-40&&za(s).catch(()=>{})})}var At=0,Et=new Set;function Da(t){t&&(Et.add(t),!At&&(At=requestAnimationFrame(()=>{At=0,Et.forEach(e=>{e.isConnected&&De(e)}),Et.clear()})))}function Ee(t){return L(t).filter(e=>c.selected.has(e))}function Na(t){return _t(ee.statusFor(t),{reading:getUIText("segment_reading","Reading"),recent:getUIText("segment_recently_read","Recently viewed")})}function es(t){return Na(t)?.outerHTML||""}function ts(){let t=document.getElementById("dashcamRoutes");t&&t.querySelectorAll(".dashcam-segment-tile[data-segment]").forEach(e=>{let a=e.querySelector(".dashcam-segment-read-status");if(!a)return;let n=Na(e.dataset.segment);a.replaceChildren(...n?[n]:[]),a.hidden=!n})}function Ha(t){let e=String(t||"").trim();if(!e)return Promise.resolve(null);let a=xa.catch(()=>null).then(()=>postJson("/api/dashcam/read-state",{recentSegment:e}));return xa=a,a}function Ea(t){$a=!0;let e=ee.snapshot(),a=ee.select(t),n=a.state.previousSegment;return a.changed&&n&&n!==e.previousSegment&&Ha(n).catch(()=>{}),a}function as(){let t=ee.finish();return t.changed&&t.state.previousSegment&&Ha(t.state.previousSegment).catch(()=>{}),t}function Oa(){return rt||(rt=getJson("/api/dashcam/read-state").then(t=>($a||ee.restoreRecent(t?.recentSegment),ee.snapshot())).catch(()=>ee.snapshot()),rt)}function ka(t,e,a,n={}){let s=n.entry||(c.routes||[]).find(p=>p.route===t),r=n.compact===!0,o=n.animate===!0,i=escapeHtml(t),l=escapeHtml(e),u=escapeHtml(Ht(s,e)),d=es(e),m=c.selected.has(e)?" checked":"",g=["dashcam-segment-tile",r?"dashcam-segment-tile--compact":"",o?"dashcam-segment-tile--append":""].filter(Boolean).join(" "),f=r?"dashcam-segment-thumb dashcam-segment-thumb--compact":"dashcam-segment-thumb",h=r?"dashcam-segment-check dashcam-segment-check--compact":"dashcam-segment-check";return`
    +– ${s.date} ${s.time}`:`${n.date} ${n.time} – ${s.date} ${s.time}`}function cs(t,e){let a=re(t?.routeStartEpoch),n=re(t?.routeEndEpoch),s=a&&n&&a.date===n.date;return{route:e,title:t?.title||ke(e),dateLabel:s?a.date:"",timeRange:a&&n?s?`${a.time} – ${n.time}`:`${a.date} ${a.time} – ${n.date} ${n.time}`:String(t?.dateLabel||""),segmentCount:V(t)}}function is(t,e){let a=Se(t,e),n=re(a?.startEpoch),s=re(a?.endEpoch);return n&&s?n.date===s.date?`${n.time}–${s.time}`:`${n.date} ${n.time}–${s.date} ${s.time}`:Ht(t,e)}function ls(t,e,a){let n=Y(a),s=Se(t,a);return Object.freeze({id:a,name:`${getUIText("segment_label","Segment")} ${n}`,timeLabel:is(t,a),title:`${ke(e)} · ${getUIText("segment_label","Segment")} ${n}`,subtitle:Le(s?.startEpoch,s?.endEpoch),src:he("video",a),thumbnailSrc:he("thumbnail",a)})}function Ua(t,e,a){return(a||[]).map(n=>ls(t,e,n))}function Se(t,e){let a=t?.segmentTimes?.[e];return a&&typeof a=="object"?a:null}function us(t){let e=re(t);return e?e.time.slice(0,5):""}function Ht(t,e){let a=Se(t,e);return us(a?.startEpoch)||`SEG ${Y(e)}`}function ds(t,e){let a=Se(t,e);return a?Le(a.startEpoch,a.endEpoch):""}function ms(t){let e=String(t||"").trim();if(e.length<=22)return e;let a=e.split("--");if(a.length>=3){let n=a[0].slice(0,8),s=a[a.length-2].slice(-10),r=a[a.length-1];return`${n}…${s}--${r}`}return`${e.slice(0,10)}…${e.slice(-9)}`}function gs(t,e){let a=Se(t,e),n=Number(a?.startEpoch||0),s=Number(a?.endEpoch||0),o=Object.entries(t?.segmentTimes||{}).map(([f,h])=>({index:Y(f),startEpoch:Number(h?.startEpoch||0),endEpoch:Number(h?.endEpoch||0)})).filter(f=>f.startEpoch>0&&f.endEpoch>=f.startEpoch).sort((f,h)=>f.index-h.index)[0];if(!o)return null;let c=Y(e),l=o.startEpoch+(c-o.index)*La,d=s-n,u=d>0&&d<=Ea?d:La,m=n>0&&Math.abs(n-l)<=Ea?n:l;return{startEpoch:m,endEpoch:m+u}}function ps(t,e){let a=ms(e),n=gs(t,e),s=re(n?.startEpoch),r=re(n?.endEpoch);if(s&&r){let o=`${s.time}–${r.time}`;return a?`${a} · ${o}`:o}return a||`#${Y(e)}`}var K=null;function fs(t){return!t||!("IntersectionObserver"in window)?null:(K&&K._root===t||(K&&K.disconnect(),K=new IntersectionObserver(e=>{C()&&e.forEach(a=>{if(!a.isIntersecting)return;let n=a.target.dataset?.route||"";!n||i.loadingSegments?.has(n)||Ka(n).catch(()=>{})})},{root:t,rootMargin:"240px 0px",threshold:.01}),K._root=t),K)}function ze(t=document.getElementById("dashcamRoutes")){if(!t||!C())return;let e=fs(t);if(e){t.querySelectorAll("[data-segment-loader]").forEach(n=>{n.dataset.observed!=="1"&&(n.dataset.observed="1",e.observe(n))});return}let a=t.getBoundingClientRect();t.querySelectorAll("[data-segment-loader]").forEach(n=>{let s=n.dataset.route||"";if(!s||i.loadingSegments?.has(s))return;let r=n.getBoundingClientRect();r.top<=a.bottom+160&&r.bottom>=a.top-40&&Ka(s).catch(()=>{})})}var Lt=0,Et=new Set;function ja(t){t&&(Et.add(t),!Lt&&(Lt=requestAnimationFrame(()=>{Lt=0,Et.forEach(e=>{e.isConnected&&He(e)}),Et.clear()})))}function Ee(t){return E(t).filter(e=>i.selected.has(e))}function qa(t){return vt(ee.statusFor(t),{reading:getUIText("segment_reading","Reading"),recent:getUIText("segment_recently_read","Recently viewed")})}function hs(t){return qa(t)?.outerHTML||""}function ys(){let t=document.getElementById("dashcamRoutes");t&&t.querySelectorAll(".dashcam-segment-tile[data-segment]").forEach(e=>{let a=e.querySelector(".dashcam-segment-read-status");if(!a)return;let n=qa(e.dataset.segment);a.replaceChildren(...n?[n]:[]),a.hidden=!n})}function Ga(t){let e=String(t||"").trim();if(!e)return Promise.resolve(null);let a=$a.catch(()=>null).then(()=>postJson("/api/dashcam/read-state",{recentSegment:e}));return $a=a,a}function Na(t){Pa=!0;let e=ee.snapshot(),a=ee.select(t),n=a.state.previousSegment;return a.changed&&n&&n!==e.previousSegment&&Ga(n).catch(()=>{}),a}function Ss(){let t=ee.finish();return t.changed&&t.state.previousSegment&&Ga(t.state.previousSegment).catch(()=>{}),t}function Va(){return ot||(ot=getJson("/api/dashcam/read-state").then(t=>(Pa||ee.restoreRecent(t?.recentSegment),ee.snapshot())).catch(()=>ee.snapshot()),ot)}function Wa(t,e,a,n={}){let s=n.entry||(i.routes||[]).find(p=>p.route===t),r=n.compact===!0,o=n.animate===!0,c=escapeHtml(t),l=escapeHtml(e),d=escapeHtml(Ht(s,e)),u=hs(e),g=i.selected.has(e)?" checked":"",m=["dashcam-segment-tile",r?"dashcam-segment-tile--compact":"",o?"dashcam-segment-tile--append":""].filter(Boolean).join(" "),f=r?"dashcam-segment-thumb dashcam-segment-thumb--compact":"dashcam-segment-thumb",h=r?"dashcam-segment-check dashcam-segment-check--compact":"dashcam-segment-check";return`
    -
    ${u}
    - ${d} +
    ${d}
    + ${u}
    ${l}
    - -
    `}function Pa(t,e=0,a={}){let n=a.animate!==!1,s=Number.isFinite(a.animateIndex)?a.animateIndex:e,r=String(t.route||""),o=escapeHtml(Oe(t)),i=L(t),l=V(t),u=i.length,d=gt(t),m=c.loadingSegments?.has(r),g=c.expanded.has(r),f=isCompactLandscapeMode(),h=g||f,p=Ee(t),x=l>0&&p.length===l,$=x?getUIText("deselect_all","Deselect all"):getUIText("select_all","Select all"),S=i[0]||"",T=escapeHtml(r),k=escapeHtml(t.title||He(r)),w=escapeHtml(t.dateLabel||r),H=escapeHtml(Ye(t.latestModifiedEpoch)||Xe(t.latestModifiedLabel)||"-"),O=Ae(t.routeStartEpoch,t.routeEndEpoch,{multiline:!0}),v=O?`
    ${escapeHtml(O).replace(` -`,"
    ")}
    `:"",Le=S?`
    -
    - +
    `}function Ja(t,e=0,a={}){let n=a.animate!==!1,s=Number.isFinite(a.animateIndex)?a.animateIndex:e,r=String(t.route||""),o=escapeHtml(Pe(t)),c=E(t),l=V(t),d=c.length,u=gt(t),g=i.loadingSegments?.has(r),m=i.expanded.has(r),f=isCompactLandscapeMode(),h=m||f,p=Ee(t),x=l>0&&p.length===l,$=x?getUIText("deselect_all","Deselect all"):getUIText("select_all","Select all"),b=c[0]||"",T=escapeHtml(r),k=escapeHtml(t.title||ke(r)),w=escapeHtml(t.dateLabel||r),O=escapeHtml(Xe(t.latestModifiedEpoch)||Ze(t.latestModifiedLabel)||"-"),H=Le(t.routeStartEpoch,t.routeEndEpoch,{multiline:!0}),_=H?`
    ${escapeHtml(H).replace(` +`,"
    ")}
    `:"",Ie=b?`
    +
    +
    - ${v} + ${_}
    ${escapeHtml(getUIText("segment_count","{count} segments",{count:l}))} - ${H} + ${O}
    - -
    `:"",Ie=h?i.map((de,$e)=>ka(r,de,$e,{compact:f,entry:t})).join(""):"",F=h&&d?``:"";return`
    - ${Le} +
    `:"",$e=h?c.map((ue,Ce)=>Wa(r,ue,Ce,{compact:f,entry:t})).join(""):"",B=h&&u?``:"";return`
    + ${Ie}
    ${k}
    ${w}
    -
    -
    +
    ${escapeHtml(getUIText("selected_count","{count} selected",{count:p.length}))} @@ -77,34 +77,38 @@ ${n.time} – ${s.time}`:`${n.date} ${n.time} – ${s.time}`:a.multiline?`${n.da
    -
    ${Ie}${F}
    +
    ${$e}${B}
    -
    `}function N(t={}){let e=document.getElementById("dashcamRoutes");if(!e)return;let a=t.animate!==!1,n=t.preserve===!0,s=c.routes||[];if(Ut(),!C())return;if(c.loading&&!s.length){ot(getUIText("loading","Loading...")),e.innerHTML=et("dashcam"),e.dataset.signature="",e.dataset.renderCount="0";return}if(!s.length){e.innerHTML=tt("dashcam"),e.dataset.signature="",e.dataset.renderCount="0",ot("");return}ot("");let r=On(e,s),o=`${c.signature||ye(s)}|${mt()}|${r.start}:${r.end}|${Math.round(r.topHeight)}:${Math.round(r.bottomHeight)}`;if(n&&e.dataset.signature===o){ie(e);return}Fn(e,s,r,{animate:a}),e.dataset.signature=o,e.dataset.renderCount=String(r.end-r.start),e.dataset.windowStart=String(r.start),e.dataset.windowEnd=String(r.end),c.windowStart=r.start,c.windowEnd=r.end,ie(e),requestAnimationFrame(()=>{C()&&(Ua(e)&&!c.scrollBusy&&Se(),ke(e))})}function pt(t){let e=document.getElementById("dashcamRoutes");if(!e)return!1;let a=c.routes||[],n=a.findIndex(l=>l.route===t);if(n<0)return!1;let s=Array.from(e.querySelectorAll("[data-route-card]")).find(l=>l.dataset.routeCard===t);if(!s)return!1;let r=document.createElement("template");r.innerHTML=Pa(a[n],n,{animate:!1});let o=r.content.querySelector(".dashcam-route-main"),i=s.querySelector(".dashcam-route-main");return!o||!i?!1:(i.replaceWith(o),s.dataset.renderKey=Oe(a[n]),ie(o),requestAnimationFrame(()=>{C()&&(Ua(e)&&Se(),ke(e))}),!0)}function ft(t){let e=document.getElementById("dashcamRoutes");if(!e)return!1;let a=(c.routes||[]).find(d=>d.route===t);if(!a)return!1;let n=Array.from(e.querySelectorAll("[data-route-card]")).find(d=>d.dataset.routeCard===t);if(!n)return!1;let s=Ee(a),r=V(a),o=r>0&&s.length===r,i=n.querySelector(".dashcam-selection-count");i&&(i.textContent=getUIText("selected_count","{count} selected",{count:s.length}));let l=n.querySelector('[data-action="select-route"]');l&&(l.dataset.selected=o?"1":"0",l.textContent=o?getUIText("deselect_all","Deselect all"):getUIText("select_all","Select all"));let u=n.querySelector('[data-action="upload-selected"]');return u&&(u.disabled=s.length===0),n.querySelectorAll('input[data-action="select-segment"]').forEach(d=>{let m=d.dataset.segment||"";d.checked=c.selected.has(m)}),n.dataset.renderKey=Oe(a),!0}function It(t,e=!1){let a=document.getElementById("dashcamRoutes"),n=(c.routes||[]).find(o=>o.route===t);if(!a||!n)return!1;let s=Array.from(a.querySelectorAll("[data-route-card]")).find(o=>o.dataset.routeCard===t);if(!s)return!1;let r=s.querySelector("[data-segment-loader]");return r?gt(n)?(r.classList.toggle("is-loading",!!e),!0):(r.remove(),!0):!1}function ns(t,e,a=0){if(!e.length)return!1;let n=document.getElementById("dashcamRoutes"),s=(c.routes||[]).find(p=>p.route===t);if(!n||!s)return!1;let r=Array.from(n.querySelectorAll("[data-route-card]")).find(p=>p.dataset.routeCard===t),o=r?.querySelector(".dashcam-segment-list");if(!r||!o)return!1;let i=o.scrollTop,u=o.scrollHeight>o.clientHeight+1&&o.scrollHeight-o.scrollTop-o.clientHeight<=48,d=!!c.scrollBusy;De(o);let m=isCompactLandscapeMode(),g=document.createElement("template"),f=!d;g.innerHTML=e.map((p,x)=>ka(t,p,a+x,{compact:m,animate:f,entry:s})).join("");let h=o.querySelector("[data-segment-loader]");if(o.insertBefore(g.content,h||null),ie(o),ft(t),It(t,!1),u&&!d){let p=Math.max(0,o.scrollHeight-o.clientHeight);o.scrollTop=p,De(o)}else o.scrollTop=i,De(o);return r.dataset.renderKey=Oe(s),!0}async function za(t){if(!t||c.loadingSegments?.has(t))return;let e=(c.routes||[]).find(n=>n.route===t);if(!e||!gt(e))return;let a=L(e).length;c.loadingSegments.add(t),ht({renderOnIdle:!1}),It(t,!0);try{let n=Lt(e),s=await getJson(`/api/dashcam/segments/${encodeURIComponent(t)}?offset=${n}&limit=${Ia}&sort=${se()}`),r=(c.routes||[]).find(u=>u.route===t);if(!r)return;let o=Array.isArray(s.segments)?s.segments:[],i=new Set(L(r)),l=o.filter(u=>u&&!i.has(u));if(r.segmentFolders=G(L(r),o),r.segmentTimes={...r.segmentTimes||{},...s.segmentTimes||{}},r.segmentCount=Number.isFinite(Number(s.total))?Number(s.total):V(r),r.segmentsNextOffset=s.nextOffset==null?r.segmentFolders.length:Number(s.nextOffset)||r.segmentFolders.length,r.segmentsHasMore=!!s.hasMore,c.signature=ye(c.routes),l.length&&ns(t,l,a))return}catch(n){C()&&showAppToast(n.message||getUIText("dashcam_load_failed","Failed to load dashcam list"),{tone:"error"})}finally{c.loadingSegments.delete(t),It(t,!1),requestAnimationFrame(()=>ke())}}async function ve({silent:t=!1,append:e=!1}={}){if(e&&(!c.hasMore||c.loading||c.loadingMore))return;let a=++c.loadSeq;e?(c.loadingMore=!0,J(!0)):t||(c.loading=!0,c.loadingMore=!1,J(!1),N());try{let n=e&&(c.nextOffset||c.routes.length)||0,s=c.routes.length||0,r=Hn(),o=e?r:Math.max(r,s||0),i=await getJson(`/api/dashcam/routes?offset=${n}&limit=${o}&segment_limit=${Ia}&sort=${se()}`);if(a!==c.loadSeq){e&&(c.loadingMore=!1,J(!1));return}if(!C()){c.loading=!1,c.loadingMore=!1,J(!1);return}let l=Array.isArray(i.routes)?i.routes:[],u=new Map((c.routes||[]).map(p=>[p.route,p])),d=e?l:l.map(p=>qn(p,u.get(p.route))),m=e?c.routes.concat(d):d,g=ye(m);if(t&&g===c.signature){c.loading=!1,c.loadingMore=!1,c.total=Number.isFinite(Number(i.total))?Number(i.total):m.length,c.nextOffset=i.nextOffset==null?m.length:Number(i.nextOffset)||m.length,c.hasMore=!!i.hasMore,J(!1);return}let f=new Set(m.map(p=>p.route)),h=new Set(m.flatMap(p=>L(p)));c.expanded=new Set(Array.from(c.expanded).filter(p=>f.has(p))),c.selected=new Set(Array.from(c.selected).filter(p=>h.has(p))),c.routeHeights=Object.fromEntries(Object.entries(c.routeHeights||{}).filter(([p])=>f.has(p))),c.segmentScrollTops=Object.fromEntries(Object.entries(c.segmentScrollTops||{}).filter(([p])=>f.has(p))),c.routes=m,c.signature=g,c.total=Number.isFinite(Number(i.total))?Number(i.total):m.length,c.nextOffset=i.nextOffset==null?m.length:Number(i.nextOffset)||m.length,c.hasMore=!!i.hasMore,c.loading=!1,c.loadingMore=!1,J(!1),N({animate:e}),!t&&we.dashcam===0&&Re("dashcam",{reset:!0}),requestAnimationFrame(()=>{Nt(),ke()})}catch(n){if(a!==c.loadSeq){e&&(c.loadingMore=!1,J(!1));return}c.loading=!1,c.loadingMore=!1,J(!1),!t&&C()&&(ot(`${getUIText("dashcam_load_failed","Failed to load dashcam list")}: ${n.message||n}`,"error"),showAppToast(n.message||getUIText("dashcam_load_failed","Failed to load dashcam list"),{tone:"error"}))}}function Ba(){c.refreshTimer||(c.refreshTimer=window.setInterval(()=>{CURRENT_PAGE!=="logs"||c.scrollBusy||(U==="screen"?te({silent:!0}).catch(()=>{}):!c.loading&&!c.loadingMore&&!c.loadingSegments?.size&&ve({silent:!0}).catch(()=>{}))},1e4))}function ht(t={}){let e=t.renderOnIdle!==!1;c.scrollBusy=!0,c.scrollTimer&&window.clearTimeout(c.scrollTimer),c.scrollTimer=window.setTimeout(()=>{if(c.scrollBusy=!1,e&&C()&&U==="dashcam"){let a=yt("dashcam");Dt(a)&&Se()}},380)}function Ot(t,e){let a=(c.routes||[]).find(u=>u.route===t),n=G(L(a),[],"asc"),s=Ea(e).state,r=be(a,e),o=Number(r?.endEpoch||0),i=Ae(r?.startEpoch,o),l=at(`${He(t)} · ${getUIText("segment_label","Segment")} ${Y(e)}`,he("video",e),{kind:"dashcam",currentGroup:Gn(a,t),segments:Aa(a,t,n),activeSegment:e,previousSegment:s.previousSegment,subtitle:i,subtitleForSegmentDuration:(u,d)=>{let m=be(a,u),g=Number(m?.endEpoch||0),f=Ae(m?.startEpoch,g),h=Number(d||0);return!Number.isFinite(h)||h<=0||g<=0?f:Ae(g-h,g)},onSegmentSend:u=>Pe([u],{confirm:!1,showProgress:!1,showResult:!0,showSuccessToast:!1}),onSegmentMenu:(u,{close:d}={})=>kt(t,u,{activePlayerClose:d}),onSegmentChange:u=>{Ea(u)},onClose:()=>{as()}});ze(t).then(u=>{if(!l?.isOpen?.())return;let d=(c.routes||[]).find(g=>g.route===t)||a,m=G(u,n,"asc");l.updateSegments?.(Aa(d,t,m))}).catch(()=>{})}ee.subscribe(ts);async function ss(t,e){let a=++c.replayLoadSeq,n=window.CarrotVisionReplay;if(!n?.start){showAppToast(getUIText("replay_unavailable","Drive replay is not available in this browser."),{tone:"error"});return}let s=(c.routes||[]).find(l=>l.route===t),r=G(L(s),[],"asc"),o=l=>Zn(s,l),i=n.start({route:t,segment:e,segments:r,title:o(e),titleForSegment:o});ze(t).then(l=>{a!==c.replayLoadSeq||!n.isActive?.()||n.updateSegments?.(G(l,[],"asc"),t)}).catch(()=>{}),await i}function it(t){return(Array.isArray(t)?t:[]).reduce((a,n)=>{let s=Array.isArray(n?.files)?n.files:[],r=Number(n?.totalSize)||s.reduce((o,i)=>o+(Number(i?.size)||0),0);return a.segments+=1,a.files+=s.length,a.bytes+=r,a},{segments:0,files:0,bytes:0})}function lt(t){let e=Number(t?.files||0),a=Number(t?.bytes||0),n=e>0?getUIText("upload_file_count","{count} files",{count:e}):getUIText("upload_files_unknown","files unknown"),s=a>0?Ze(a):getUIText("upload_size_unknown","size unknown");return`${n} · ${s}`}function rs(t){let e=String(t?.shareText||t?.message||""),a=it(t?.results||[]);return` - ${u} ${d} + ${u} -
    `;let $=x.frame.querySelector(".dashcam-player-title");$&&($.id="dashcamPlayerDialogTitle",x.dialog.setAttribute("aria-labelledby",$.id)),n.append(x.dialog);let S=n.querySelector("video");S.controls=!1,S.removeAttribute("controls");let T=n.querySelector(".dashcam-player-toast"),k=n.querySelector(".dashcam-player-subtitle"),w=e+(e.includes("?")?"&":"?")+"download=1",H=null,O=!0,v=null,Le=!1,Ie=!1,F=!1,de=0,$e=null,Be=null,ce=n.querySelector(".dashcam-player-transport"),ue=window.CarrotMediaTransport,X=null,me=null,Z=null,Ft=y=>{let E=String(y||"");return E+(E.includes("?")?"&":"?")+"download=1"},_e=()=>o?.snapshot().activeSegment||"",an=()=>{let y=Number(v?.duration??S.duration);return o&&typeof a.subtitleForSegmentDuration=="function"?a.subtitleForSegmentDuration(_e(),y)||p.subtitle:typeof a.subtitleForDuration=="function"&&a.subtitleForDuration(y)||p.subtitle},vt=()=>{if(!k)return;let y=an();k.textContent=y||"",k.hidden=!y};S.addEventListener("loadedmetadata",vt);let j=y=>{!T||O||!y||(T.textContent=y,T.classList.add("is-visible"),H&&window.clearTimeout(H),H=window.setTimeout(()=>T.classList.remove("is-visible"),850))},Ce=()=>{if(!F){F=!0;try{a.onClose?.(_e())}catch{}de+=1,H&&window.clearTimeout(H),$e?.dispose?.(),$e=null;try{v?.destroy?.()}catch{}n.remove()}},jt=y=>{Ie||typeof y!="function"||(Ie=!0,Promise.resolve().then(()=>y({close:Ce})).catch(E=>{typeof showAppToast=="function"&&showAppToast(E?.message||String(E),{tone:"error"})}).finally(()=>{Ie=!1}))},qt=()=>{if(!(!X||!Z)){if(o){let y=o.snapshot();X.disabled=!y.canMovePrevious,Z.disabled=!y.canMoveNext;return}X.disabled=typeof a.onPrevious!="function",Z.disabled=typeof a.onNext!="function"}},Fe=()=>{if(!me||!ue?.setActionState)return;let y=(typeof v?.paused=="boolean"?v.paused:S.paused)||(typeof v?.ended=="boolean"?v.ended:S.ended);ue.setActionState(me,y?"play":"pause",{label:y?getUIText("replay_play","Play replay"):getUIText("replay_pause","Pause replay")}),me.setAttribute("aria-pressed",String(!y))},nn=async()=>{try{let y=typeof v?.ended=="boolean"?v.ended:S.ended,E=typeof v?.paused=="boolean"?v.paused:S.paused;if(y&&(v?v.currentTime=0:S.currentTime=0),E||y){let M=v?.play?v.play():S.play();M&&typeof M.then=="function"&&await M}else v?.pause?v.pause():S.pause()}catch(y){typeof showAppToast=="function"&&showAppToast(y?.message||getUIText("playback_failed","Playback failed"),{tone:"error"})}finally{window.requestAnimationFrame(Fe)}},sn=y=>{let E=()=>{if(F||y!==de)return;let M=v?.play?v.play():S.play();M&&typeof M.catch=="function"&&M.catch(()=>{})};window.setTimeout(()=>{if(F||y!==de)return;let M=v?.media||S;M.readyState>=2?E():M.addEventListener("canplay",E,{once:!0})},0)},Gt=(y,E={})=>{if(!y?.src||F)return!1;let M=E.autoplay!==!1;p=y,de+=1;let B=de;return $&&($.textContent=p.title||p.name),vt(),v?(v.source={type:"video",title:p.title||p.name,sources:[{src:p.src,type:"video/mp4"}]},v.download=Ft(p.src)):(S.src=p.src,S.load()),M&&sn(B),!0},Vt=(y,E=!0)=>{if(!y?.changed)return!1;let M=g.get(y.state.activeSegment);return M?(Be?.setActive(y.state.activeSegment,!0),qt(),Gt(M,{autoplay:E}),a.onSegmentChange?.(y.state.activeSegment),!0):!1},rn=y=>o?Vt(o.select(y)):!1,Wt=y=>o?Vt(o.move(y)):!1;o&&x.segmentHost&&(Be=Qt(m,{activeSegmentId:_e(),labels:{segments:getUIText("segment_browser","Segments"),reading:getUIText("segment_reading","Reading"),recent:getUIText("segment_recently_read","Recently viewed")},onSelect:rn,statusFor:y=>o.statusFor(y)}),x.segmentHost.hidden=!1,x.segmentHost.append(Be.element)),ce&&ue?.createActionButton?(X=ue.createActionButton("previous",{label:getUIText("replay_previous_segment","Previous segment"),disabled:o?!o.snapshot().canMovePrevious:typeof a.onPrevious!="function",onActivate:()=>jt(o?()=>Wt(-1):a.onPrevious)}),me=ue.createActionButton("play",{playLabel:getUIText("replay_play","Play replay"),pauseLabel:getUIText("replay_pause","Pause replay"),onActivate:nn}),Z=ue.createActionButton("next",{label:getUIText("replay_next_segment","Next segment"),disabled:o?!o.snapshot().canMoveNext:typeof a.onNext!="function",onActivate:()=>jt(o?()=>Wt(1):a.onNext)}),ce.append(X,me,Z),["pointerdown","mousedown","touchstart"].forEach(y=>{ce.addEventListener(y,E=>E.stopPropagation(),{passive:!0})}),["play","pause","ended"].forEach(y=>{S.addEventListener(y,Fe)}),me.setAttribute("aria-keyshortcuts","Space K"),X.setAttribute("aria-keyshortcuts","Shift+P"),Z.setAttribute("aria-keyshortcuts","Shift+N"),Fe()):ce&&(ce.hidden=!0),$e=ue?.createInputController?.({media:S,stage:x.frame,rates:[.5,1,1.5,2],canInteract:()=>!F&&n.isConnected,canPrevious:()=>!!(X&&!X.disabled),canNext:()=>!!(Z&&!Z.disabled),onTogglePlayback:()=>me?.click(),onPrevious:()=>X?.click(),onNext:()=>Z?.click(),onRate:y=>{v?v.speed=y:S.playbackRate=y},onSeek:Fe,blockedPointerSelector:["button","input","select","a",".plyr__controls",".dashcam-player-top",".dashcam-player-transport"].join(", ")})||null,n.addEventListener("click",y=>{y.target===n&&Ce()}),n.querySelector(".dashcam-player-close")?.addEventListener("click",Ce);let Jt=(y,E,M)=>{if(y.stopPropagation(),Le||typeof E!="function")return;Le=!0;let B=y.currentTarget;B.disabled=!0,n.classList.add(M),Promise.resolve().then(()=>E({close:Ce})).catch(Q=>{typeof showAppToast=="function"&&showAppToast(Q?.message||String(Q),{tone:"error"})}).finally(()=>{n.classList.remove(M),B.disabled=!1,Le=!1})};return n.querySelector(".dashcam-player-send")?.addEventListener("click",y=>{let E=_e(),M=typeof a.onSegmentSend=="function"?({close:B}={})=>a.onSegmentSend(E,{close:B}):a.onSend;Jt(y,M,"is-action-open")}),n.querySelector(".dashcam-player-menu")?.addEventListener("click",y=>{let E=_e(),M=typeof a.onSegmentMenu=="function"?({close:B}={})=>a.onSegmentMenu(E,{close:B}):a.onMenu;Jt(y,M,"is-menu-open")}),document.body.appendChild(n),requestAnimationFrame(()=>{if(!F){n.classList.add("is-open");try{v=new Plyr(S,{controls:["play","progress","current-time","duration","settings","fullscreen","download"],hideControls:!1,seekTime:5,settings:["speed"],speed:{selected:1,options:[.5,1,1.5,2]},keyboard:{focused:!0,global:!1},fullscreen:{enabled:!0,fallback:!0,iosNative:!0},urls:{download:Ft(p.src)}}),S.controls=!1,S.removeAttribute("controls"),v.source={type:"video",title:p.title||p.name,sources:[{src:p.src,type:"video/mp4"}]},v.once("ready",()=>{let y=v.elements?.container||n;ce&&ce.parentElement!==y&&y.appendChild(ce),((M,B)=>{y.querySelectorAll(M).forEach(Q=>Q.addEventListener("click",()=>j(B)))})('[data-plyr="download"]',`⤓ ${getUIText("download","Download")}`),y.addEventListener("keydown",M=>{M.key==="ArrowLeft"?j(`⏪ ${getUIText("rewind_5","5s")}`):M.key==="ArrowRight"&&j(`${getUIText("forward_5","5s")} ⏩`)}),v.on("play",()=>j(`▶ ${getUIText("play","Play")}`)),v.on("pause",()=>j(`⏸ ${getUIText("pause","Pause")}`)),v.on("ended",()=>j(getUIText("ended","End"))),v.on("ratechange",()=>j(`⚡ ${v.speed}x`)),v.on("enterfullscreen",()=>j(`⛶ ${getUIText("fullscreen","Fullscreen")}`)),v.on("exitfullscreen",()=>j(getUIText("fullscreen_exit","Exit fullscreen"))),S.addEventListener("enterpictureinpicture",()=>j("⊞ PiP")),S.addEventListener("leavepictureinpicture",()=>j(`⊟ ${getUIText("pip_exit","Exit PiP")}`)),window.setTimeout(()=>{O=!1},350)})}catch{S.controls=!0,S.setAttribute("controls",""),S.src=p.src}}}),Object.freeze({close:Ce,isOpen:()=>!F&&n.isConnected,updateSegments:y=>{if(!o||F)return null;let E=Ge(y);if(!E.length)return o.snapshot();let M=_e(),B=p.src;m=E,g=new Map(m.map(qe=>[qe.id,qe]));let Q=o.replaceSegments(m.map(qe=>qe.id),{activeSegment:M}),je=g.get(Q.state.activeSegment);return Be?.render(m,Q.state.activeSegment),qt(),je&&(Q.state.activeSegment!==M||je.src!==B?Gt(je,{autoplay:!1}):(p=je,$&&($.textContent=p.title||p.name),vt())),Q.state}})}function St(t,e={}){let a=Ya(t),n=e.load!==!1;a!==U&&bt(U),U=a;for(let s of ls()){let r=Ya(s.dataset.logsTab)===U;s.classList.toggle("is-active",r),s.setAttribute("aria-selected",r?"true":"false");let o=document.getElementById(s.getAttribute("aria-controls")||"");o&&(o.hidden=!r)}Qa?.sync(),n&&(U==="screen"&&!b.initialized?(b.initialized=!0,te().catch(()=>{})):U==="screen"?(q(),te({silent:!0}).catch(()=>{})):c.initialized&&ve({silent:!0}).catch(()=>{})),e.restoreScroll!==!1&&Re(U)}function ys(t){t&&(en(),t.dataset.bound!=="1"&&(t.dataset.bound="1",t.addEventListener("click",e=>{let a=e.target?.closest?.(zt);a&&t.contains(a)&&St(a.dataset.logsTab)}),Qa=window.CarrotUI?.segmentedControl?.create(t,{itemSelector:zt,onActivate:e=>St(e?.dataset?.logsTab)})||null))}function bs(t){(t?.detail?.page||"")!=="logs"&&(bt(U),Ut(),c.loadSeq+=1,b.loadSeq+=1,c.loading=!1,c.loadingMore=!1,c.loadingSegments?.clear?.(),J(!1),b.loading=!1,c.scrollBusy=!1,c.scrollTimer&&(window.clearTimeout(c.scrollTimer),c.scrollTimer=null),c.layoutTimer&&(window.clearTimeout(c.layoutTimer),c.layoutTimer=null),hs())}function Ss(){let t=document.getElementById("logsTabs"),e=document.getElementById("dashcamRoutes"),a=document.getElementById("screenrecordVideos");gs(),ys(t),c.layoutBound||(c.layoutBound=!0,c.landscape=isCompactLandscapeMode(),c.layoutKey=mt(),window.addEventListener("carrot:pagechange",bs),window.addEventListener("carrot:languagechange",()=>{tn(),en(),c.signature="",b.signature="",c.routeHeights=Object.create(null);let n=document.getElementById("dashcamRoutes");n&&(n.dataset.signature="");let s=document.getElementById("screenrecordVideos");s&&(s.dataset.signature=""),C()&&(N({animate:!1}),typeof q=="function"&&q({animate:!1}))}),window.addEventListener("resize",()=>{CURRENT_PAGE==="logs"&&(c.layoutTimer&&window.clearTimeout(c.layoutTimer),c.layoutTimer=window.setTimeout(()=>{if(c.layoutTimer=null,!C())return;let n=isCompactLandscapeMode(),s=mt();if(c.layoutKey===s)return;c.landscape=n,c.layoutKey=s,c.routeHeights=Object.create(null),c.routeHeight=ut();let r=document.getElementById("dashcamRoutes");r&&(r.dataset.signature=""),N({animate:!1}),typeof q=="function"&&q({preserve:!0,animate:!1})},120))},{passive:!0})),e&&e.dataset.bound!=="1"&&(e.dataset.bound="1",e.addEventListener("scroll",()=>{ht(),bt("dashcam"),Dt(e)&&Se(),Nt(e)},{passive:!0}),e.addEventListener("scroll",n=>{let s=n.target?.closest?.(".dashcam-segment-list");!s||s===e||Da(s)},{passive:!0,capture:!0}),e.addEventListener("click",n=>{let s=n.target?.closest?.("[data-action]");if(!s)return;let r=s.dataset.action,o=s.dataset.route||"",i=s.dataset.segment||"";if(r==="toggle-route")c.expanded.has(o)?c.expanded.delete(o):c.expanded.add(o),o&&c.routeHeights&&delete c.routeHeights[o],pt(o)||N({animate:!1});else if(r==="play")Ot(o,i);else if(r==="segment-menu")n.stopPropagation(),kt(o,i).catch(()=>{});else if(r==="route-menu")n.stopPropagation(),Ka(o).catch(()=>{});else if(r==="select-route"){let l=s.dataset.selected==="1";Ja(o,l).catch(()=>{})}else if(r==="upload-selected"){let l=c.routes.find(d=>d.route===o),u=Ee(l||{segmentFolders:[]});Pe(u).catch(()=>{})}}),e.addEventListener("change",n=>{let s=n.target;if(!s?.matches?.('input[data-action="select-segment"]'))return;let r=s.dataset.segment||"";s.checked?c.selected.add(r):c.selected.delete(r);let o=s.closest("[data-route-card]")?.dataset.routeCard||"";ft(o)||N({animate:!1})})),a&&a.dataset.bound!=="1"&&(a.dataset.bound="1",a.addEventListener("scroll",()=>{ht(),bt("screen"),Ke(),ra(a)&&te({silent:!0,append:!0}).catch(()=>{})},{passive:!0}),a.addEventListener("click",n=>{let s=n.target?.closest?.("[data-action]");if(s)if(s.dataset.action==="download-screenrecord"){let r=s.dataset.id||"";r&&window.open(Je("download",r),"_blank","noopener")}else s.dataset.action==="play-screenrecord"&&We(s.dataset.id||"",s.dataset.name||"")}))}function vs(){Ss(),St(U,{load:!1}),Ba(),Oa().catch(()=>{}),dt().catch(()=>{}),U==="screen"?b.initialized?(q({preserve:!0}),te({silent:!0}).catch(()=>{})):(b.initialized=!0,te().catch(()=>{})):c.initialized?(N({animate:!1,preserve:!0}),ve({silent:!0}).catch(()=>{})):(c.initialized=!0,ve().catch(()=>{}))}var _s=Object.freeze({init:vs,activateTab:St,dashcam:Object.freeze({load:ve,render:N}),screenrecord:Object.freeze({load:te,render:q})});globalThis.CarrotLogsRuntime=_s;})(); +
    `;let $=x.frame.querySelector(".dashcam-player-title");$&&($.id="dashcamPlayerDialogTitle",x.dialog.setAttribute("aria-labelledby",$.id)),n.append(x.dialog);let b=n.querySelector("video");b.controls=!1,b.removeAttribute("controls");let T=n.querySelector(".dashcam-player-toast"),k=n.querySelector(".dashcam-player-subtitle"),w=e+(e.includes("?")?"&":"?")+"download=1",O=null,H=!0,_=null,Ie=!1,$e=!1,B=!1,ue=0,Ce=null,je=null,ce=n.querySelector(".dashcam-player-transport"),de=window.CarrotMediaTransport,X=null,me=null,Z=null,qt=y=>{let L=String(y||"");return L+(L.includes("?")?"&":"?")+"download=1"},ve=()=>o?.snapshot().activeSegment||"",mn=()=>{let y=Number(_?.duration??b.duration);return o&&typeof a.subtitleForSegmentDuration=="function"?a.subtitleForSegmentDuration(ve(),y)||p.subtitle:typeof a.subtitleForDuration=="function"&&a.subtitleForDuration(y)||p.subtitle},_t=()=>{if(!k)return;let y=mn();k.textContent=y||"",k.hidden=!y};b.addEventListener("loadedmetadata",_t);let j=y=>{!T||H||!y||(T.textContent=y,T.classList.add("is-visible"),O&&window.clearTimeout(O),O=window.setTimeout(()=>T.classList.remove("is-visible"),850))},Re=()=>{if(!B){B=!0;try{a.onClose?.(ve())}catch{}ue+=1,O&&window.clearTimeout(O),Ce?.dispose?.(),Ce=null;try{_?.destroy?.()}catch{}n.remove()}},Gt=y=>{$e||typeof y!="function"||($e=!0,Promise.resolve().then(()=>y({close:Re})).catch(L=>{typeof showAppToast=="function"&&showAppToast(L?.message||String(L),{tone:"error"})}).finally(()=>{$e=!1}))},Vt=()=>{if(!(!X||!Z)){if(o){let y=o.snapshot();X.disabled=!y.canMovePrevious,Z.disabled=!y.canMoveNext;return}X.disabled=typeof a.onPrevious!="function",Z.disabled=typeof a.onNext!="function"}},Ue=()=>{if(!me||!de?.setActionState)return;let y=(typeof _?.paused=="boolean"?_.paused:b.paused)||(typeof _?.ended=="boolean"?_.ended:b.ended);de.setActionState(me,y?"play":"pause",{label:y?getUIText("replay_play","Play replay"):getUIText("replay_pause","Pause replay")}),me.setAttribute("aria-pressed",String(!y))},Wt=()=>{try{_?.pause?_.pause():b.pause()}finally{window.requestAnimationFrame(Ue)}},gn=async()=>{try{let y=typeof _?.ended=="boolean"?_.ended:b.ended,L=typeof _?.paused=="boolean"?_.paused:b.paused;if(y&&(_?_.currentTime=0:b.currentTime=0),L||y){let M=_?.play?_.play():b.play();M&&typeof M.then=="function"&&await M}else Wt()}catch(y){typeof showAppToast=="function"&&showAppToast(y?.message||getUIText("playback_failed","Playback failed"),{tone:"error"})}finally{window.requestAnimationFrame(Ue)}},pn=y=>{let L=()=>{if(B||y!==ue)return;let M=_?.play?_.play():b.play();M&&typeof M.catch=="function"&&M.catch(()=>{})};window.setTimeout(()=>{if(B||y!==ue)return;let M=_?.media||b;M.readyState>=2?L():M.addEventListener("canplay",L,{once:!0})},0)},Jt=(y,L={})=>{if(!y?.src||B)return!1;let M=L.autoplay!==!1;p=y,ue+=1;let F=ue;return $&&($.textContent=p.title||p.name),_t(),_?(_.source={type:"video",title:p.title||p.name,sources:[{src:p.src,type:"video/mp4"}]},_.download=qt(p.src)):(b.src=p.src,b.load()),M&&pn(F),!0},Kt=(y,L=!0)=>{if(!y?.changed)return!1;let M=m.get(y.state.activeSegment);return M?(je?.setActive(y.state.activeSegment,!0),Vt(),Jt(M,{autoplay:L}),a.onSegmentChange?.(y.state.activeSegment),!0):!1},fn=y=>o?Kt(o.select(y)):!1,Yt=y=>o?Kt(o.move(y)):!1;o&&x.segmentHost&&(je=aa(g,{activeSegmentId:ve(),labels:{segments:getUIText("segment_browser","Segments"),reading:getUIText("segment_reading","Reading"),recent:getUIText("segment_recently_read","Recently viewed")},onSelect:fn,statusFor:y=>o.statusFor(y)}),x.segmentHost.hidden=!1,x.segmentHost.append(je.element)),ce&&de?.createActionButton?(X=de.createActionButton("previous",{label:getUIText("replay_previous_segment","Previous segment"),disabled:o?!o.snapshot().canMovePrevious:typeof a.onPrevious!="function",onActivate:()=>Gt(o?()=>Yt(-1):a.onPrevious)}),me=de.createActionButton("play",{playLabel:getUIText("replay_play","Play replay"),pauseLabel:getUIText("replay_pause","Pause replay"),onActivate:gn}),Z=de.createActionButton("next",{label:getUIText("replay_next_segment","Next segment"),disabled:o?!o.snapshot().canMoveNext:typeof a.onNext!="function",onActivate:()=>Gt(o?()=>Yt(1):a.onNext)}),ce.append(X,me,Z),["pointerdown","mousedown","touchstart"].forEach(y=>{ce.addEventListener(y,L=>L.stopPropagation(),{passive:!0})}),["play","pause","ended"].forEach(y=>{b.addEventListener(y,Ue)}),me.setAttribute("aria-keyshortcuts","Space K"),X.setAttribute("aria-keyshortcuts","Shift+P"),Z.setAttribute("aria-keyshortcuts","Shift+N"),Ue()):ce&&(ce.hidden=!0),Ce=de?.createInputController?.({media:b,stage:x.frame,rates:[.5,1,1.5,2],canInteract:()=>!B&&n.isConnected,canPrevious:()=>!!(X&&!X.disabled),canNext:()=>!!(Z&&!Z.disabled),onTogglePlayback:()=>me?.click(),onPrevious:()=>X?.click(),onNext:()=>Z?.click(),onRate:y=>{_?_.speed=y:b.playbackRate=y},onSeek:Ue,blockedPointerSelector:["button","input","select","a",".plyr__controls",".dashcam-player-top",".dashcam-player-transport"].join(", ")})||null,n.addEventListener("click",y=>{y.target===n&&Re()}),n.querySelector(".dashcam-player-close")?.addEventListener("click",Re);let Xt=(y,L,M)=>{if(y.stopPropagation(),Ie||typeof L!="function")return;Ie=!0;let F=y.currentTarget;F.disabled=!0,n.classList.add(M),Promise.resolve().then(()=>L({close:Re})).catch(Q=>{typeof showAppToast=="function"&&showAppToast(Q?.message||String(Q),{tone:"error"})}).finally(()=>{n.classList.remove(M),F.disabled=!1,Ie=!1})};return n.querySelector(".dashcam-player-send")?.addEventListener("click",y=>{let L=ve(),M=typeof a.onSegmentSend=="function"?({close:F}={})=>a.onSegmentSend(L,{close:F}):a.onSend;Wt(),Xt(y,M,"is-action-open")}),n.querySelector(".dashcam-player-menu")?.addEventListener("click",y=>{let L=ve(),M=typeof a.onSegmentMenu=="function"?({close:F}={})=>a.onSegmentMenu(L,{close:F}):a.onMenu;Xt(y,M,"is-menu-open")}),document.body.appendChild(n),requestAnimationFrame(()=>{if(!B){n.classList.add("is-open");try{_=new Plyr(b,{controls:["play","progress","current-time","duration","settings","fullscreen","download"],hideControls:!1,seekTime:5,settings:["speed"],speed:{selected:1,options:[.5,1,1.5,2]},keyboard:{focused:!0,global:!1},fullscreen:{enabled:!0,fallback:!0,iosNative:!0},urls:{download:qt(p.src)}}),b.controls=!1,b.removeAttribute("controls"),_.source={type:"video",title:p.title||p.name,sources:[{src:p.src,type:"video/mp4"}]},_.once("ready",()=>{let y=_.elements?.container||n;ce&&ce.parentElement!==y&&y.appendChild(ce),((M,F)=>{y.querySelectorAll(M).forEach(Q=>Q.addEventListener("click",()=>j(F)))})('[data-plyr="download"]',`⤓ ${getUIText("download","Download")}`),y.addEventListener("keydown",M=>{M.key==="ArrowLeft"?j(`⏪ ${getUIText("rewind_5","5s")}`):M.key==="ArrowRight"&&j(`${getUIText("forward_5","5s")} ⏩`)}),_.on("play",()=>j(`▶ ${getUIText("play","Play")}`)),_.on("pause",()=>j(`⏸ ${getUIText("pause","Pause")}`)),_.on("ended",()=>j(getUIText("ended","End"))),_.on("ratechange",()=>j(`⚡ ${_.speed}x`)),_.on("enterfullscreen",()=>j(`⛶ ${getUIText("fullscreen","Fullscreen")}`)),_.on("exitfullscreen",()=>j(getUIText("fullscreen_exit","Exit fullscreen"))),b.addEventListener("enterpictureinpicture",()=>j("⊞ PiP")),b.addEventListener("leavepictureinpicture",()=>j(`⊟ ${getUIText("pip_exit","Exit PiP")}`)),window.setTimeout(()=>{H=!1},350)})}catch{b.controls=!0,b.setAttribute("controls",""),b.src=p.src}}}),Object.freeze({close:Re,isOpen:()=>!B&&n.isConnected,updateSegments:y=>{if(!o||B)return null;let L=Ve(y);if(!L.length)return o.snapshot();let M=ve(),F=p.src;g=L,m=new Map(g.map(Ge=>[Ge.id,Ge]));let Q=o.replaceSegments(g.map(Ge=>Ge.id),{activeSegment:M}),qe=m.get(Q.state.activeSegment);return je?.render(g,Q.state.activeSegment),Vt(),qe&&(Q.state.activeSegment!==M||qe.src!==F?Jt(qe,{autoplay:!1}):(p=qe,$&&($.textContent=p.title||p.name),_t())),Q.state}})}function bt(t,e={}){let a=rn(t),n=e.load!==!1;a!==U&&St(U),U=a;for(let s of Ls()){let r=rn(s.dataset.logsTab)===U;s.classList.toggle("is-active",r),s.setAttribute("aria-selected",r?"true":"false");let o=document.getElementById(s.getAttribute("aria-controls")||"");o&&(o.hidden=!r)}ln?.sync(),n&&(U==="screen"&&!S.initialized?(S.initialized=!0,te().catch(()=>{})):U==="screen"?(q(),te({silent:!0}).catch(()=>{})):i.initialized&&_e({silent:!0}).catch(()=>{})),e.restoreScroll!==!1&&Ne(U)}function Ds(t){t&&(un(),t.dataset.bound!=="1"&&(t.dataset.bound="1",t.addEventListener("click",e=>{let a=e.target?.closest?.(Bt);a&&t.contains(a)&&bt(a.dataset.logsTab)}),ln=window.CarrotUI?.segmentedControl?.create(t,{itemSelector:Bt,onActivate:e=>bt(e?.dataset?.logsTab)})||null))}function Os(t){(t?.detail?.page||"")!=="logs"&&(St(U),Nt(),i.loadSeq+=1,S.loadSeq+=1,i.loading=!1,i.loadingMore=!1,i.loadingSegments?.clear?.(),J(!1),S.loading=!1,i.scrollBusy=!1,i.scrollTimer&&(window.clearTimeout(i.scrollTimer),i.scrollTimer=null),i.layoutTimer&&(window.clearTimeout(i.layoutTimer),i.layoutTimer=null),Ns())}function Hs(){let t=document.getElementById("logsTabs"),e=document.getElementById("dashcamRoutes"),a=document.getElementById("screenrecordVideos");Cs(),Ds(t),i.layoutBound||(i.layoutBound=!0,i.landscape=isCompactLandscapeMode(),i.layoutKey=mt(),window.addEventListener("carrot:pagechange",Os),window.addEventListener("carrot:languagechange",()=>{dn(),un(),i.signature="",S.signature="",i.routeHeights=Object.create(null);let n=document.getElementById("dashcamRoutes");n&&(n.dataset.signature="");let s=document.getElementById("screenrecordVideos");s&&(s.dataset.signature=""),C()&&(D({animate:!1}),typeof q=="function"&&q({animate:!1}))}),window.addEventListener("resize",()=>{CURRENT_PAGE==="logs"&&(i.layoutTimer&&window.clearTimeout(i.layoutTimer),i.layoutTimer=window.setTimeout(()=>{if(i.layoutTimer=null,!C())return;let n=isCompactLandscapeMode(),s=mt();if(i.layoutKey===s)return;i.landscape=n,i.layoutKey=s,i.routeHeights=Object.create(null),i.routeHeight=dt();let r=document.getElementById("dashcamRoutes");r&&(r.dataset.signature=""),D({animate:!1}),typeof q=="function"&&q({preserve:!0,animate:!1})},120))},{passive:!0})),e&&e.dataset.bound!=="1"&&(e.dataset.bound="1",e.addEventListener("scroll",()=>{ht(),St("dashcam"),Dt(e)&&be(),Ot(e)},{passive:!0}),e.addEventListener("scroll",n=>{let s=n.target?.closest?.(".dashcam-segment-list");!s||s===e||ja(s)},{passive:!0,capture:!0}),e.addEventListener("click",n=>{let s=n.target?.closest?.("[data-action]");if(!s)return;let r=s.dataset.action,o=s.dataset.route||"",c=s.dataset.segment||"";if(r==="toggle-route")i.expanded.has(o)?i.expanded.delete(o):i.expanded.add(o),o&&i.routeHeights&&delete i.routeHeights[o],pt(o)||D({animate:!1});else if(r==="play")kt(o,c);else if(r==="segment-menu")n.stopPropagation(),zt(o,c).catch(()=>{});else if(r==="route-menu")n.stopPropagation(),sn(o).catch(()=>{});else if(r==="select-route"){let l=s.dataset.selected==="1";nn(o,l).catch(()=>{})}else if(r==="upload-selected"){let l=i.routes.find(u=>u.route===o),d=Ee(l||{segmentFolders:[]});Fe(d).catch(()=>{})}}),e.addEventListener("change",n=>{let s=n.target;if(!s?.matches?.('input[data-action="select-segment"]'))return;let r=s.dataset.segment||"";s.checked?i.selected.add(r):i.selected.delete(r);let o=s.closest("[data-route-card]")?.dataset.routeCard||"";ft(o)||D({animate:!1})})),a&&a.dataset.bound!=="1"&&(a.dataset.bound="1",a.addEventListener("scroll",()=>{ht(),St("screen"),Ye(),ia(a)&&te({silent:!0,append:!0}).catch(()=>{})},{passive:!0}),a.addEventListener("click",n=>{let s=n.target?.closest?.("[data-action]");if(s)if(s.dataset.action==="download-screenrecord"){let r=s.dataset.id||"";r&&window.open(Ke("download",r),"_blank","noopener")}else s.dataset.action==="play-screenrecord"&&Je(s.dataset.id||"",s.dataset.name||"")}))}function ks(){Hs(),bt(U,{load:!1}),Ya(),Va().catch(()=>{}),ut().catch(()=>{}),U==="screen"?S.initialized?(q({preserve:!0}),te({silent:!0}).catch(()=>{})):(S.initialized=!0,te().catch(()=>{})):i.initialized?(D({animate:!1,preserve:!0}),_e({silent:!0}).catch(()=>{})):(i.initialized=!0,_e().catch(()=>{}))}var Ps=Object.freeze({init:ks,activateTab:bt,dashcam:Object.freeze({load:_e,render:D}),screenrecord:Object.freeze({load:te,render:q})});globalThis.CarrotLogsRuntime=Ps;})(); diff --git a/openpilot/selfdrive/carrot/web/js/realtime/mini_hud_mode.js b/openpilot/selfdrive/carrot/web/js/realtime/mini_hud_mode.js index 22b23f3fe8..48fb6922dc 100644 --- a/openpilot/selfdrive/carrot/web/js/realtime/mini_hud_mode.js +++ b/openpilot/selfdrive/carrot/web/js/realtime/mini_hud_mode.js @@ -77,7 +77,6 @@ '[role="dialog"][aria-modal="true"]', ".training-guide-modal", ".dashcam-player-overlay.is-open", - ".dashcam-upload-progress", ].join(","); return Array.from(document.querySelectorAll(modalSelector)).some(isRendered); } diff --git a/openpilot/selfdrive/carrot/web/js/translations/en.js b/openpilot/selfdrive/carrot/web/js/translations/en.js index 60838b97cd..1c6a638ed3 100644 --- a/openpilot/selfdrive/carrot/web/js/translations/en.js +++ b/openpilot/selfdrive/carrot/web/js/translations/en.js @@ -1232,6 +1232,16 @@ window.CarrotTranslations.register("en", { log_upload: "Upload Logs", log_upload_confirm: "Upload {count} logs to the Carrot server?", upload_data_warning: "This upload may use mobile data depending on your network connection.", + upload_original_files_confirm: "Upload the original files from these segments to the Carrot server?", + upload_segments_label: "Segments", + upload_segment_progress: "Segments {current}/{total}", + upload_more_failed: "{count} more failed", + upload_segment_count: "{count} segments", + upload_kind_count: "{name} {count}", + upload_summary_label: "Upload summary", + upload_total_count: "{count} files total", + upload_total_files: "Total {count} files · {size}", + upload_summary_unavailable: "Upload information is unavailable.", upload_file_count: "{count} files", upload_files_unknown: "files unknown", upload_size_unknown: "size unknown", @@ -1241,6 +1251,18 @@ window.CarrotTranslations.register("en", { upload_complete_count: "Upload complete {uploaded}/{total}", upload_canceled: "Upload canceled.", upload_canceling: "Canceling...", + upload_send: "Send", + upload_progress_label: "Log upload progress", + upload_phase_queued: "Waiting to upload", + upload_phase_preparing: "Preparing original files", + upload_phase_uploading: "Uploading original files", + upload_phase_notifying: "Sending completion notice", + upload_phase_canceling: "Canceling upload", + upload_phase_complete: "Upload complete", + upload_phase_canceled: "Upload canceled", + upload_phase_failed: "Upload failed", + upload_status_complete: "Complete", + upload_status_failed: "Failed", upload_already_running: "Upload already running.", dashcam_load_failed: "Failed to load dashcam list", screenrecord_empty: "No screen recordings found.", diff --git a/openpilot/selfdrive/carrot/web/js/translations/ko.js b/openpilot/selfdrive/carrot/web/js/translations/ko.js index 67c587dc69..7078426f83 100644 --- a/openpilot/selfdrive/carrot/web/js/translations/ko.js +++ b/openpilot/selfdrive/carrot/web/js/translations/ko.js @@ -222,7 +222,7 @@ window.CarrotTranslations.register("ko", { reboot: "재부팅", backup: "Backup", restore: "Restore", - copy: "Copy", + copy: "복사", view: "View", device_info: "기기 정보", device_tab_error: "기기 정보를 불러오지 못했습니다.", @@ -1230,6 +1230,16 @@ window.CarrotTranslations.register("ko", { log_upload: "로그 전송", log_upload_confirm: "당근 서버에 {count}개 로그를 전송할까요?", upload_data_warning: "네트워크 연결 상태에 따라 모바일 데이터가 사용될 수 있습니다.", + upload_original_files_confirm: "다음 세그먼트의 원본 파일을 당근 서버에 전송할까요?", + upload_segments_label: "세그먼트", + upload_segment_progress: "세그먼트 {current}/{total}", + upload_more_failed: "외 {count}개 실패", + upload_segment_count: "세그먼트 {count}개", + upload_kind_count: "{name} {count}개", + upload_summary_label: "전송 요약", + upload_total_count: "총 {count}개", + upload_total_files: "총 {count}개 · {size}", + upload_summary_unavailable: "전송 정보를 불러올 수 없습니다.", upload_file_count: "{count}개 파일", upload_files_unknown: "파일 수 알 수 없음", upload_size_unknown: "용량 알 수 없음", @@ -1239,6 +1249,18 @@ window.CarrotTranslations.register("ko", { upload_complete_count: "전송 완료 {uploaded}/{total}", upload_canceled: "전송이 취소되었습니다.", upload_canceling: "취소 중...", + upload_send: "전송", + upload_progress_label: "로그 전송 진행률", + upload_phase_queued: "전송 대기 중", + upload_phase_preparing: "원본 파일 준비 중", + upload_phase_uploading: "원본 파일 전송 중", + upload_phase_notifying: "전송 완료 알림 중", + upload_phase_canceling: "전송 취소 중", + upload_phase_complete: "전송 완료", + upload_phase_canceled: "전송 취소됨", + upload_phase_failed: "전송 실패", + upload_status_complete: "완료", + upload_status_failed: "실패", upload_already_running: "이미 로그 전송 중입니다.", dashcam_load_failed: "대시캠 목록 로드 실패", screenrecord_empty: "화면녹화 폴더/영상이 없습니다.", diff --git a/openpilot/selfdrive/carrot/web/js/translations/zh.js b/openpilot/selfdrive/carrot/web/js/translations/zh.js index 4d30347eb4..8954ce65c5 100644 --- a/openpilot/selfdrive/carrot/web/js/translations/zh.js +++ b/openpilot/selfdrive/carrot/web/js/translations/zh.js @@ -222,7 +222,7 @@ window.CarrotTranslations.register("zh", { reboot: "重启", backup: "备份设置", restore: "恢复设置", - copy: "Copy", + copy: "复制", view: "View", device_info: "设备信息", device_tab_error: "设备信息加载失败。", @@ -1167,6 +1167,16 @@ window.CarrotTranslations.register("zh", { log_upload: "发送日志", log_upload_confirm: "要向 Carrot 服务器发送 {count} 个日志吗?", upload_data_warning: "This upload may use mobile data depending on your network connection.", + upload_original_files_confirm: "要将这些片段的原始文件上传到 Carrot 服务器吗?", + upload_segments_label: "片段", + upload_segment_progress: "片段 {current}/{total}", + upload_more_failed: "另有 {count} 个失败", + upload_segment_count: "{count} 个片段", + upload_kind_count: "{name} {count} 个", + upload_summary_label: "发送摘要", + upload_total_count: "共 {count} 个文件", + upload_total_files: "共 {count} 个文件 · {size}", + upload_summary_unavailable: "无法获取上传信息。", upload_file_count: "{count} files", upload_files_unknown: "files unknown", upload_size_unknown: "size unknown", @@ -1176,6 +1186,18 @@ window.CarrotTranslations.register("zh", { upload_complete_count: "发送完成 {uploaded}/{total}", upload_canceled: "发送已取消。", upload_canceling: "正在取消...", + upload_send: "发送", + upload_progress_label: "日志发送进度", + upload_phase_queued: "等待发送", + upload_phase_preparing: "正在准备原始文件", + upload_phase_uploading: "正在发送原始文件", + upload_phase_notifying: "正在发送完成通知", + upload_phase_canceling: "正在取消发送", + upload_phase_complete: "发送完成", + upload_phase_canceled: "发送已取消", + upload_phase_failed: "发送失败", + upload_status_complete: "完成", + upload_status_failed: "失败", upload_already_running: "日志发送已在进行中。", dashcam_load_failed: "行车记录列表加载失败", screenrecord_empty: "没有屏幕录制视频。", diff --git a/openpilot/selfdrive/carrot/web/src/entries/design_system.css b/openpilot/selfdrive/carrot/web/src/entries/design_system.css index b5135e1728..c7b7a9e858 100644 --- a/openpilot/selfdrive/carrot/web/src/entries/design_system.css +++ b/openpilot/selfdrive/carrot/web/src/entries/design_system.css @@ -2,6 +2,7 @@ @import "../ui/primitives/controls.css"; @import "../ui/primitives/chip.css"; @import "../ui/components/dialog/style.css"; +@import "../ui/components/progress/style.css"; @import "../ui/components/menu/style.css"; @import "../ui/components/popover/style.css"; @import "../ui/components/feedback/style.css"; diff --git a/openpilot/selfdrive/carrot/web/src/features/logs/dashcam.js b/openpilot/selfdrive/carrot/web/src/features/logs/dashcam.js index f38b6faaf5..99b42c544c 100644 --- a/openpilot/selfdrive/carrot/web/src/features/logs/dashcam.js +++ b/openpilot/selfdrive/carrot/web/src/features/logs/dashcam.js @@ -20,6 +20,11 @@ import { syncLogsMenu, unobserveLogsLazyImages, } from "./runtime.js"; +import { + createDashcamUploadProgressModel, + dashcamUploadProgressState, +} from "./upload_progress.js"; +import { dashcamUploadStats } from "./upload_summary.js"; // Logs page — Dashcam tab. // Route + segment virtual listing, FFmpeg thumb/preview lazy load, @@ -36,6 +41,10 @@ const DASHCAM_SEGMENT_SECONDS = 60; const DASHCAM_REPLAY_TIME_TOLERANCE_SECONDS = 120; const DASHCAM_LOAD_AHEAD_VIEWPORTS = 1.5; const DASHCAM_ROUTE_WINDOW_OVERSCAN_VIEWPORTS = 1.25; +const DASHCAM_UPLOAD_POLL_INTERVAL_MS = 160; +const DASHCAM_UPLOAD_PROGRESS_MIN_VISIBLE_MS = 800; +const DASHCAM_UPLOAD_PROGRESS_FINISH_HOLD_MS = 240; +const DASHCAM_UPLOAD_RESULT_VISIBLE_LIMIT = 5; let dashcamUploadActiveJobId = null; let dashcamUploadResumePromise = null; let dashcamReadStateLoadPromise = null; @@ -1189,8 +1198,7 @@ function openDashcamPlayer(route, segment) { return formatDashcamTimeRange(targetEndEpoch - seconds, targetEndEpoch); }, onSegmentSend: (target) => uploadDashcamSegments([target], { - confirm: false, - showProgress: false, + showProgress: true, showResult: true, showSuccessToast: false, }), @@ -1239,18 +1247,6 @@ async function openDashcamDriveReplay(route, segment) { await startPromise; } -function dashcamUploadStats(items) { - const list = Array.isArray(items) ? items : []; - return list.reduce((stats, item) => { - const files = Array.isArray(item?.files) ? item.files : []; - const totalSize = Number(item?.totalSize) || files.reduce((sum, file) => sum + (Number(file?.size) || 0), 0); - stats.segments += 1; - stats.files += files.length; - stats.bytes += totalSize; - return stats; - }, { segments: 0, files: 0, bytes: 0 }); -} - function dashcamUploadSummaryLabel(stats) { const fileCount = Number(stats?.files || 0); const bytes = Number(stats?.bytes || 0); @@ -1261,15 +1257,82 @@ function dashcamUploadSummaryLabel(stats) { return `${fileLabel} · ${sizeLabel}`; } +function dashcamUploadConfirmHtml(stats) { + const segmentCountLabel = getUIText("upload_segment_count", "{count} segments", { + count: Number(stats?.segments || 0), + }); + const qcameraLabel = getUIText("upload_kind_count", "{name} {count}", { + name: "qcamera", + count: Number(stats?.qcamera || 0), + }); + const rlogLabel = getUIText("upload_kind_count", "{name} {count}", { + name: "rlog", + count: Number(stats?.rlog || 0), + }); + const totalCountLabel = getUIText("upload_total_count", "{count} files total", { + count: Number(stats?.files || 0), + }); + const bytes = Number(stats?.bytes || 0); + const sizeLabel = bytes > 0 ? formatLogBytes(bytes) : getUIText("upload_size_unknown", "size unknown"); + return `
    +
    + ${escapeHtml(segmentCountLabel)} + ${escapeHtml(`${qcameraLabel} · ${rlogLabel}`)} +
    +
    + ${escapeHtml(sizeLabel)} + ${escapeHtml(totalCountLabel)} +
    +
    `; +} + function dashcamUploadResultHtml(result) { - const text = String(result?.shareText || result?.message || ""); - const stats = dashcamUploadStats(result?.results || []); - return `