32edd07636
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
216 lines
22 KiB
HTML
216 lines
22 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Штурман — роадмапа</title>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3.6.0/dist/tabler-icons.min.css">
|
||
<style>
|
||
:root{
|
||
--bg0:#ffffff; --bg1:#f6f6f4; --bg2:#ececea; --tx0:#1a1a18; --tx1:#67675f; --tx2:#9a9a92;
|
||
--bd0:rgba(0,0,0,.13); --bd1:rgba(0,0,0,.24); --rmd:8px; --rlg:12px;
|
||
--v0:#6b7280; --v1:#2f6fd0; --v2b:#c2790a; --v3:#0d8a80; --v4:#7c54d0;
|
||
--font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
|
||
}
|
||
@media(prefers-color-scheme:dark){:root{
|
||
--bg0:#1f1f1d; --bg1:#272724; --bg2:#161614; --tx0:#ececea; --tx1:#a6a69f; --tx2:#74746e;
|
||
--bd0:rgba(255,255,255,.14); --bd1:rgba(255,255,255,.26);
|
||
--v0:#9aa3b2; --v1:#6ea2f0; --v2b:#e0a64a; --v3:#3bb9ad; --v4:#a98ae8;
|
||
}}
|
||
*{box-sizing:border-box}
|
||
body{margin:0;background:var(--bg2);color:var(--tx0);font-family:var(--font);line-height:1.55;font-size:15px;
|
||
-webkit-font-smoothing:antialiased}
|
||
.wrap{max-width:1080px;margin:0 auto;padding:28px 20px 64px}
|
||
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}
|
||
h1{font-size:22px;font-weight:600;margin:0 0 2px}
|
||
.sub{color:var(--tx1);font-size:14px;margin:0 0 18px}
|
||
.tabs{display:flex;gap:8px;margin:0 0 18px;flex-wrap:wrap}
|
||
.tab{appearance:none;border:.5px solid var(--bd1);background:var(--bg0);color:var(--tx0);font:inherit;font-size:14px;
|
||
padding:7px 14px;border-radius:var(--rmd);cursor:pointer;display:flex;align-items:center;gap:7px;transition:.12s}
|
||
.tab:hover{background:var(--bg1)}
|
||
.tab.on{background:var(--tx0);color:var(--bg0);border-color:var(--tx0)}
|
||
.view{display:none} .view.on{display:block}
|
||
.legend{display:flex;gap:14px;flex-wrap:wrap;color:var(--tx1);font-size:13px;margin:0 0 16px}
|
||
.legend i{font-style:normal;display:inline-flex;align-items:center;gap:5px}
|
||
.dot{width:10px;height:10px;border-radius:3px;display:inline-block}
|
||
|
||
.phase{background:var(--bg0);border:.5px solid var(--bd0);border-left:3px solid var(--ac);border-radius:var(--rlg);
|
||
margin:0 0 12px;overflow:hidden}
|
||
.phead{display:flex;align-items:center;gap:12px;padding:14px 16px;cursor:pointer;user-select:none}
|
||
.phead:hover{background:var(--bg1)}
|
||
.badge{font-weight:600;font-size:13px;color:var(--bg0);background:var(--ac);border-radius:6px;padding:3px 9px;flex:none}
|
||
.ptitle{font-weight:500;font-size:16px} .pdemo{color:var(--tx1);font-size:13px;margin-top:2px}
|
||
.pmeta{flex:1;min-width:0} .chev{color:var(--tx2);transition:.15s;flex:none}
|
||
.phase.open .chev{transform:rotate(90deg)}
|
||
.crit{font-size:12px;color:var(--ac);border:.5px solid var(--ac);border-radius:20px;padding:2px 9px;flex:none;white-space:nowrap}
|
||
.body{display:none;padding:4px 16px 16px;border-top:.5px solid var(--bd0)}
|
||
.phase.open .body{display:block}
|
||
|
||
.ms{border:.5px solid var(--bd0);border-radius:var(--rmd);margin-top:10px;background:var(--bg1)}
|
||
.mhead{display:flex;align-items:center;gap:10px;padding:10px 12px;cursor:pointer}
|
||
.mhead:hover{background:var(--bg2)}
|
||
.mid{font-weight:600;font-size:13px;color:var(--ac);flex:none;font-variant-numeric:tabular-nums}
|
||
.mname{font-weight:500;font-size:14px;flex:1}
|
||
.killer{font-size:11px;font-weight:600;background:var(--v2b);color:#000;border-radius:5px;padding:2px 7px;flex:none}
|
||
.mdet{display:none;padding:2px 12px 13px 12px;font-size:13.5px}
|
||
.ms.open .mdet{display:block}
|
||
.row{display:flex;gap:8px;padding:5px 0;border-top:.5px solid var(--bd0)}
|
||
.row:first-child{border-top:none}
|
||
.k{color:var(--tx2);flex:none;width:104px;font-size:12.5px;padding-top:1px}
|
||
.v{color:var(--tx0)}
|
||
.ids{display:flex;flex-wrap:wrap;gap:4px}
|
||
.id{font-family:ui-monospace,Menlo,monospace;font-size:12px;background:var(--bg0);border:.5px solid var(--bd0);
|
||
border-radius:5px;padding:1px 6px;color:var(--tx0)}
|
||
.gates{margin-top:12px;font-size:13px;color:var(--tx1)}
|
||
.gates b{color:var(--tx0);font-weight:500}
|
||
|
||
.flow{display:flex;flex-direction:column;gap:6px}
|
||
.fstep{display:flex;align-items:center;gap:12px;background:var(--bg0);border:.5px solid var(--bd0);border-left:3px solid var(--ac);
|
||
border-radius:var(--rmd);padding:11px 14px}
|
||
.fb{font-weight:600;font-size:13px;color:var(--bg0);background:var(--ac);border-radius:6px;padding:3px 9px;flex:none}
|
||
.fchain{font-size:13.5px;color:var(--tx0)} .fchain b{color:var(--ac);font-weight:600}
|
||
.arrow{color:var(--tx2);text-align:center;font-size:18px;line-height:.7}
|
||
.note{color:var(--tx1);font-size:13px;margin-top:14px;padding:12px 14px;background:var(--bg0);border:.5px solid var(--bd0);border-radius:var(--rmd)}
|
||
|
||
.rk{width:100%;border-collapse:collapse;font-size:13.5px}
|
||
.rk th{text-align:left;font-weight:500;color:var(--tx1);font-size:12.5px;padding:8px 10px;border-bottom:.5px solid var(--bd1)}
|
||
.rk td{padding:9px 10px;border-bottom:.5px solid var(--bd0);vertical-align:top}
|
||
.rk tr:hover td{background:var(--bg1)}
|
||
.rg{font-family:ui-monospace,Menlo,monospace;font-size:12px;color:var(--tx0)}
|
||
.by{font-weight:600;color:var(--ac2);white-space:nowrap}
|
||
.hg{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:12px}
|
||
.hcard{background:var(--bg0);border:.5px solid var(--bd0);border-radius:var(--rlg);padding:14px 16px}
|
||
.hcard h3{font-size:14px;font-weight:500;margin:0 0 8px;color:var(--tx1)}
|
||
.hitem{display:flex;gap:8px;align-items:baseline;padding:4px 0;font-size:13.5px}
|
||
.hitem .id{flex:none}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<h2 class="sr">Интерактивная роадмапа проекта Штурман: пять фаз v0–v4, разложенных на вехи с зависимостями, тестом без машины и критериями готовности, критический путь и реестр рисков.</h2>
|
||
<h1>Штурман — роадмапа</h1>
|
||
<p class="sub">Лестница v0–v4: каждая фаза → вехи с зависимостями, тестом без машины и критерием готовности. 170 функций, источник правды — <span class="rg">docs/roadmap.md</span>.</p>
|
||
|
||
<div class="tabs" id="tabs">
|
||
<button class="tab on" data-v="ladder"><i class="ti ti-stairs-up" aria-hidden="true"></i>Лестница</button>
|
||
<button class="tab" data-v="crit"><i class="ti ti-route" aria-hidden="true"></i>Критпуть</button>
|
||
<button class="tab" data-v="risk"><i class="ti ti-alert-triangle" aria-hidden="true"></i>Риски</button>
|
||
<button class="tab" data-v="horizon"><i class="ti ti-telescope" aria-hidden="true"></i>Горизонт</button>
|
||
</div>
|
||
|
||
<div class="view on" id="ladder">
|
||
<div class="legend" id="legend"></div>
|
||
<div id="phases"></div>
|
||
</div>
|
||
<div class="view" id="crit"><div class="flow" id="flow"></div>
|
||
<div class="note">Параллельно (не на критпути): телефон/медиа/камера (v2.4–v2.6), companion (v3.4), расширения (v3.5); нав- и OTA-ветки v4 идут одновременно. Сквозной enabling-трек — dev-environment.</div>
|
||
</div>
|
||
<div class="view" id="risk"><table class="rk"><thead><tr><th>Гейт</th><th>Выбор между</th><th>Решить к</th><th>Док</th></tr></thead><tbody id="risks"></tbody></table></div>
|
||
<div class="view" id="horizon"><p class="sub" style="margin-bottom:14px">За горизонтом v4 — доразвитие из тех же доменов по мере спроса (20 функций, вне лестницы v0–v4).</p><div class="hg" id="hgrid"></div></div>
|
||
</div>
|
||
|
||
<script>
|
||
const AC={v0:'var(--v0)',v1:'var(--v1)',v2:'var(--v2b)',v3:'var(--v3)',v4:'var(--v4)'};
|
||
const P=[
|
||
{id:'v0',t:'База + Shell',demo:'вкл/выкл в машине → мгновенный красивый UI, переживает срыв питания',crit:'образ → power-safe → shell',
|
||
gates:'A01 (Armbian/Debian vs Yocto) · A02 (f2fs vs ext4) · B08/B09 (MCU vs supercap)',
|
||
ms:[
|
||
{id:'v0.1',n:'Образ-болванка',ids:'A01 A02 A06 A17',dep:'hardware/BSP',test:'Lima-VM',done:'init поднялся; RO-rootfs A/B + overlay + /data монтируются'},
|
||
{id:'v0.2',n:'Boot-конвейер',ids:'A04 A05 A15',dep:'v0.1',test:'VM boot',done:'Stage 0/1/2 разделены; splash мгновенно'},
|
||
{id:'v0.3',n:'Power-safe ядро',ids:'B01 B03 B04 B02 B06 B07 A14 B05',dep:'v0.2, hold-up (hw §3)',test:'fake-ACC + power-cut в VM',done:'N циклов зажигания без потери /data; abort до PONR'},
|
||
{id:'v0.4',n:'MCU/thermal fail-safe',ids:'B08 B09 B10 A12',dep:'v0.3, мок-MCU',test:'мок-MCU/sensor',done:'thermal-trip → graceful; MCU-таймер режет питание при зависании SoC'},
|
||
{id:'v0.5',n:'Shell первый кадр',ids:'C03 C04 C01 C05 C02 C07 C09 C10',dep:'v0.2, design-system',test:'нативный Slint + VM',done:'до интерактива < бюджет (perf §3); авто день/ночь'},
|
||
{id:'v0.6',n:'База-доводка + dev-харнесс',ids:'A09 A10 A11 A07 A16 F01 F02 F03 F04 J06',dep:'параллельно',test:'сам harness',done:'dev-итерация без железа; память/лог/eMMC в бюджете'}]},
|
||
{id:'v1',t:'Ассистент онлайн + связь + аудио + Location',demo:'«Штурман, …» → устный RU-ответ; аудио с ducking; distraction по GPS',crit:'ассистент-онлайн',
|
||
gates:'K05 (маппинг руля/ADC) · B08/B09 (если MCU не закрыт) · distraction-числа (safety §4)',
|
||
ms:[
|
||
{id:'v1.1',n:'Аудио-плоскость',ids:'H01 H02 H05 H04',dep:'v0 (PipeWire/WirePlumber)',test:'fake-аудио (H §12)',done:'ducking-лестница; громкость с руля; ducking ≤150 ms'},
|
||
{id:'v1.2',n:'Location / датчики',ids:'K01 K02 K03 K04 A08',dep:'v0, fake-GPS',test:'NMEA-реплей',done:'Location публикует zero-clamp-скорость; time-sync от GPS'},
|
||
{id:'v1.3',n:'Связь-core',ids:'G01 G02 G03',dep:'v0, NM/MM',test:'мок-сеть',done:'portal/limited не врёт «online»; статус в баре'},
|
||
{id:'v1.4',n:'Ассистент-пайплайн (офлайн-узлы)',ids:'D01 D02 D03 D04 D05 H03',dep:'v1.1, mic',test:'моки STT(текст)/TTS(лог)',done:'wake→STT→TTS офлайн; AEC держит wake во время медиа; wake ≤400 ms'},
|
||
{id:'v1.5',n:'Интенты + онлайн-LLM',ids:'D06 D07 D08 D09 D11',dep:'v1.4, v1.3',test:'мок-LLM (canned) + мок-сеть',done:'голос→ответ; «нет сети» graceful; локальный интент ≤300 ms без LLM'},
|
||
{id:'v1.6',n:'Distraction + руль',ids:'C11 C12 D12 C13 K05',dep:'v1.2, v1.4, safety §4',test:'fake-GPS speed + мок-руль',done:'distraction по порогам safety §4; навигация/громкость рулём'},
|
||
{id:'v1.7',n:'База-доводка v1',ids:'A13 B11 B12 B13',dep:'v0.4',test:'мок',done:'wake-word гейтится состояниями; sleep/wake базово'}]},
|
||
{id:'v2',t:'Контекст машины (killer) + телефон + медиа + камера',demo:'«прочитай ошибки / что значит лампочка» → live OBD+DTC по-человечески',crit:'контекст машины (killer)',
|
||
gates:'E03 (DTC-база своя RU vs готовая) · H06 (декодер/AAC-патент) · J01 (DRM-handoff Stage 0→1)',
|
||
ms:[
|
||
{id:'v2.1',n:'CAN-транспорт',ids:'E01 E04 E06 E05',dep:'v0/v1, data-model, hw(CAN), ISO-TP',test:'Vehicle Simulator (ELM327-emu + vcan)',done:'live-сигналы rate-cap ~10–20 Гц; engine_running (не дублирует Power)'},
|
||
{id:'v2.2',n:'DTC + диагностика',ids:'E02 E03 E07 E08',dep:'v2.1, DTC-база',test:'симулятор инжектит P0420',done:'читает реальные DTC + RU-расшифровка'},
|
||
{id:'v2.3',n:'Vehicle-context',killer:1,ids:'D10 C08',dep:'v2.2, v1-ассистент (D06/D08)',test:'симулятор P0420 → объяснение по-русски',done:'killer-demo: голос «что за ошибка» → live OBD+DTC → человеческое объяснение'},
|
||
{id:'v2.4',n:'Телефон',ids:'G04 G05 G06 G07 G08 G09 G10',dep:'v1.1, v1.6, BT, D §6',test:'fake-BT-стек (G §12)',done:'звонок руль/тач; контакты PBAP; входящий-оверлей vs реверс'},
|
||
{id:'v2.5',n:'Медиа',ids:'H06 H07 H08 H09 H10 C06',dep:'v2.4, v1.1, storage',test:'синтет-медиа + fake-A2DP',done:'локальный трек + BT-музыка; now-playing — первая Wayland-поверхность'},
|
||
{id:'v2.6',n:'Камера',ids:'A18 J01 J02 J03 J04 J05 K06 B14',dep:'v2.1, C06, A §4/B §7',test:'fake-камера (паттерн/no-signal/реверс)',done:'реверс→камера приоритетно; парктроник; fail-safe «нет сигнала»'},
|
||
{id:'v2.7',n:'Телеметрия-задел',ids:'L07',dep:'security-privacy §7, consent',test:'мок-телеметрия-sink',done:'по умолчанию ноль egress; consent-гейт'}]},
|
||
{id:'v3',t:'Офлайн-фолбэк + Plugin API + companion',demo:'ответ без сети + установка стороннего плагина + companion-телефон',crit:'офлайн + Plugin API',
|
||
gates:'D13 (офлайн-модель) · D14 (память/consent) · F11 (подпись) · L03 (моб-стек) · L06 (push) · H11 (FM-тюнер)',
|
||
ms:[
|
||
{id:'v3.1',n:'Plugin host',ids:'F05 F06 F07 F08 F09 F10 C14',dep:'v0 (App-Host, a-base §3), plugin-sdk',test:'plugin-host-харнесс (F)',done:'плагин install/update/remove в песочнице; ревью разрешений (perm-UI)'},
|
||
{id:'v3.2',n:'Plugin-интенты + подпись',ids:'D15 F11',dep:'v3.1, v1-ассистент',test:'тест-плагин с intent',done:'плагин регистрирует интент; подпись манифеста'},
|
||
{id:'v3.3',n:'Офлайн-ассистент',ids:'D13 D14',dep:'v1-пайплайн, storage',test:'мок-сеть off + локальная модель',done:'сеть выключена → локальный ответ; память водителя (.md)'},
|
||
{id:'v3.4',n:'Companion',ids:'L01 L02 L03 L04 L05 L06 L08',dep:'v1.3, v2 (E/trip-плагин)',test:'фейк-companion-peer + мок-облако',done:'паринг; синк настроек/памяти/поездок local-first; бэкап'},
|
||
{id:'v3.5',n:'Связь + медиа-расширения',ids:'G11 G12 H11 H12 H13 H14',dep:'v1.3, v3.1, network',test:'моки сети/медиа',done:'WiFi-hotspot; SMS-чтение; media-source через плагин'}]},
|
||
{id:'v4',t:'Навигация + OTA + прод-образ + ретрофит',demo:'офлайн-навигация + OTA + документированный ретрофит на реальное авто',crit:'первый «продукт»',
|
||
gates:'I03 (Valhalla vs OSRM) · I01 (карты + ODbL + размещение) · G13 (проекция scope/legal)',
|
||
ms:[
|
||
{id:'v4.1',n:'Карты + рендер',ids:'I01 I02 I04 I08',dep:'tech-stack, хранилище, C §4, G §2',test:'нав-сим (тест-регион PMTiles)',done:'карта рендерится офлайн (GPU); детект «вне региона»; геокодер'},
|
||
{id:'v4.2',n:'Роутинг + ведение',ids:'I03 I05 I06 I07 I09 I10 I11 I12 I13 K09',dep:'v4.1, v1.2, v1.1, D §6',test:'мок-маршрут (rerouting/maneuver)',done:'turn-by-turn-ведение; map-matching; назначение голосом; resume'},
|
||
{id:'v4.3',n:'Прод-образ + secure boot',ids:'A03 A20 A21 A22',dep:'v0-образ, hardware',test:'флеш на железо (HW-in-the-loop)',done:'релиз-образ прошивается; verified boot; /data шифрован; factory reset'},
|
||
{id:'v4.4',n:'OTA-канал',ids:'A19 L09 L10 L11 L12 L13',dep:'v4.3 (trust-anchor), v3.4, F11',test:'мок-облако/OTA (подписанные/битые/downgrade)',done:'OTA доставляет/применяет/откатывает; anti-rollback; каналы stable/beta'},
|
||
{id:'v4.5',n:'Ретрофит + опц.',ids:'G13',dep:'всё v0–v4 на реальном авто',test:'реальная установка',done:'документированный ретрофит на одну машину (BSP/калибровка/гайд)'}]}
|
||
];
|
||
const CRIT=[
|
||
{p:'v0',c:'<b>v0.1</b> образ → <b>v0.2</b> boot → <b>v0.3</b> power-safe → <b>v0.5</b> shell'},
|
||
{p:'v1',c:'<b>v1.1</b> аудио + <b>v1.2</b> Location + <b>v1.3</b> сеть → <b>v1.4</b> пайплайн → <b>v1.5</b> онлайн-LLM'},
|
||
{p:'v2',c:'<b>v2.1</b> CAN → <b>v2.2</b> DTC → <b>v2.3</b> контекст (KILLER)'},
|
||
{p:'v3',c:'<b>v3.1</b> plugin-host → <b>v3.3</b> офлайн-LLM'},
|
||
{p:'v4',c:'<b>v4.1</b> карты → <b>v4.2</b> роутинг ‖ <b>v4.3</b> secure boot → <b>v4.4</b> OTA → <b>v4.5</b> ретрофит'}
|
||
];
|
||
const RISK=[
|
||
['A01','Armbian/Debian vs Yocto','v0','a-base §2'],['A02','f2fs vs ext4','v0','a-base §3'],
|
||
['B08/B09','MCU-копилот vs supercap-only','v0','b-power §5, hw §3'],
|
||
['distraction','числа км/ч + список блокируемого','v1','safety §4'],
|
||
['perf','латентные числа (кадр/голос/ввод)','по фазам','performance §3'],
|
||
['K05','маппинг кнопок / ADC-калибровка','v1/v2','k-sensors §3'],
|
||
['E03','своя RU DTC-база vs готовая','v2','e-vehicle-data §5'],
|
||
['H06','symphonia + AAC-патент (юр)','v2','h-media §5'],
|
||
['J01','DRM-master handoff Stage 0→1','v2','j-cameras §3'],
|
||
['D13','YandexGPT Lite / T-lite / Qwen','v3','d-assistant §5'],
|
||
['D14','что авто-запоминать / схема / consent','v3','d-assistant §7'],
|
||
['F11','формат / keyring / ревокация','v3','f-plugin §3, sec-priv §6'],
|
||
['L03','Flutter vs Rust-core+native','v3','l-cloud §3'],
|
||
['L06','self-relay vs APNs/FCM vs локально','v3','l-cloud §5'],
|
||
['H11','FM-тюнер: добавить vs отказаться','v3','h-media §7, hw §4'],
|
||
['A19','RAUC vs Mender/swupdate/OSTree','v4','a-base §5'],
|
||
['I03','Valhalla vs OSRM','v4','i-nav §4'],
|
||
['I01','ODbL-атрибуция + размещение данных','v4','i-nav §2'],
|
||
['G13','not-first-party / плагин / вне скоупа','v4','g-conn §8']
|
||
];
|
||
const esc=s=>s.replace(/&/g,'&').replace(/</g,'<');
|
||
function ph(p){
|
||
const m=p.ms.map(x=>`<div class="ms" style="--ac:${AC[p.id]}"><div class="mhead"><span class="mid">${x.id}</span><span class="mname">${esc(x.n)}</span>${x.killer?'<span class="killer">killer</span>':''}<i class="ti ti-chevron-down chev" aria-hidden="true"></i></div><div class="mdet"><div class="row"><span class="k">ID каталога</span><span class="v ids">${x.ids.split(' ').map(i=>`<span class="id">${i}</span>`).join('')}</span></div><div class="row"><span class="k">Зависит от</span><span class="v">${esc(x.dep)}</span></div><div class="row"><span class="k">Тест без машины</span><span class="v">${esc(x.test)}</span></div><div class="row"><span class="k">Готово, когда</span><span class="v">${esc(x.done)}</span></div></div></div>`).join('');
|
||
return `<div class="phase" style="--ac:${AC[p.id]}"><div class="phead"><span class="badge">${p.id}</span><div class="pmeta"><div class="ptitle">${esc(p.t)}</div><div class="pdemo">${esc(p.demo)}</div></div><span class="crit">${esc(p.crit)}</span><i class="ti ti-chevron-right chev" aria-hidden="true"></i></div><div class="body">${m}<div class="gates"><b><i class="ti ti-alert-triangle" aria-hidden="true"></i> Гейты фазы:</b> ${esc(p.gates)}</div></div></div>`;
|
||
}
|
||
document.getElementById('phases').innerHTML=P.map(ph).join('');
|
||
document.getElementById('legend').innerHTML=P.map(p=>`<i><span class="dot" style="background:${AC[p.id]}"></span>${p.id} — ${esc(p.crit)}</i>`).join('');
|
||
document.getElementById('flow').innerHTML=CRIT.map((s,i)=>`<div class="fstep" style="--ac:${AC[s.p]}"><span class="fb">${s.p}</span><span class="fchain">${s.c}</span></div>${i<CRIT.length-1?'<div class="arrow">↓</div>':''}`).join('');
|
||
const acFor=s=>{const m=(s||'').match(/v[0-4]/);return AC[m?m[0]:'v0'];};
|
||
document.getElementById('risks').innerHTML=RISK.map(r=>`<tr style="--ac2:${acFor(r[2])}"><td class="rg">${esc(r[0])}</td><td>${esc(r[1])}</td><td class="by">${esc(r[2])}</td><td class="rg">${esc(r[3])}</td></tr>`).join('');
|
||
const HORIZON=[
|
||
{g:'База',items:[['A23','мульти-BSP'],['A24','kernel/dtb-тюнинг (ongoing)']]},
|
||
{g:'Shell / ассистент',items:[['C15','мультидисплей / профили / виджеты'],['D16','barge-in']]},
|
||
{g:'Vehicle-Data',items:[['E09','trip-производные (у плагина)'],['E10','fuel-trim'],['E11','VIN'],['E12','DBC-сниффинг'],['E13','vendor-DTC'],['E14','лог поездок']]},
|
||
{g:'Экосистема / медиа',items:[['F12','курируемый стор'],['H15','мульти-зона / EQ / A2DP-source']]},
|
||
{g:'Навигация',items:[['I14','трафик TMC/RDS'],['I15','онлайн-трафик / поиск'],['I16','DR в тоннелях']]},
|
||
{g:'Камеры / датчики',items:[['J07','0..N источников'],['J08','dashcam'],['J09','surround / 360°'],['K07','IMU'],['K08','выделенные не-CAN датчики']]}
|
||
];
|
||
document.getElementById('hgrid').innerHTML=HORIZON.map(g=>`<div class="hcard"><h3>${esc(g.g)}</h3>${g.items.map(it=>`<div class="hitem"><span class="id">${it[0]}</span><span>${esc(it[1])}</span></div>`).join('')}</div>`).join('');
|
||
document.addEventListener('click',e=>{
|
||
const t=e.target.closest('.tab'); if(t){document.querySelectorAll('.tab').forEach(x=>x.classList.toggle('on',x===t));document.querySelectorAll('.view').forEach(v=>v.classList.toggle('on',v.id===t.dataset.v));return;}
|
||
const mh=e.target.closest('.mhead'); if(mh){mh.parentElement.classList.toggle('open');return;}
|
||
const phd=e.target.closest('.phead'); if(phd){phd.parentElement.classList.toggle('open');}
|
||
});
|
||
document.querySelector('.phase').classList.add('open');
|
||
</script>
|
||
</body>
|
||
</html>
|