<!doctype html>
<html lang="ru"><head><meta charset="utf-8"/>
<title>Ordinis · compact redesign</title>
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover"/>
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&family=Tektur:wght@500;600;700&display=swap" rel="stylesheet"/>
<style>
*{box-sizing:border-box}
html,body{margin:0;background:var(--bg);color:var(--ink);font:13px/1.4 "Inter",ui-sans-serif,system-ui,sans-serif;font-feature-settings:"ss01","cv11","cv02","cv03","cv04";-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:hidden;transition:background .2s,color .2s}
button,input,select{font:inherit}
input:focus,select:focus,button:focus{outline:none}
.mono{font-family:"JetBrains Mono",ui-monospace,monospace;letter-spacing:.02em}
.cap{font-family:"Tektur","JetBrains Mono",ui-monospace,monospace;font-size:10.5px;font-weight:500;letter-spacing:.10em;text-transform:uppercase;color:var(--mute)}
.brand{font-family:"Tektur",ui-sans-serif,system-ui,sans-serif;letter-spacing:.04em}
:root,:root[data-theme="claude-light"]{
  /* Earthy light — v2. Cleaner contrast, less muddy. */
  --ink:#1a1714;--ink-2:#52483d;--mute:#8c8276;
  --line:#e3dccb;--line-2:#efe9d8;--bg:#fbf8ee;
  --surface:#ffffff;--surface-2:#f4eedc;
  --navy:#3a2818;--accent:#b05a2e;--accent-bg:rgba(176,90,46,.11);
  --green:#4f7038;--green-bg:#e4ead0;
  --warn:#a8762a;--warn-bg:#f6e8bc;
  --pink:#a64636;--pink-bg:#f4d9cd;
  --on-accent:#ffffff;
}
:root[data-theme="dark"]{
  --ink:#f0ece4;--ink-2:#c8c1b3;--mute:#8a8474;
  --line:#3a3733;--line-2:#2d2a27;--bg:#1c1a17;
  --surface:#262421;--surface-2:#2e2b27;
  --navy:#d97757;--accent:#d97757;--accent-bg:rgba(217,119,87,.16);
  --green:#6fa97d;--green-bg:rgba(111,169,125,.18);
  --warn:#d4a653;--warn-bg:rgba(212,166,83,.18);
  --pink:#d18272;--pink-bg:rgba(209,130,114,.20);
  --on-accent:#1c1a17;
}
@media (max-width: 1024px){
  body > #root [style*="grid-template-columns: 220px"],
  body > #root [style*="grid-template-columns: 56px"]{grid-template-columns:0 1fr !important}
  body > #root aside[style*="borderRight"]{position:fixed;inset:0 auto 0 0;z-index:55;width:220px;transform:translateX(-100%);transition:transform .18s}
  body > #root aside[style*="borderRight"].open{transform:none;box-shadow:0 0 0 100vmax rgba(15,17,21,.42)}
}
@media (max-width: 880px){
  body > #root [style*="calc(100vh - 76px)"]{height:auto !important;grid-template-columns:1fr !important}
  body > #root [style*="width: 560"],body > #root [style*="width: 460"]{width:100vw !important;max-width:100vw !important;border-radius:0 !important}
  body > #root [style*="width: 460"]{height:100dvh !important}
}
.editor-tabs > div::-webkit-scrollbar{display:none}
.editor-tabs > div{scrollbar-width:none}
@media (max-width: 560px){
  body > #root [style*="padding: 14"]{padding:8px !important}
  body > #root th:nth-child(n+7),body > #root td:nth-child(n+7){display:none}
}
</style></head><body>
<div id="root"></div>

<div id="theme-switch" style="position:fixed;top:10px;right:10px;z-index:60;display:inline-flex;border:1px solid var(--line);border-radius:8px;background:var(--surface);box-shadow:0 6px 16px -8px rgba(0,0,0,.25);overflow:hidden;font-family:Tektur,sans-serif;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;font-weight:600">
  <button data-th="claude-light" class="on" style="height:26px;padding:0 10px;border:none;background:transparent;color:var(--mute);cursor:pointer;font:inherit;border-right:1px solid var(--line)">Earth</button>
  <button data-th="dark" style="height:26px;padding:0 10px;border:none;background:transparent;color:var(--mute);cursor:pointer;font:inherit">Dark</button>
</div>
<script>
(function(){
  const sw=document.getElementById('theme-switch');
  const saved=localStorage.getItem('ord-theme')||'claude-light';
  apply(saved);
  sw.addEventListener('click',e=>{const b=e.target.closest('button[data-th]');if(!b)return;apply(b.dataset.th);localStorage.setItem('ord-theme',b.dataset.th);});
  function apply(th){document.documentElement.setAttribute('data-theme',th);sw.querySelectorAll('button').forEach(b=>{const on=b.dataset.th===th;b.style.background=on?'var(--accent)':'transparent';b.style.color=on?'var(--on-accent)':'var(--mute)';});}
})();
</script>

<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>

<script type="text/babel" data-presets="env,react">
const { useState } = React;

const NAV = [
  {k:"home", label:"Главная", icon:"⌂"},
  {k:"dicts", label:"Справочники", icon:"⊞", active:true, count:40},
  {k:"my-drafts", label:"Мои черновики", icon:"✎", count:3},
  {k:"reviews", label:"На ревью", icon:"◐", count:7},
  {k:"outbox", label:"Outbox", icon:"↗", count:2},
  {k:"webhooks", label:"Webhooks", icon:"⇆"},
  {k:"audit", label:"Аудит", icon:"☷"},
  {k:"search", label:"Поиск", icon:"⌕"},
];

const DICTS=[
  {id:"satellites",title:"Космические аппараты",desc:"Каталог КА ДЗЗ-миссий",scope:"PUBLIC",bundle:"cuod",records:127,fk:5,refBy:1,updated:"2ч"},
  {id:"satellite_types",title:"Типы КА",desc:"Классификация по миссиям",scope:"PUBLIC",bundle:"cuod",records:14,fk:0,refBy:2,updated:"3д"},
  {id:"satellite_statuses",title:"Статусы КА",desc:"planned → operational → decommissioned",scope:"PUBLIC",bundle:"cuod",records:5,fk:0,refBy:1,updated:"3д"},
  {id:"missions",title:"Миссии / программы",desc:"Ресурс-П, Канопус-В, Sentinel",scope:"PUBLIC",bundle:"cuod",records:42,fk:2,refBy:1,updated:"1д"},
  {id:"instruments",title:"Инструменты съёмки",desc:"Полезная нагрузка / сенсоры",scope:"PUBLIC",bundle:"cuod",records:89,fk:2,refBy:2,updated:"6ч"},
  {id:"sensor_types",title:"Типы сенсоров",desc:"optical/SAR/hyperspectral/lidar",scope:"PUBLIC",bundle:"cuod",records:7,fk:0,refBy:1,updated:"3д"},
  {id:"spectral_bands",title:"Спектральные каналы",desc:"PAN/R/G/B/NIR/SWIR/TIR",scope:"PUBLIC",bundle:"cuod",records:23,fk:1,refBy:1,updated:"1д"},
  {id:"ground_stations",title:"Наземные станции",desc:"Пункты приёма и управления",scope:"PUBLIC",bundle:"cuod",records:34,fk:2,refBy:1,updated:"4ч"},
  {id:"antennas",title:"Антенны",desc:"С G/T и диапазонами",scope:"PUBLIC",bundle:"cuod",records:58,fk:1,refBy:1,updated:"5д"},
  {id:"orbit_types",title:"Типы орбит",desc:"LEO / SSO / MEO / GEO / HEO",scope:"PUBLIC",bundle:"cuod",records:5,fk:0,refBy:1,updated:"1мес"},
  {id:"countries",title:"Страны",desc:"ISO 3166: коды и имена",scope:"PUBLIC",bundle:"geo",records:249,fk:0,refBy:2,updated:"6мес"},
  {id:"timezones",title:"Часовые пояса",desc:"IANA tz database",scope:"PUBLIC",bundle:"geo",records:419,fk:1,refBy:1,updated:"6мес"},
  {id:"languages",title:"Языки",desc:"BCP-47: ru-RU, en-US…",scope:"PUBLIC",bundle:"i18n",records:14,fk:0,refBy:1,updated:"1мес"},
  {id:"acquisitions",title:"Сеансы съёмки",desc:"Планы и факты сеансов",scope:"INTERNAL",bundle:"cuod",records:2841,fk:5,refBy:1,updated:"5мин"},
  {id:"products",title:"Продукты ДЗЗ",desc:"Сцены и тайлы",scope:"INTERNAL",bundle:"cuod",records:1024,fk:3,refBy:0,updated:"12мин"},
  {id:"users_roles",title:"Роли пользователей",desc:"RBAC внутри платформы",scope:"INTERNAL",bundle:"core",records:11,fk:0,refBy:0,updated:"2д"},
  {id:"webhooks_topics",title:"Webhook topics",desc:"Темы исходящих событий",scope:"INTERNAL",bundle:"core",records:18,fk:0,refBy:0,updated:"3д"},
  {id:"tenant_quotas",title:"Квоты тенантов",desc:"Лимиты по тенантам",scope:"TENANT",bundle:"core",records:6,fk:0,refBy:0,updated:"1д"},
];

const SC = { PUBLIC:["var(--navy)","var(--accent-bg)"], INTERNAL:["var(--warn)","var(--warn-bg)"], TENANT:["var(--pink)","var(--pink-bg)"] };

/* ────────────────── SHELL ────────────────── */

function Shell({ view, setView, children }){
  const [collapsed, setCollapsed] = useState(false);
  const w = collapsed ? 56 : 220;
  return (
    <div style={{display:"grid",gridTemplateColumns:`${w}px 1fr`,minHeight:"100vh",transition:"grid-template-columns .15s"}}>
      <aside style={{background:"var(--surface)",borderRight:"1px solid var(--line)",display:"flex",flexDirection:"column"}}>
        <div style={{height:48,display:"flex",alignItems:"center",gap:10,padding:"0 14px",borderBottom:"1px solid var(--line-2)"}}>
          <svg width="22" height="22" viewBox="0 0 32 32" fill="none" style={{color:"var(--accent)",flexShrink:0}} aria-hidden="true">
            <circle cx="16" cy="16" r="13" stroke="currentColor" strokeWidth="1.6"/>
            <circle cx="16" cy="16" r="6" stroke="currentColor" strokeWidth="1" opacity=".45"/>
            <circle cx="22.5" cy="16" r="2.2" fill="currentColor"/>
          </svg>
          {!collapsed && (
            <div style={{display:"inline-flex",alignItems:"baseline",gap:5,fontFamily:"Tektur,sans-serif",fontWeight:600,fontSize:14,color:"var(--ink)",letterSpacing:".20em"}}>
              ORDINIS<span style={{fontWeight:400,fontSize:9.5,letterSpacing:".30em",color:"var(--mute)"}}>MDM</span>
            </div>
          )}
        </div>
        <nav style={{flex:1,padding:"6px 6px"}}>
          {NAV.map(n=>{
            const active = n.k==="dicts" && view==="catalog" ? true : (n.k===view);
            return (
              <button key={n.k} onClick={()=>n.k==="dicts"&&setView("catalog")} style={{
                width:"100%",display:"flex",alignItems:"center",gap:10,
                padding: collapsed ? "8px 10px" : "7px 10px",
                border:"none",background: active?"var(--accent-bg)":"transparent",
                color: active?"var(--navy)":"var(--ink-2)",
                borderRadius:6,fontSize:13,cursor:"pointer",marginBottom:1,
                fontWeight: active?600:500,
                justifyContent: collapsed?"center":"flex-start",
              }}>
                <span style={{width:18,textAlign:"center",fontSize:14,color:active?"var(--accent)":"var(--mute)"}}>{n.icon}</span>
                {!collapsed && <>
                  <span style={{flex:1,textAlign:"left"}}>{n.label}</span>
                  {n.count!=null && <span className="mono" style={{fontSize:11,padding:"1px 6px",borderRadius:4,background:active?"#fff":"#f4f5f8",color:"var(--mute)"}}>{n.count}</span>}
                </>}
              </button>
            );
          })}
        </nav>
        <div style={{padding:"8px 6px",borderTop:"1px solid var(--line-2)"}}>
          <button onClick={()=>setCollapsed(!collapsed)} style={{width:"100%",padding:"7px 10px",border:"none",background:"transparent",color:"var(--mute)",cursor:"pointer",borderRadius:6,fontSize:12,display:"flex",alignItems:"center",gap:8,justifyContent:collapsed?"center":"flex-start"}}>
            <span>{collapsed?"›":"‹"}</span>{!collapsed && <span>Свернуть</span>}
          </button>
        </div>
      </aside>

      <div style={{display:"flex",flexDirection:"column",minWidth:0}}>
        <header style={{height:48,background:"var(--surface)",borderBottom:"1px solid var(--line)",display:"flex",alignItems:"center",gap:12,padding:"0 16px"}}>
          {view==="catalog" && <>
            <div style={{fontWeight:600,fontSize:14,color:"var(--ink)"}}>Справочники</div>
            <span className="cap">40 шт.</span>
          </>}
          {view==="editor" && <>
            <button onClick={()=>setView("catalog")} style={{border:"none",background:"transparent",color:"var(--mute)",cursor:"pointer",fontSize:12,padding:0}}>← Справочники</button>
            <span style={{color:"var(--mute)"}}>/</span>
            <div style={{fontWeight:600,fontSize:14,color:"var(--ink)"}}>Космические аппараты</div>
            <span className="mono" style={{fontSize:11,color:"var(--mute)"}}>satellites · v1.0.0</span>
          </>}
          <div style={{flex:1,maxWidth:360,marginLeft:12,position:"relative"}}>
            <input placeholder="Поиск…  ⌘K" style={{width:"100%",height:30,padding:"0 12px 0 30px",border:"1px solid var(--line)",borderRadius:6,fontSize:12.5,background:"var(--surface-2)"}}/>
            <span style={{position:"absolute",left:10,top:7,color:"var(--mute)",fontSize:13}}>⌕</span>
          </div>
          <div style={{flex:1}}/>
          <button style={{height:28,padding:"0 8px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12}}>RU</button>
          <button style={{height:28,padding:"0 8px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:13}}>◔</button>
          <div style={{width:1,height:20,background:"var(--line)"}}/>
          <div style={{width:28,height:28,borderRadius:"50%",background:"var(--accent-bg)",color:"var(--accent)",display:"flex",alignItems:"center",justifyContent:"center",fontSize:11,fontWeight:600}}>OP</div>
        </header>
        <main style={{flex:1,overflow:"auto",padding:14}}>{children}</main>
      </div>
    </div>
  );
}

/* ────────────────── CATALOG (dense table) ────────────────── */

function Catalog({ onOpen, onCreate }){
  const [scopes,setScopes]=useState(new Set(["PUBLIC","INTERNAL","TENANT"]));
  const [bundle,setBundle]=useState(null);
  const tog=s=>{const n=new Set(scopes);n.has(s)?n.delete(s):n.add(s);setScopes(n);};
  const rows=DICTS.filter(d=>scopes.has(d.scope) && (!bundle||d.bundle===bundle));

  const cell={padding:"7px 12px",fontSize:12.5,color:"var(--ink-2)",borderBottom:"1px solid var(--line-2)"};
  const Pill = ({s})=>(<span className="mono" style={{padding:"1px 6px",borderRadius:4,background:SC[s][1],color:SC[s][0],fontSize:10.5,letterSpacing:".04em"}}>{s}</span>);

  return (
    <div style={{background:"var(--surface)",border:"1px solid var(--line)",borderRadius:8,overflow:"hidden"}}>
      <div style={{display:"flex",flexWrap:"wrap",alignItems:"center",gap:6,padding:"8px 12px",borderBottom:"1px solid var(--line-2)",background:"var(--surface-2)"}}>
        {["PUBLIC","INTERNAL","TENANT"].map(s=>{
          const on=scopes.has(s);
          return <button key={s} onClick={()=>tog(s)} style={{height:24,padding:"0 8px",border:`1px solid ${on?SC[s][0]:"var(--line)"}`,borderRadius:5,background:on?SC[s][1]:"var(--surface)",color:on?SC[s][0]:"var(--mute)",cursor:"pointer",fontSize:11,fontFamily:"JetBrains Mono,monospace",letterSpacing:".04em",whiteSpace:"nowrap"}}>{s}</button>;
        })}
        <div style={{width:1,height:18,background:"var(--line)",margin:"0 2px"}}/>
        {[null,"cuod","geo","i18n","core"].map(b=>(
          <button key={b||"all"} onClick={()=>setBundle(b)} style={{height:24,padding:"0 8px",border:"1px solid var(--line)",borderRadius:5,background:bundle===b?"var(--accent-bg)":"var(--surface)",color:bundle===b?"var(--accent)":"var(--ink-2)",cursor:"pointer",fontSize:11.5,fontWeight:bundle===b?600:400,whiteSpace:"nowrap"}}>{b||"все"}</button>
        ))}
        <div style={{flex:1,minWidth:8}}/>
        <span className="cap" style={{whiteSpace:"nowrap"}}>{rows.length} / {DICTS.length}</span>
        <button style={{height:26,padding:"0 10px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12,whiteSpace:"nowrap"}}>Граф ⇄</button>
        <button onClick={onCreate} style={{height:26,padding:"0 12px",border:"1px solid var(--navy)",borderRadius:6,background:"var(--navy)",color:"var(--on-accent)",cursor:"pointer",fontSize:12,fontWeight:600,whiteSpace:"nowrap"}}>+ Создать</button>
      </div>

      <div style={{overflowX:"auto"}}><table style={{width:"100%",minWidth:880,borderCollapse:"collapse"}}>
        <thead>
          <tr style={{background:"var(--surface)"}}>
            {["","Название","ID","Bundle","Scope","Записей","→","←","Изменён",""].map((h,i)=>(
              <th key={i} className="cap" style={{textAlign:"left",padding:"8px 12px",fontSize:10,color:"var(--mute)",borderBottom:"1px solid var(--line)",fontWeight:600}}>{h}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map(d=>(
            <tr key={d.id} onClick={()=>onOpen(d.id)} style={{cursor:"pointer",background:"var(--surface)"}}
                onMouseEnter={e=>e.currentTarget.style.background="var(--surface-2)"}
                onMouseLeave={e=>e.currentTarget.style.background="var(--surface)"}>
              <td style={{...cell,width:3,padding:0,borderLeft:`3px solid ${SC[d.scope][0]}`}}/>
              <td style={{...cell,fontWeight:600,color:"var(--ink)"}}>{d.title}<div style={{fontSize:11,color:"var(--mute)",fontWeight:400,marginTop:1}}>{d.desc}</div></td>
              <td style={{...cell,fontFamily:"JetBrains Mono,monospace",fontSize:11.5,color:"var(--mute)"}}>{d.id}</td>
              <td style={{...cell,fontFamily:"JetBrains Mono,monospace",fontSize:11.5}}>{d.bundle}</td>
              <td style={cell}><Pill s={d.scope}/></td>
              <td style={{...cell,fontFamily:"JetBrains Mono,monospace",fontSize:11.5,textAlign:"right",color:"var(--ink)"}}>{d.records.toLocaleString("ru")}</td>
              <td style={{...cell,fontFamily:"JetBrains Mono,monospace",fontSize:11.5,textAlign:"center",color:d.fk?"var(--ink-2)":"var(--line)"}}>{d.fk||"·"}</td>
              <td style={{...cell,fontFamily:"JetBrains Mono,monospace",fontSize:11.5,textAlign:"center",color:d.refBy?"var(--ink-2)":"var(--line)"}}>{d.refBy||"·"}</td>
              <td style={{...cell,fontFamily:"JetBrains Mono,monospace",fontSize:11.5,color:"var(--mute)"}}>{d.updated}</td>
              <td style={{...cell,textAlign:"right",color:"var(--mute)"}}>›</td>
            </tr>
          ))}
        </tbody>
      </table></div>
    </div>
  );
}

/* ────────────────── EDITOR (split + dense) ────────────────── */

const RECORDS=[
  ["RES-P-1","Ресурс-П №1","Роскосмос","operational","SSO","2013-06-25"],
  ["RES-P-2","Ресурс-П №2","Роскосмос","operational","SSO","2014-12-26"],
  ["RES-P-3","Ресурс-П №3","Роскосмос","operational","SSO","2016-03-13"],
  ["KAN-V-3","Канопус-В №3","Роскосмос","operational","SSO","2017-07-14"],
  ["KAN-V-4","Канопус-В №4","Роскосмос","operational","SSO","2017-07-14"],
  ["KAN-V-5","Канопус-В №5","Роскосмос","operational","SSO","2017-07-14"],
  ["KAN-V-6","Канопус-В №6","Роскосмос","operational","SSO","2018-02-01"],
  ["SNT-1A","Sentinel-1A","ESA","operational","SSO","2014-04-03"],
  ["SNT-1B","Sentinel-1B","ESA","decommissioned","SSO","2016-04-25"],
  ["SNT-2A","Sentinel-2A","ESA","operational","SSO","2015-06-23"],
  ["SNT-2B","Sentinel-2B","ESA","operational","SSO","2017-03-07"],
  ["LST-8","Landsat 8","NASA","operational","SSO","2013-02-11"],
  ["LST-9","Landsat 9","NASA","operational","SSO","2021-09-27"],
];

function Editor({ onEdit, onCreate, onTimeTravel, onExport }){
  const [tab,setTab]=useState("records");
  const tabs=[["records","Записи",127],["links","Связи",6],["fields","Поля",12],["json","JSON",null],["events","События",null],["history","История",null]];

  return (
    <div style={{display:"grid",gridTemplateColumns:"260px 1fr",gap:14,height:"calc(100vh - 76px)"}}>
      {/* meta sidebar */}
      <aside style={{background:"var(--surface)",border:"1px solid var(--line)",borderRadius:8,padding:14,fontSize:12.5,display:"flex",flexDirection:"column",gap:14,overflow:"auto"}}>
        <div>
          <span className="mono" style={{padding:"1px 6px",borderRadius:4,background:SC.PUBLIC[1],color:SC.PUBLIC[0],fontSize:10.5}}>PUBLIC</span>
        </div>
        <div>
          <div className="cap">описание</div>
          <div style={{marginTop:4,lineHeight:1.45,color:"var(--ink-2)"}}>Каталог космических аппаратов ДЗЗ-миссий. Включает планируемые, действующие и выведенные из эксплуатации КА.</div>
        </div>
        <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:10}}>
          {[["bundle","cuod"],["версия","1.0.0"],["записей","127"],["локали","ru, en"]].map(([k,v])=>(
            <div key={k}><div className="cap">{k}</div><div className="mono" style={{marginTop:2,fontSize:12,color:"var(--ink)"}}>{v}</div></div>
          ))}
        </div>
        <div style={{borderTop:"1px solid var(--line-2)",paddingTop:10}}>
          <div className="cap" style={{marginBottom:6}}>связи (6)</div>
          <div style={{display:"flex",flexDirection:"column",gap:4,fontSize:11.5}}>
            <div style={{color:"var(--mute)"}}>→ ссылается</div>
            {["satellite_types","satellite_statuses","missions","operators","orbit_types"].map(x=>(
              <a key={x} className="mono" style={{color:"var(--accent)",textDecoration:"none",padding:"2px 0"}}>{x}</a>
            ))}
            <div style={{color:"var(--mute)",marginTop:6}}>← используют</div>
            <a className="mono" style={{color:"var(--accent)",textDecoration:"none",padding:"2px 0"}}>acquisitions</a>
          </div>
        </div>
        <div style={{marginTop:"auto",display:"flex",flexDirection:"column",gap:6}}>
          <button onClick={onExport} style={{height:30,border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12,display:"flex",alignItems:"center",justifyContent:"center",gap:6}}><span style={{fontSize:12}}>⇣</span> Экспорт…</button>
          <button onClick={onTimeTravel} style={{height:30,border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12,display:"flex",alignItems:"center",justifyContent:"center",gap:6}}><span style={{fontSize:13}}>⟲</span> Time-travel…</button>
        </div>
      </aside>

      <section style={{background:"var(--surface)",border:"1px solid var(--line)",borderRadius:8,display:"flex",flexDirection:"column",minWidth:0,overflow:"hidden"}}>
        <div style={{display:"flex",alignItems:"stretch",borderBottom:"1px solid var(--line)"}}>
          <div style={{flex:1,minWidth:0,display:"flex",overflowX:"auto",padding:"0 4px 0 8px",scrollbarWidth:"none"}}>
            {tabs.map(([k,label,n])=>(
              <button key={k} onClick={()=>setTab(k)} style={{
                border:"none",background:"transparent",padding:"10px 10px",fontSize:12.5,cursor:"pointer",
                color: tab===k?"var(--ink)":"var(--mute)",fontWeight: tab===k?600:500,
                borderBottom: tab===k?"2px solid var(--navy)":"2px solid transparent",
                marginBottom:-1,display:"flex",alignItems:"center",gap:5,whiteSpace:"nowrap",flexShrink:0,
              }}>{label}{n!=null && <span className="mono" style={{fontSize:10.5,color:"var(--mute)",padding:"0 4px",background:"var(--bg)",borderRadius:3}}>{n}</span>}</button>
            ))}
          </div>
          <div style={{display:"flex",alignItems:"center",gap:6,padding:"0 8px",borderLeft:"1px solid var(--line-2)",flexShrink:0}}>
            <button style={{height:26,width:30,border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12}} title="Колонки">⚙</button>
            <button onClick={onCreate} style={{height:26,padding:"0 10px",border:"1px solid var(--navy)",borderRadius:6,background:"var(--navy)",color:"var(--on-accent)",cursor:"pointer",fontSize:12,fontWeight:600,whiteSpace:"nowrap"}}>+ Запись</button>
          </div>
        </div>

        {tab==="records" && (
          <>
            <div style={{display:"flex",flexWrap:"wrap",alignItems:"center",gap:6,padding:"8px 12px",borderBottom:"1px solid var(--line-2)",background:"var(--surface-2)"}}>
              <input placeholder="Поиск по записям…" style={{flex:"1 1 180px",minWidth:140,maxWidth:280,height:28,padding:"0 10px",border:"1px solid var(--line)",borderRadius:5,fontSize:12,background:"var(--surface)"}}/>
              <button style={{height:26,padding:"0 8px",border:"1px solid var(--line)",borderRadius:5,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:11.5,whiteSpace:"nowrap"}}>operational ▾</button>
              <button style={{height:26,padding:"0 8px",border:"1px solid var(--line)",borderRadius:5,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:11.5,whiteSpace:"nowrap"}}>оператор ▾</button>
              <div style={{flex:1,minWidth:8}}/>
              <span className="cap" style={{whiteSpace:"nowrap"}}>13 / 127</span>
            </div>
            <div style={{flex:1,overflow:"auto"}}>
              <table style={{width:"100%",minWidth:760,borderCollapse:"collapse",fontSize:12.5,whiteSpace:"nowrap"}}>
                <thead style={{position:"sticky",top:0,background:"var(--surface)",zIndex:1}}>
                  <tr>
                    {["","ID","Название","Оператор","Статус","Орбита","Запуск",""].map((h,i)=>(
                      <th key={i} className="cap" style={{textAlign:"left",padding:"7px 12px",fontSize:10,color:"var(--mute)",borderBottom:"1px solid var(--line)",fontWeight:600}}>{h}</th>
                    ))}
                  </tr>
                </thead>
                <tbody>
                  {RECORDS.map(r=>(
                    <tr key={r[0]} onClick={()=>onEdit(r)} style={{borderBottom:"1px solid var(--line-2)",cursor:"pointer",background:"var(--surface)"}}
                        onMouseEnter={e=>e.currentTarget.style.background="var(--surface-2)"}
                        onMouseLeave={e=>e.currentTarget.style.background="var(--surface)"}>
                      <td style={{padding:"6px 12px"}}><input type="checkbox" style={{margin:0}}/></td>
                      <td style={{padding:"6px 12px",fontFamily:"JetBrains Mono,monospace",fontSize:11.5,color:"var(--accent)"}}>{r[0]}</td>
                      <td style={{padding:"6px 12px",color:"var(--ink)"}}>{r[1]}</td>
                      <td style={{padding:"6px 12px",color:"var(--ink-2)"}}>{r[2]}</td>
                      <td style={{padding:"6px 12px"}}><span style={{display:"inline-flex",alignItems:"center",gap:5,fontSize:11.5,color:r[3]==="operational"?"var(--green)":"var(--mute)"}}><span style={{width:6,height:6,borderRadius:"50%",background:r[3]==="operational"?"var(--green)":"var(--mute)"}}/>{r[3]}</span></td>
                      <td style={{padding:"6px 12px",fontFamily:"JetBrains Mono,monospace",fontSize:11.5,color:"var(--ink-2)"}}>{r[4]}</td>
                      <td style={{padding:"6px 12px",fontFamily:"JetBrains Mono,monospace",fontSize:11.5,color:"var(--mute)"}}>{r[5]}</td>
                      <td style={{padding:"6px 12px",textAlign:"right",color:"var(--mute)"}}>⋯</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </>
        )}
        {tab==="links" && <TabLinks/>}
        {tab==="fields" && <TabFields/>}
        {tab==="json" && <TabJson/>}
        {tab==="events" && <TabEvents/>}
        {tab==="history" && <TabHistory/>}
      </section>
    </div>
  );
}

/* ────────────────── EDITOR TABS ────────────────── */

const LINK_GRAPH = {
  out: [
    {name:"operators",     records:23, fields:["operator"],                req:true},
    {name:"satellite_statuses",records:5, fields:["status"],               req:true},
    {name:"orbit_types",   records:8,  fields:["orbit_type"],              req:true},
    {name:"missions",      records:34, fields:["mission"],                 req:false},
    {name:"launch_sites",  records:18, fields:["launch_site"],             req:false},
    {name:"launch_vehicles",records:42,fields:["launch_vehicle"],          req:false},
  ],
  in: [
    {name:"acquisitions",  records:18420,fields:["satellite_id"],          critical:true},
    {name:"products",      records:312,  fields:["satellite_id"]},
    {name:"telemetry_streams",records:127,fields:["satellite_id"]},
  ],
};
function TabLinks(){
  return (
    <div style={{flex:1,overflow:"auto",padding:"14px 18px"}}>
      <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:18}}>
        <div>
          <div className="cap" style={{marginBottom:8}}>→ ссылается на ({LINK_GRAPH.out.length})</div>
          <div style={{border:"1px solid var(--line)",borderRadius:7,overflow:"hidden"}}>
            {LINK_GRAPH.out.map(l=>(
              <div key={l.name} style={{padding:"10px 12px",borderBottom:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10}}>
                <span className="mono" style={{color:"var(--accent)",fontSize:12.5,fontWeight:600,flex:1}}>{l.name}</span>
                <span className="mono" style={{fontSize:11,color:"var(--mute)"}}>{l.records}</span>
                <span style={{fontSize:11,padding:"1px 6px",borderRadius:3,background: l.req?"var(--warn-bg)":"var(--line-2)",color: l.req?"var(--warn)":"var(--mute)"}}>{l.req?"required":"optional"}</span>
                <span className="mono" style={{fontSize:11,color:"var(--ink-2)"}}>{l.fields.join(", ")}</span>
              </div>
            ))}
          </div>
        </div>
        <div>
          <div className="cap" style={{marginBottom:8}}>← ссылаются на нас ({LINK_GRAPH.in.length})</div>
          <div style={{border:"1px solid var(--line)",borderRadius:7,overflow:"hidden"}}>
            {LINK_GRAPH.in.map(l=>(
              <div key={l.name} style={{padding:"10px 12px",borderBottom:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10}}>
                <span className="mono" style={{color:"var(--accent)",fontSize:12.5,fontWeight:600,flex:1}}>{l.name}</span>
                <span className="mono" style={{fontSize:11,color:"var(--mute)"}}>{l.records.toLocaleString()}</span>
                {l.critical && <span style={{fontSize:11,padding:"1px 6px",borderRadius:3,background:"var(--pink-bg)",color:"var(--pink)"}}>критично</span>}
                <span className="mono" style={{fontSize:11,color:"var(--ink-2)"}}>{l.fields.join(", ")}</span>
              </div>
            ))}
            <div style={{padding:"10px 12px",fontSize:11.5,color:"var(--mute)",background:"var(--surface-2)"}}>
              удаление записи в этом справочнике сломает <span className="mono" style={{color:"var(--pink)"}}>18 859</span> ссылок
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

const FIELDS = [
  {id:"id",          type:"string",   req:true, uniq:true,  i18n:false, idx:true,  desc:"Иммутабельный идентификатор"},
  {id:"name_ru",     type:"string",   req:true, uniq:false, i18n:true,  idx:false, desc:"Название на русском"},
  {id:"name_en",     type:"string",   req:false,uniq:false, i18n:true,  idx:false, desc:"Название на английском"},
  {id:"operator",    type:"fk → operators",req:true, uniq:false, i18n:false, idx:true, desc:"Оператор/владелец"},
  {id:"status",      type:"enum",     req:true, uniq:false, i18n:false, idx:true,  desc:"planned / operational / decommissioned / lost"},
  {id:"orbit_type",  type:"fk → orbit_types", req:true, uniq:false, i18n:false, idx:false, desc:"Тип орбиты"},
  {id:"launch_date", type:"date",     req:false,uniq:false, i18n:false, idx:true,  desc:"Дата выведения на орбиту"},
  {id:"mass_kg",     type:"number",   req:false,uniq:false, i18n:false, idx:false, desc:"Масса аппарата, кг"},
  {id:"instruments", type:"string[]", req:false,uniq:false, i18n:false, idx:false, desc:"Список приборов"},
  {id:"raw_json",    type:"jsonb",    req:false,uniq:false, i18n:false, idx:false, desc:"Произвольные данные"},
  {id:"active",      type:"boolean",  req:true, uniq:false, i18n:false, idx:false, desc:"Видна потребителям"},
  {id:"created_at",  type:"timestamp",req:true, uniq:false, i18n:false, idx:true,  desc:"system"},
];
function TabFields(){
  return (
    <div style={{flex:1,overflow:"auto"}}>
      <table style={{width:"100%",minWidth:760,borderCollapse:"collapse",fontSize:12.5}}>
        <thead style={{position:"sticky",top:0,background:"var(--surface)",zIndex:1}}>
          <tr>{["","Поле","Тип","Required","Unique","i18n","Index","Описание",""].map((h,i)=>(
            <th key={i} className="cap" style={{textAlign:"left",padding:"7px 12px",fontSize:10,color:"var(--mute)",borderBottom:"1px solid var(--line)",fontWeight:600}}>{h}</th>
          ))}</tr>
        </thead>
        <tbody>
          {FIELDS.map((f,i)=>(
            <tr key={f.id} style={{borderBottom:"1px solid var(--line-2)",background:"var(--surface)"}} onMouseEnter={e=>e.currentTarget.style.background="var(--surface-2)"} onMouseLeave={e=>e.currentTarget.style.background="var(--surface)"}>
              <td style={{padding:"6px 12px",color:"var(--mute)",fontSize:11}} className="mono">{String(i+1).padStart(2,"0")}</td>
              <td className="mono" style={{padding:"6px 12px",color:"var(--accent)",fontWeight:600}}>{f.id}</td>
              <td className="mono" style={{padding:"6px 12px",color:"var(--ink-2)",fontSize:11.5}}>{f.type}</td>
              <td style={{padding:"6px 12px",fontSize:11.5,color: f.req?"var(--ink)":"var(--line)"}}>{f.req?"✓":"·"}</td>
              <td style={{padding:"6px 12px",fontSize:11.5,color: f.uniq?"var(--ink)":"var(--line)"}}>{f.uniq?"✓":"·"}</td>
              <td style={{padding:"6px 12px",fontSize:11.5,color: f.i18n?"var(--accent)":"var(--line)"}}>{f.i18n?"✓":"·"}</td>
              <td style={{padding:"6px 12px",fontSize:11.5,color: f.idx?"var(--ink)":"var(--line)"}}>{f.idx?"✓":"·"}</td>
              <td style={{padding:"6px 12px",color:"var(--mute)",fontSize:11.5}}>{f.desc}</td>
              <td style={{padding:"6px 12px",textAlign:"right",color:"var(--mute)"}}>⋯</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function TabJson(){
  const code = `{
  "id": "satellites",
  "version": "1.8.0",
  "bundle": "cuod",
  "scope": "PUBLIC",
  "locales": ["ru","en"],
  "records_count": 127,
  "fields": [
    {"id":"id",          "type":"string",   "required":true, "unique":true,  "indexed":true},
    {"id":"name_ru",     "type":"string",   "required":true,  "i18n":true},
    {"id":"name_en",     "type":"string",   "required":false, "i18n":true},
    {"id":"operator",    "type":"fk",       "ref":"operators",      "required":true},
    {"id":"status",      "type":"enum",     "values":["planned","operational","decommissioned","lost"]},
    {"id":"orbit_type",  "type":"fk",       "ref":"orbit_types",    "required":true},
    {"id":"launch_date", "type":"date"},
    {"id":"instruments", "type":"string[]"},
    {"id":"raw_json",    "type":"jsonb"},
    {"id":"active",      "type":"boolean",  "default":true}
  ],
  "sample": {
    "id": "RES-P-1",
    "name_ru": "Ресурс-П №1",
    "name_en": "Resurs-P 1",
    "operator": "roscosmos",
    "status": "operational",
    "orbit_type": "SSO",
    "launch_date": "2013-06-25",
    "instruments": ["MSU-GS","Geoton-L1","KShMSA-VR"]
  }
}`;
  const highlight = (line)=>{
    return line
      .replace(/(&|<|>)/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;'})[m])
      .replace(/("[a-z_]+")(\s*:)/gi, '<span style="color:#7c5cff">$1</span>$2')
      .replace(/: ("[^"]*")/g, ': <span style="color:#2a6f3a">$1</span>')
      .replace(/: (true|false|null)/g, ': <span style="color:#b45309">$1</span>')
      .replace(/: (\d+)/g, ': <span style="color:#b45309">$1</span>');
  };
  return (
    <div style={{flex:1,overflow:"auto",padding:"14px 18px"}}>
      <div style={{display:"flex",alignItems:"center",gap:10,marginBottom:10}}>
        <div className="cap">satellites.schema.json</div>
        <div style={{flex:1}}/>
        <button style={{height:26,padding:"0 10px",border:"1px solid var(--line)",borderRadius:5,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:11.5}}>Скопировать</button>
        <button style={{height:26,padding:"0 10px",border:"1px solid var(--line)",borderRadius:5,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:11.5}}>Скачать .json</button>
      </div>
      <pre style={{margin:0,padding:"14px 16px",background:"#0f1115",color:"#d9dde5",borderRadius:7,fontFamily:"JetBrains Mono,monospace",fontSize:12,lineHeight:1.6,overflow:"auto",border:"1px solid var(--line)"}}>
        {code.split("\n").map((line,i)=>(
          <div key={i} style={{display:"flex",gap:14}}>
            <span style={{color:"#3a4250",userSelect:"none",width:24,textAlign:"right",flexShrink:0}}>{i+1}</span>
            <span dangerouslySetInnerHTML={{__html:highlight(line)||"&nbsp;"}}/>
          </div>
        ))}
      </pre>
    </div>
  );
}

const EVENTS=[
  {ts:"09:14:02", date:"2026-04-30", kind:"webhook",  status:"ok",   to:"data-platform", note:"satellites.updated · 127 rec · 412ms"},
  {ts:"09:11:53", date:"2026-04-30", kind:"publish",  status:"ok",   to:"production",    note:"v1.8.0 опубликована, 3 потребителя уведомлены"},
  {ts:"09:11:21", date:"2026-04-30", kind:"review",   status:"ok",   by:"a.korovin",     note:"approve · «выглядит хорошо»"},
  {ts:"08:42:08", date:"2026-04-30", kind:"draft",    status:"ok",   by:"e.morozov",     note:"submitted for review (3 changes)"},
  {ts:"19:01:46", date:"2026-04-29", kind:"webhook",  status:"fail", to:"legacy-erp",    note:"504 timeout · retry x3 · queued"},
  {ts:"14:22:12", date:"2026-04-29", kind:"edit",     status:"ok",   by:"e.morozov",     note:"+ RES-P-1.raw_json"},
  {ts:"11:08:30", date:"2026-04-29", kind:"export",   status:"ok",   by:"v.petrov",      note:"CSV · all · 127 rows"},
  {ts:"10:55:09", date:"2026-04-29", kind:"webhook",  status:"ok",   to:"analytics",     note:"satellites.snapshot · 312ms"},
];
const EV_META={
  webhook:["#7c5cff","webhook"],publish:["var(--green)","publish"],review:["var(--accent)","review"],
  draft:["var(--amber, #b45309)","draft"],edit:["var(--ink-2)","edit"],export:["var(--ink-2)","export"]
};
function TabEvents(){
  const [filter,setFilter]=useState("all");
  const filtered = EVENTS.filter(e=>filter==="all"||e.kind===filter);
  return (
    <div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden"}}>
      <div style={{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",borderBottom:"1px solid var(--line-2)",background:"var(--surface-2)",flexWrap:"wrap"}}>
        {[["all","все",EVENTS.length],["webhook","webhooks",2],["publish","publish",1],["review","review",1],["edit","edit",1],["export","export",1]].map(([k,l,n])=>(
          <button key={k} onClick={()=>setFilter(k)} style={{height:24,padding:"0 9px",borderRadius:12,border:"1px solid "+(filter===k?"var(--navy)":"var(--line)"),background:filter===k?"var(--navy)":"var(--surface)",color:filter===k?"#fff":"var(--ink-2)",fontSize:11.5,cursor:"pointer",display:"inline-flex",alignItems:"center",gap:6}}>{l}<span className="mono" style={{fontSize:10,opacity:.7}}>{n}</span></button>
        ))}
        <div style={{flex:1}}/>
        <button style={{height:24,padding:"0 9px",border:"1px solid var(--line)",borderRadius:5,background:"var(--surface)",color:"var(--ink-2)",fontSize:11.5,cursor:"pointer"}}>retry failed</button>
      </div>
      <div style={{flex:1,overflow:"auto"}}>
        <table style={{width:"100%",borderCollapse:"collapse",fontSize:12.5}}>
          <thead><tr>{["Время","Тип","Статус","Куда / Кто","Сообщение"].map(h=>(
            <th key={h} className="cap" style={{textAlign:"left",padding:"7px 12px",fontSize:10,color:"var(--mute)",borderBottom:"1px solid var(--line)",fontWeight:600,position:"sticky",top:0,background:"var(--surface)"}}>{h}</th>
          ))}</tr></thead>
          <tbody>
            {filtered.map((e,i)=>{
              const [color,label]=EV_META[e.kind]||["var(--ink-2)",e.kind];
              return (
                <tr key={i} style={{borderBottom:"1px solid var(--line-2)",background:"var(--surface)"}}
                    onMouseEnter={e=>e.currentTarget.style.background="var(--surface-2)"}
                    onMouseLeave={e=>e.currentTarget.style.background="var(--surface)"}>
                  <td className="mono" style={{padding:"6px 12px",color:"var(--mute)",whiteSpace:"nowrap"}}>{e.date.slice(5)} {e.ts}</td>
                  <td style={{padding:"6px 12px"}}><span className="cap" style={{fontSize:9.5,color,letterSpacing:".12em"}}>{label}</span></td>
                  <td style={{padding:"6px 12px"}}><span style={{display:"inline-flex",alignItems:"center",gap:5,fontSize:11.5,color:e.status==="ok"?"var(--green)":"var(--pink)"}}><span style={{width:6,height:6,borderRadius:"50%",background:e.status==="ok"?"var(--green)":"var(--pink)"}}/>{e.status}</span></td>
                  <td className="mono" style={{padding:"6px 12px",color:"var(--accent)",fontSize:11.5}}>{e.to||e.by||"·"}</td>
                  <td style={{padding:"6px 12px",color:"var(--ink-2)"}}>{e.note}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

const COMMITS=[
  {v:"1.8.0", date:"2026-04-30 09:11", by:"e.morozov", msg:"+ raw_json column",          delta:"+3 ~11 −0", rev:"r508", current:true},
  {v:"1.7.0", date:"2026-04-02 10:15", by:"e.morozov", msg:"design_life: дефолт 5→7",    delta:"+0 ~2 −0",  rev:"r492"},
  {v:"1.6.0", date:"2026-02-14 13:30", by:"v.petrov",  msg:"merge feature/orbits",       delta:"+7 ~4 −0",  rev:"r478"},
  {v:"1.5.0", date:"2025-12-05 08:54", by:"e.morozov", msg:"+ ground_stations",          delta:"+11 ~3 −0", rev:"r459"},
  {v:"1.4.0", date:"2025-09-30 19:27", by:"a.korovin", msg:"deprecation Sentinel-1B",    delta:"+0 ~1 −0",  rev:"r445"},
  {v:"1.3.0", date:"2025-07-18 11:03", by:"v.petrov",  msg:"+ field design_life",        delta:"+2 ~96 −0", rev:"r431"},
  {v:"1.2.0", date:"2025-05-22 16:48", by:"e.morozov", msg:"переименование операторов",  delta:"+0 ~12 −0", rev:"r423"},
  {v:"1.1.0", date:"2025-03-04 09:11", by:"a.korovin", msg:"+ Sentinel-2 series",        delta:"+16 ~0 −0", rev:"r418"},
  {v:"1.0.0", date:"2025-01-12 14:22", by:"e.morozov", msg:"initial seed",               delta:"+78 ~0 −0", rev:"r412"},
];
function TabHistory(){
  return (
    <div style={{flex:1,overflow:"auto",padding:"14px 18px"}}>
      <div style={{position:"relative",paddingLeft:22}}>
        <div style={{position:"absolute",left:7,top:6,bottom:6,width:1,background:"var(--line)"}}/>
        {COMMITS.map((c,i)=>(
          <div key={c.v} style={{position:"relative",paddingBottom:16,display:"grid",gridTemplateColumns:"1fr 220px 80px",gap:12,alignItems:"baseline"}}>
            <span style={{position:"absolute",left:-19,top:6,width:13,height:13,borderRadius:"50%",background: c.current?"var(--navy)":"var(--surface)",border:`2px solid ${c.current?"var(--navy)":"var(--accent)"}`}}/>
            <div>
              <div style={{display:"flex",alignItems:"baseline",gap:10,flexWrap:"wrap"}}>
                <span className="mono" style={{fontSize:14,fontWeight:600,color: c.current?"var(--navy)":"var(--ink)"}}>v{c.v}</span>
                {c.current && <span style={{fontSize:10,padding:"1px 6px",borderRadius:3,background:"var(--navy)",color:"var(--surface)"}}>HEAD</span>}
                <span className="mono" style={{fontSize:11,color:"var(--mute)"}}>{c.rev}</span>
              </div>
              <div style={{fontSize:13,color:"var(--ink)",marginTop:3}}>{c.msg}</div>
              <div style={{fontSize:11.5,color:"var(--mute)",marginTop:2}}>{c.date} · автор <span className="mono">{c.by}</span></div>
            </div>
            <div className="mono" style={{fontSize:11,color:"var(--ink-2)"}}>{c.delta}</div>
            <div style={{textAlign:"right"}}>
              <button style={{height:24,padding:"0 9px",border:"1px solid var(--line)",borderRadius:5,background:"var(--surface)",color:"var(--ink-2)",fontSize:11,cursor:"pointer"}}>diff</button>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ────────────────── OVERLAYS ────────────────── */

function Backdrop({onClose,children,align="center"}){
  return (
    <div onClick={onClose} style={{position:"fixed",inset:0,background:"rgba(15,17,21,.42)",backdropFilter:"blur(2px)",zIndex:50,display:"flex",alignItems:align==="right"?"stretch":"center",justifyContent:align==="right"?"flex-end":"center",animation:"fade .12s ease-out"}}>
      <div onClick={e=>e.stopPropagation()}>{children}</div>
      <style>{`@keyframes fade{from{opacity:0}to{opacity:1}}@keyframes slide{from{transform:translateX(20px);opacity:0}to{transform:none;opacity:1}}`}</style>
    </div>
  );
}

const Field = ({label,hint,children,required,span=1})=>(
  <label style={{display:"flex",flexDirection:"column",gap:5,gridColumn:`span ${span}`}}>
    <span className="cap" style={{fontSize:10}}>{label}{required && <span style={{color:"var(--warn)",marginLeft:4}}>*</span>}</span>
    {children}
    {hint && <span style={{fontSize:11,color:"var(--mute)"}}>{hint}</span>}
  </label>
);
const inp = {height:32,padding:"0 10px",border:"1px solid var(--line)",borderRadius:6,fontSize:12.5,background:"var(--surface)",width:"100%"};
const inpMono = {...inp,fontFamily:"JetBrains Mono,monospace",fontSize:12};

function CreateDictModal({onClose}){
  const [scope,setScope]=useState("PUBLIC");
  const [locales,setLocales]=useState(["ru","en"]);
  const tog=l=>setLocales(locales.includes(l)?locales.filter(x=>x!==l):[...locales,l]);
  return (
    <Backdrop onClose={onClose}>
      <div style={{width:560,maxWidth:"calc(100vw - 32px)",background:"var(--surface)",borderRadius:10,boxShadow:"0 24px 60px -20px rgba(15,17,21,.35),0 0 0 1px var(--line)",overflow:"hidden",animation:"fade .14s ease-out"}}>
        <header style={{padding:"14px 18px",borderBottom:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10}}>
          <div className="cap" style={{color:"var(--accent)"}}>новый справочник</div>
          <div style={{flex:1}}/>
          <button onClick={onClose} style={{border:"none",background:"transparent",color:"var(--mute)",cursor:"pointer",fontSize:18,padding:0,width:24,height:24}}>×</button>
        </header>
        <div style={{padding:18,display:"grid",gridTemplateColumns:"1fr 1fr",gap:14}}>
          <Field label="Название" required span={2}><input style={inp} placeholder="Космические аппараты" autoFocus/></Field>
          <Field label="ID" required hint="snake_case, иммутабельный"><input style={inpMono} placeholder="satellites"/></Field>
          <Field label="Bundle" required>
            <select style={inp}><option>cuod</option><option>geo</option><option>i18n</option><option>core</option></select>
          </Field>
          <Field label="Описание" span={2}><textarea style={{...inp,height:60,padding:"8px 10px",resize:"vertical",fontFamily:"inherit"}} placeholder="Каталог КА ДЗЗ-миссий"/></Field>
          <Field label="Scope" required span={2}>
            <div style={{display:"flex",gap:6}}>
              {["PUBLIC","INTERNAL","TENANT"].map(s=>{
                const on=scope===s;
                return <button key={s} onClick={()=>setScope(s)} style={{flex:1,height:36,padding:"0 12px",border:`1px solid ${on?SC[s][0]:"var(--line)"}`,borderRadius:6,background:on?SC[s][1]:"var(--surface)",color:on?SC[s][0]:"var(--ink-2)",cursor:"pointer",display:"flex",alignItems:"center",gap:8,fontSize:12.5,fontWeight:on?600:500}}>
                  <span style={{width:8,height:8,borderRadius:"50%",background:SC[s][0]}}/>
                  <span className="cap" style={{color:on?SC[s][0]:"var(--mute)",fontSize:10.5}}>{s}</span>
                </button>;
              })}
            </div>
          </Field>
          <Field label="Поддерживаемые локали" span={2}>
            <div style={{display:"flex",flexWrap:"wrap",gap:6}}>
              {["ru","en","de","fr","es","zh","ja","ar"].map(l=>{
                const on=locales.includes(l);
                return <button key={l} onClick={()=>tog(l)} style={{height:26,padding:"0 10px",border:`1px solid ${on?"var(--accent)":"var(--line)"}`,borderRadius:5,background:on?"var(--accent-bg)":"var(--surface)",color:on?"var(--accent)":"var(--ink-2)",cursor:"pointer",fontSize:12,fontFamily:"JetBrains Mono,monospace",fontWeight:on?600:400}}>{on?"✓ ":""}{l}</button>;
              })}
            </div>
          </Field>
        </div>
        <footer style={{padding:"12px 18px",borderTop:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10,background:"var(--surface-2)"}}>
          <span className="cap" style={{fontSize:10}}>создаётся как draft v0.1.0</span>
          <div style={{flex:1}}/>
          <button onClick={onClose} style={{height:32,padding:"0 14px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12.5}}>Отмена</button>
          <button style={{height:32,padding:"0 16px",border:"1px solid var(--navy)",borderRadius:6,background:"var(--navy)",color:"var(--on-accent)",cursor:"pointer",fontSize:12.5,fontWeight:600}}>Создать</button>
        </footer>
      </div>
    </Backdrop>
  );
}

/* Schema with many fields, grouped into sections.
   Each field: {id, label, kind, fk?, ph?, unit?, ro?, span?, val?}
   Kinds: text|mono|num|date|select|fk|tags|toggle|textarea|json|enum
*/
const RECORD_SCHEMA = [
  {id:"core", title:"Основное", fields:[
    {id:"id",          label:"ID",                       kind:"mono",   ro:true, span:1, valKey:0},
    {id:"active",      label:"Активен",                  kind:"toggle", span:1, val:true},
    {id:"name_ru",     label:"Название (ru)",            kind:"text",   span:2, valKey:1},
    {id:"name_en",     label:"Название (en)",            kind:"text",   span:2},
    {id:"name_zh",     label:"Название (zh)",            kind:"text",   span:1},
    {id:"name_es",     label:"Название (es)",            kind:"text",   span:1},
    {id:"description", label:"Описание",                 kind:"textarea", span:2,
       val:"Спутник серии Ресурс-П предназначен для оперативной съёмки земной поверхности в видимом и ближнем ИК-диапазонах."},
  ]},
  {id:"fk", title:"Связи (FK)", fields:[
    {id:"operator",    label:"operator →",     kind:"fk", fk:"operators",      span:1, valKey:2},
    {id:"status",      label:"status →",       kind:"enum",  span:1, valKey:3, opts:["planned","operational","decommissioned","lost"]},
    {id:"orbit_type",  label:"orbit_type →",   kind:"fk", fk:"orbit_types",    span:1, valKey:4},
    {id:"mission",     label:"mission →",      kind:"fk", fk:"missions",       span:1, val:"resurs-p"},
    {id:"launch_site", label:"launch_site →",  kind:"fk", fk:"launch_sites",   span:1, val:"baikonur"},
    {id:"launch_vehicle",label:"launch_vehicle →",kind:"fk",fk:"launch_vehicles",span:1, val:"soyuz-2.1b"},
    {id:"manufacturer",label:"manufacturer →", kind:"fk", fk:"manufacturers",  span:1, val:"tsskb-progress"},
    {id:"data_center", label:"data_center →",  kind:"fk", fk:"data_centers",   span:1, val:"ntsomz"},
  ]},
  {id:"geom", title:"Геометрия орбиты", fields:[
    {id:"altitude_km",  label:"Высота",        kind:"num", unit:"км",   span:1, val:475},
    {id:"inclination",  label:"Наклонение",    kind:"num", unit:"°",    span:1, val:97.27},
    {id:"period_min",   label:"Период",        kind:"num", unit:"мин",  span:1, val:93.8},
    {id:"eccentricity", label:"Эксцентриситет",kind:"num", unit:"",     span:1, val:0.0001},
    {id:"raan",         label:"RAAN",          kind:"num", unit:"°",    span:1, val:182.4},
    {id:"arg_perigee",  label:"Аргумент перигея",kind:"num", unit:"°",  span:1, val:124.7},
  ]},
  {id:"phys", title:"Физические параметры", fields:[
    {id:"mass_kg",      label:"Масса",            kind:"num", unit:"кг",  span:1, val:5920},
    {id:"power_w",      label:"Мощность",         kind:"num", unit:"Вт",  span:1, val:2800},
    {id:"design_life",  label:"Расчётный срок",   kind:"num", unit:"лет", span:1, val:5},
    {id:"dimensions",   label:"Габариты",         kind:"text",            span:1, val:"5.7 × 2.7 м"},
  ]},
  {id:"inst", title:"Инструменты и продукты", fields:[
    {id:"instruments",  label:"Инструменты",   kind:"tags", span:2, val:["MSU-GS","Geoton-L1","KShMSA-VR"]},
    {id:"products",     label:"Data products", kind:"tags", span:2, val:["L1A","L1B","L2","L3"]},
    {id:"ground_stations",label:"Наземные станции",kind:"tags", span:2, val:["Москва","Железногорск","Хабаровск"]},
  ]},
  {id:"ids", title:"Внешние идентификаторы", fields:[
    {id:"cospar",       label:"COSPAR ID",     kind:"mono", span:1, val:"2013-066A"},
    {id:"norad",        label:"NORAD ID",      kind:"mono", span:1, val:"39186"},
    {id:"sat_cat",      label:"SatCat #",      kind:"mono", span:1, val:""},
    {id:"itu",          label:"ITU code",      kind:"mono", span:1, val:""},
  ]},
  {id:"dates", title:"Даты и события", fields:[
    {id:"launch_date",     label:"Дата запуска",    kind:"date", span:1, valKey:5},
    {id:"decom_date",      label:"Дата вывода",     kind:"date", span:1, val:""},
    {id:"first_light",     label:"First light",     kind:"date", span:1, val:"2013-07-02"},
    {id:"last_contact",    label:"Последний контакт",kind:"date",span:1, val:""},
  ]},
  {id:"flags", title:"Флаги", fields:[
    {id:"public",       label:"Виден потребителям", kind:"toggle", span:1, val:true},
    {id:"deprecated",   label:"Устаревший",         kind:"toggle", span:1, val:false},
    {id:"sensitive",    label:"Чувствительный",     kind:"toggle", span:1, val:false},
    {id:"draft_only",   label:"Только в draft",     kind:"toggle", span:1, val:false},
  ]},
  {id:"json", title:"Произвольные данные", fields:[
    {id:"raw",label:"raw_json",kind:"json",span:2,
      val:'{\n  "tle": {\n    "line1": "1 39186U 13066A   ...",\n    "line2": "2 39186  97.2762 ..."\n  },\n  "tags": ["eo","optical","high-res"]\n}'},
  ]},
  {id:"sys", title:"Системные", fields:[
    {id:"created_at",label:"created_at",kind:"mono",ro:true,span:1,val:"2025-11-12 14:22:08Z"},
    {id:"updated_at",label:"updated_at",kind:"mono",ro:true,span:1,val:"2026-04-30 09:11:53Z"},
    {id:"created_by",label:"created_by",kind:"mono",ro:true,span:1,val:"e.morozov"},
    {id:"version",   label:"version",   kind:"mono",ro:true,span:1,val:"v1.8.0"},
  ]},
];

function resolveVal(f,rec){
  if(f.valKey!=null) return rec[f.valKey] ?? "";
  return f.val ?? "";
}
function hasVal(v,kind){
  if(kind==="toggle") return v===true;
  if(Array.isArray(v)) return v.length>0;
  return v!=null && v!=="";
}

function RecordField({f,rec}){
  const v = resolveVal(f,rec);
  if(f.kind==="text")     return <Field label={f.label} span={f.span} hint={f.ro?"readonly":null}><input style={inp} defaultValue={v} readOnly={f.ro}/></Field>;
  if(f.kind==="mono")     return <Field label={f.label} span={f.span} hint={f.ro?"readonly":null}><input style={inpMono} defaultValue={v} readOnly={f.ro}/></Field>;
  if(f.kind==="num")      return <Field label={f.label} span={f.span} hint={f.unit||null}><input style={inpMono} defaultValue={v}/></Field>;
  if(f.kind==="date")     return <Field label={f.label} span={f.span}><input style={inpMono} type="date" defaultValue={v||""}/></Field>;
  if(f.kind==="fk")       return <Field label={f.label} span={f.span} hint={"→ "+f.fk}><input style={inpMono} defaultValue={v}/></Field>;
  if(f.kind==="enum")     return <Field label={f.label} span={f.span} hint="enum"><select style={inp} defaultValue={v}>{f.opts.map(o=><option key={o}>{o}</option>)}</select></Field>;
  if(f.kind==="toggle")   return (
    <Field label={f.label} span={f.span}>
      <div style={{display:"flex",alignItems:"center",gap:10,padding:"6px 10px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",height:32,boxSizing:"border-box"}}>
        <span style={{width:28,height:16,borderRadius:8,background:v?"var(--green)":"var(--line)",position:"relative",cursor:"pointer",transition:"background .15s"}}>
          <span style={{position:"absolute",left:v?14:2,top:2,width:12,height:12,borderRadius:"50%",background:"var(--surface)",transition:"left .15s"}}/>
        </span>
        <span style={{fontSize:12,color:"var(--ink-2)"}}>{v?"включено":"выключено"}</span>
      </div>
    </Field>);
  if(f.kind==="tags") return (
    <Field label={f.label} span={f.span}>
      <div style={{display:"flex",flexWrap:"wrap",gap:6,padding:6,border:"1px solid var(--line)",borderRadius:6,minHeight:36,background:"var(--surface)"}}>
        {(v||[]).map(x=>(
          <span key={x} className="mono" style={{padding:"3px 8px",borderRadius:4,background:"var(--accent-bg)",color:"var(--accent)",fontSize:11,display:"inline-flex",alignItems:"center",gap:6}}>{x}<button style={{border:"none",background:"transparent",color:"var(--accent)",cursor:"pointer",padding:0,fontSize:11,opacity:.6}}>×</button></span>
        ))}
        <input style={{border:"none",outline:"none",fontSize:12,minWidth:80,flex:1,background:"transparent"}} placeholder="добавить…"/>
      </div>
    </Field>);
  if(f.kind==="textarea") return <Field label={f.label} span={f.span}><textarea style={{...inp,minHeight:64,resize:"vertical",lineHeight:1.5,padding:"8px 10px"}} defaultValue={v}/></Field>;
  if(f.kind==="json")     return <Field label={f.label} span={f.span} hint="json"><textarea style={{...inpMono,minHeight:120,resize:"vertical",lineHeight:1.55,padding:"8px 10px",background:"var(--surface-2)"}} defaultValue={v}/></Field>;
  return null;
}

function RecordDrawer({rec,onClose,onDelete}){
  const [q,setQ]=useState("");
  const [showHist,setShowHist]=useState(false);
  const [onlyFilled,setOnlyFilled]=useState(false);
  const [wide,setWide]=useState(false);
  const [active,setActive]=useState("core");
  const bodyRef = React.useRef(null);
  if(!rec) return null;

  // filter sections/fields by query and "only filled"
  const ql = q.trim().toLowerCase();
  const sections = RECORD_SCHEMA.map(s=>{
    const fields = s.fields.filter(f=>{
      const v = resolveVal(f,rec);
      if(onlyFilled && !hasVal(v,f.kind)) return false;
      if(!ql) return true;
      return (f.label+" "+f.id+" "+(f.fk||"")).toLowerCase().includes(ql);
    });
    return {...s, fields};
  }).filter(s=>s.fields.length>0);

  const filledCount = RECORD_SCHEMA.flatMap(s=>s.fields).filter(f=>hasVal(resolveVal(f,rec),f.kind)).length;
  const totalCount  = RECORD_SCHEMA.flatMap(s=>s.fields).length;

  const scrollTo = (id) => {
    setActive(id);
    const el = bodyRef.current?.querySelector(`[data-sect="${id}"]`);
    if(el && bodyRef.current){ bodyRef.current.scrollTo({top: el.offsetTop - 8, behavior:"smooth"}); }
  };

  const drawerW = wide ? 880 : 520;

  return (
    <Backdrop onClose={onClose} align="right">
      <div style={{width:drawerW,maxWidth:"calc(100vw - 32px)",height:"100vh",background:"var(--surface)",boxShadow:"-20px 0 60px -20px rgba(15,17,21,.35)",display:"flex",flexDirection:"column",animation:"slide .16s ease-out",transition:"width .18s ease",position:"relative"}}>

        {/* Header */}
        <header style={{padding:"12px 18px",borderBottom:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10,flexShrink:0}}>
          <div style={{minWidth:0,flex:1}}>
            <div className="cap" style={{fontSize:10,color:"var(--mute)"}}>редактирование записи · satellites</div>
            <div style={{display:"flex",alignItems:"center",gap:8,marginTop:2}}>
              <span className="mono" style={{fontSize:13,color:"var(--accent)",fontWeight:600}}>{rec[0]}</span>
              <span style={{fontSize:13,color:"var(--ink)",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"}}>· {rec[1]}</span>
            </div>
          </div>
          <button title={wide?"уже":"шире"} onClick={()=>setWide(!wide)} style={{height:28,width:28,border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:13,display:"inline-flex",alignItems:"center",justifyContent:"center"}}>{wide?"›":"‹"}</button>
          <button onClick={()=>setShowHist(true)} style={{height:28,padding:"0 10px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12}}>История</button>
          <button onClick={onClose} style={{border:"none",background:"transparent",color:"var(--mute)",cursor:"pointer",fontSize:18,padding:0,width:24,height:24}}>×</button>
        </header>

        {/* Toolbar: search + filter + counter */}
        <div style={{padding:"10px 18px",borderBottom:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10,flexShrink:0,background:"var(--surface-2)"}}>
          <div style={{position:"relative",flex:1}}>
            <input value={q} onChange={e=>setQ(e.target.value)} placeholder="Поиск по полям…" style={{...inp,paddingLeft:28,height:30}}/>
            <span style={{position:"absolute",left:9,top:7,color:"var(--mute)",fontSize:13}}>⌕</span>
          </div>
          <label style={{display:"inline-flex",alignItems:"center",gap:6,fontSize:12,color:"var(--ink-2)",cursor:"pointer",whiteSpace:"nowrap"}}>
            <input type="checkbox" checked={onlyFilled} onChange={e=>setOnlyFilled(e.target.checked)} style={{accentColor:"var(--navy)"}}/>
            только заполненные
          </label>
          <span className="cap" style={{fontSize:10,color:"var(--mute)",whiteSpace:"nowrap"}}>{filledCount} / {totalCount}</span>
        </div>

        {/* Section chips (anchors) */}
        <div style={{padding:"8px 18px",borderBottom:"1px solid var(--line-2)",display:"flex",gap:6,flexShrink:0,overflowX:"auto",scrollbarWidth:"none"}}>
          <style>{`.sec-chips::-webkit-scrollbar{display:none}`}</style>
          <div className="sec-chips" style={{display:"flex",gap:6}}>
            {sections.map(s=>{
              const isOn = active===s.id;
              return (
                <button key={s.id} onClick={()=>scrollTo(s.id)} style={{
                  height:24,padding:"0 9px",borderRadius:12,
                  border:"1px solid "+(isOn?"var(--navy)":"var(--line)"),
                  background:isOn?"var(--navy)":"var(--surface)",
                  color:isOn?"#fff":"var(--ink-2)",
                  fontSize:11.5,cursor:"pointer",whiteSpace:"nowrap",
                  display:"inline-flex",alignItems:"center",gap:6
                }}>
                  {s.title}
                  <span className="mono" style={{fontSize:10,opacity:.7}}>{s.fields.length}</span>
                </button>
              );
            })}
          </div>
        </div>

        {/* Body: optional left ToC in wide mode + form */}
        <div style={{flex:1,display:"flex",minHeight:0}}>
          {wide && (
            <nav style={{width:180,borderRight:"1px solid var(--line-2)",overflow:"auto",padding:"12px 0",flexShrink:0,background:"var(--surface-2)"}}>
              {sections.map(s=>{
                const isOn = active===s.id;
                return (
                  <button key={s.id} onClick={()=>scrollTo(s.id)} style={{
                    display:"flex",width:"100%",alignItems:"center",gap:8,
                    padding:"7px 14px",border:"none",background:"transparent",
                    cursor:"pointer",textAlign:"left",
                    color:isOn?"var(--ink)":"var(--ink-2)",
                    fontSize:12.5,fontWeight:isOn?600:400,
                    borderLeft:"2px solid "+(isOn?"var(--accent)":"transparent")
                  }}>
                    <span style={{flex:1}}>{s.title}</span>
                    <span className="mono" style={{fontSize:10,color:"var(--mute)"}}>{s.fields.length}</span>
                  </button>
                );
              })}
            </nav>
          )}

          <div ref={bodyRef} style={{flex:1,overflow:"auto",padding:"4px 18px 24px"}}>
            {sections.length===0 && (
              <div style={{padding:"40px 0",textAlign:"center",color:"var(--mute)",fontSize:13}}>
                Ничего не найдено по запросу «<span className="mono">{q}</span>»
              </div>
            )}
            {sections.map(s=>(
              <section key={s.id} data-sect={s.id} style={{paddingTop:18}}>
                <div style={{display:"flex",alignItems:"center",gap:8,marginBottom:10,position:"sticky",top:0,background:"var(--surface)",paddingBottom:8,zIndex:1}}>
                  <div className="cap" style={{fontSize:10,color:"var(--mute)",letterSpacing:".14em"}}>{s.title}</div>
                  <div style={{flex:1,height:1,background:"var(--line-2)"}}/>
                  <span className="mono" style={{fontSize:10,color:"var(--mute)"}}>{s.fields.length}</span>
                </div>
                <div style={{display:"grid",gridTemplateColumns: wide?"1fr 1fr 1fr 1fr":"1fr 1fr",gap:14}}>
                  {s.fields.map(f=>(
                    <RecordField key={f.id} f={{...f, span: wide ? Math.min((f.span||1)*2, 4) : (f.span||1)}} rec={rec}/>
                  ))}
                </div>
              </section>
            ))}
          </div>
        </div>

        {/* Footer */}
        <footer style={{padding:"12px 18px",borderTop:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10,background:"var(--surface-2)",flexShrink:0}}>
          <button onClick={onDelete} style={{height:32,padding:"0 12px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--pink)",cursor:"pointer",fontSize:12.5}}>Удалить</button>
          <span className="cap" style={{fontSize:10,color:"var(--mute)",marginLeft:6}}>не сохранено · 3 поля</span>
          <div style={{flex:1}}/>
          <button onClick={onClose} style={{height:32,padding:"0 14px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12.5}}>Отмена</button>
          <button style={{height:32,padding:"0 16px",border:"1px solid var(--navy)",borderRadius:6,background:"var(--navy)",color:"var(--on-accent)",cursor:"pointer",fontSize:12.5,fontWeight:600}}>Сохранить</button>
        </footer>

        {showHist && <RecordHistoryPanel rec={rec} onClose={()=>setShowHist(false)}/>}
      </div>
    </Backdrop>
  );
}

function RecordHistoryPanel({rec,onClose}){
  const events=[
    {ts:"2026-04-30 09:11",by:"e.morozov",v:"1.8.0",kind:"edit",   note:"+ raw_json (TLE данные)"},
    {ts:"2026-04-02 10:15",by:"e.morozov",v:"1.7.0",kind:"edit",   note:"design_life: 5 → 7"},
    {ts:"2026-02-14 13:30",by:"v.petrov", v:"1.6.0",kind:"merge",  note:"merge feature/orbits → period_min уточнён"},
    {ts:"2025-12-05 08:54",by:"e.morozov",v:"1.5.0",kind:"edit",   note:"+ ground_stations[3]"},
    {ts:"2025-09-30 19:27",by:"a.korovin",v:"1.4.0",kind:"review", note:"approved by review-board"},
    {ts:"2025-07-18 11:03",by:"v.petrov", v:"1.3.0",kind:"edit",   note:"+ instruments[3]"},
    {ts:"2025-05-22 16:48",by:"e.morozov",v:"1.2.0",kind:"rename", note:"оператор: РКА → Роскосмос"},
    {ts:"2025-03-04 09:11",by:"a.korovin",v:"1.1.0",kind:"edit",   note:"статус: planned → operational"},
    {ts:"2025-01-12 14:22",by:"e.morozov",v:"1.0.0",kind:"create", note:"запись создана"},
  ];
  const kindMeta = {
    create: ["var(--green)","создано"],
    edit:   ["var(--accent)","изменено"],
    rename: ["var(--amber, #b45309)","переименование"],
    merge:  ["#7c5cff","merge"],
    review: ["var(--green)","review"],
  };
  return (
    <div style={{position:"absolute",inset:0,background:"var(--surface)",display:"flex",flexDirection:"column",animation:"slide .14s ease-out",zIndex:5}}>
      <header style={{padding:"12px 18px",borderBottom:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10,flexShrink:0}}>
        <button onClick={onClose} style={{height:28,width:28,border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:13}}>‹</button>
        <div style={{minWidth:0}}>
          <div className="cap" style={{fontSize:10,color:"var(--mute)"}}>история записи</div>
          <div className="mono" style={{fontSize:13,color:"var(--accent)",fontWeight:600,marginTop:2}}>{rec[0]}</div>
        </div>
        <div style={{flex:1}}/>
        <span className="cap" style={{fontSize:10,color:"var(--mute)"}}>{events.length} событий</span>
      </header>
      <div style={{flex:1,overflow:"auto",padding:"14px 18px"}}>
        <div style={{position:"relative",paddingLeft:18}}>
          <div style={{position:"absolute",left:5,top:6,bottom:6,width:1,background:"var(--line)"}}/>
          {events.map((e,idx)=>{
            const [color,label]=kindMeta[e.kind]||["var(--ink-2)",e.kind];
            return (
              <div key={idx} style={{position:"relative",paddingBottom:14}}>
                <span style={{position:"absolute",left:-17,top:5,width:11,height:11,borderRadius:"50%",background:"var(--surface)",border:`2px solid ${color}`}}/>
                <div style={{display:"flex",alignItems:"baseline",gap:8,flexWrap:"wrap"}}>
                  <span className="cap" style={{fontSize:9.5,color,letterSpacing:".12em"}}>{label}</span>
                  <span className="mono" style={{fontSize:11,color:"var(--accent)"}}>v{e.v}</span>
                  <span className="mono" style={{fontSize:11,color:"var(--mute)"}}>{e.ts}</span>
                </div>
                <div style={{fontSize:12.5,color:"var(--ink)",marginTop:3}}>{e.note}</div>
                <div style={{fontSize:11.5,color:"var(--mute)",marginTop:2}}>автор · <span className="mono">{e.by}</span></div>
              </div>
            );
          })}
        </div>
      </div>
      <footer style={{padding:"10px 18px",borderTop:"1px solid var(--line-2)",background:"var(--surface-2)",flexShrink:0,display:"flex",gap:8}}>
        <button style={{height:28,padding:"0 10px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12}}>Сравнить с текущим</button>
        <button style={{height:28,padding:"0 10px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12}}>Откатить к v1.7.0</button>
      </footer>
    </div>
  );
}

function ConfirmDialog({rec,onClose,onConfirm}){
  if(!rec) return null;
  return (
    <Backdrop onClose={onClose}>
      <div style={{width:420,background:"var(--surface)",borderRadius:10,boxShadow:"0 24px 60px -20px rgba(15,17,21,.35),0 0 0 1px var(--line)",overflow:"hidden"}}>
        <div style={{padding:"16px 18px 14px",borderTop:"3px solid var(--pink)"}}>
          <div className="cap" style={{color:"var(--pink)"}}>подтверждение</div>
          <div style={{fontSize:15,fontWeight:600,marginTop:6,color:"var(--ink)"}}>Удалить запись <span className="mono" style={{color:"var(--pink)"}}>{rec[0]}</span>?</div>
          <div style={{fontSize:12.5,color:"var(--mute)",marginTop:6,lineHeight:1.5}}>Запись «{rec[1]}» будет помечена как удалённая. Версия пройдёт через ревью; ссылки на неё из <span className="mono" style={{color:"var(--ink-2)"}}>acquisitions</span> станут битыми.</div>
        </div>
        <footer style={{padding:"12px 18px",borderTop:"1px solid var(--line-2)",display:"flex",gap:10,justifyContent:"flex-end",background:"var(--surface-2)"}}>
          <button onClick={onClose} style={{height:32,padding:"0 14px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12.5}}>Отмена</button>
          <button onClick={onConfirm} style={{height:32,padding:"0 16px",border:"1px solid var(--pink)",borderRadius:6,background:"var(--pink)",color:"var(--on-accent)",cursor:"pointer",fontSize:12.5,fontWeight:600}}>Удалить</button>
        </footer>
      </div>
    </Backdrop>
  );
}

function Toast({msg,onClose}){
  React.useEffect(()=>{if(msg){const t=setTimeout(onClose,3000);return()=>clearTimeout(t);}},[msg]);
  if(!msg) return null;
  return (
    <div style={{position:"fixed",bottom:16,right:16,zIndex:60,background:"var(--navy)",color:"var(--surface)",padding:"10px 14px",borderRadius:8,fontSize:12.5,display:"flex",alignItems:"center",gap:10,boxShadow:"0 12px 30px -10px rgba(15,17,21,.4)",animation:"slide .16s ease-out"}}>
      <span className="cap" style={{color:"var(--warn-bg)",fontSize:10}}>ok</span>
      <span>{msg}</span>
    </div>
  );
}

/* ────────────────── EXPORT ────────────────── */

function ExportModal({onClose,onDone}){
  const [fmt,setFmt]=useState("csv");
  const [scope,setScope]=useState("filtered");
  const [enc,setEnc]=useState("utf-8");
  const [cols,setCols]=useState({id:true,name:true,operator:true,status:true,orbit:true,launch:true,instruments:false,raw:false});
  const toggleCol=k=>setCols({...cols,[k]:!cols[k]});
  const selN = Object.values(cols).filter(Boolean).length;
  const rows = scope==="all"?127:scope==="filtered"?13:1;
  return (
    <Backdrop onClose={onClose}>
      <div style={{width:620,maxWidth:"calc(100vw - 32px)",background:"var(--surface)",borderRadius:10,boxShadow:"0 24px 60px -20px rgba(15,17,21,.35),0 0 0 1px var(--line)",overflow:"hidden"}}>
        <header style={{padding:"14px 18px",borderBottom:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10}}>
          <div className="cap" style={{color:"var(--accent)"}}>экспорт · satellites v1.8.0</div>
          <div style={{flex:1}}/>
          <button onClick={onClose} style={{border:"none",background:"transparent",color:"var(--mute)",cursor:"pointer",fontSize:18,padding:0,width:24,height:24}}>×</button>
        </header>
        <div style={{padding:18,display:"grid",gridTemplateColumns:"1fr 1fr",gap:14}}>
          <Field label="Формат" span={2}>
            <div style={{display:"grid",gridTemplateColumns:"repeat(4,1fr)",gap:6}}>
              {[["csv","CSV",".csv"],["json","JSON",".json"],["xlsx","Excel",".xlsx"],["sql","SQL",".sql"]].map(([k,l,ext])=>{
                const on=fmt===k;
                return <button key={k} onClick={()=>setFmt(k)} style={{height:48,border:`1px solid ${on?"var(--navy)":"var(--line)"}`,borderRadius:6,background:on?"#fff":"var(--surface)",color:on?"var(--ink)":"var(--ink-2)",cursor:"pointer",fontSize:12.5,fontWeight:on?600:500,display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:2,boxShadow:on?"inset 0 0 0 1px var(--navy)":"none"}}>{l}<span className="mono" style={{fontSize:10,color:"var(--mute)"}}>{ext}</span></button>;
              })}
            </div>
          </Field>
          <Field label="Что выгружаем" span={2}>
            <div style={{display:"inline-flex",border:"1px solid var(--line)",borderRadius:6,overflow:"hidden",width:"fit-content"}}>
              {[["all","всё (127)"],["filtered","отфильтрованное (13)"],["selected","выделенное (1)"]].map(([k,l])=>(
                <button key={k} onClick={()=>setScope(k)} style={{height:30,padding:"0 12px",border:"none",background: scope===k?"var(--navy)":"var(--surface)",color: scope===k?"#fff":"var(--ink-2)",fontSize:12,cursor:"pointer",borderRight:"1px solid var(--line)"}}>{l}</button>
              ))}
            </div>
          </Field>
          <Field label={`Колонки (${selN})`} span={2}>
            <div style={{display:"grid",gridTemplateColumns:"repeat(4,1fr)",gap:6}}>
              {[["id","id"],["name","name_ru/en"],["operator","operator"],["status","status"],["orbit","orbit_type"],["launch","launch_date"],["instruments","instruments"],["raw","raw_json"]].map(([k,l])=>{
                const on=cols[k];
                return <button key={k} onClick={()=>toggleCol(k)} style={{height:28,padding:"0 8px",border:`1px solid ${on?"var(--accent)":"var(--line)"}`,borderRadius:5,background:on?"var(--accent-bg)":"var(--surface)",color:on?"var(--accent)":"var(--ink-2)",cursor:"pointer",fontSize:11.5,fontFamily:"JetBrains Mono,monospace",textAlign:"left"}}>{on?"✓ ":"  "}{l}</button>;
              })}
            </div>
          </Field>
          <Field label="Кодировка"><select style={inp} value={enc} onChange={e=>setEnc(e.target.value)}><option>utf-8</option><option>utf-8-bom</option><option>windows-1251</option></select></Field>
          <Field label="Разделитель" hint={fmt!=="csv"?"только для CSV":null}><select style={inp} disabled={fmt!=="csv"}><option>запятая ,</option><option>точка с запятой ;</option><option>табуляция \t</option></select></Field>
        </div>
        <div style={{padding:"10px 18px",borderTop:"1px solid var(--line-2)",background:"var(--surface-2)",fontSize:12,color:"var(--ink-2)",display:"flex",alignItems:"center",gap:10}}>
          <span className="cap" style={{fontSize:10}}>превью имени файла</span>
          <span className="mono" style={{color:"var(--accent)"}}>satellites_v1.8.0_{scope}.{fmt==='xlsx'?'xlsx':fmt==='sql'?'sql':fmt}</span>
          <div style={{flex:1}}/>
          <span className="cap" style={{fontSize:10}}>≈ {rows} строк · {selN} колонок</span>
        </div>
        <footer style={{padding:"12px 18px",borderTop:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10,background:"var(--surface-2)"}}>
          <span className="cap" style={{fontSize:10,color:"var(--mute)"}}>включает только публичные поля · readonly snapshot</span>
          <div style={{flex:1}}/>
          <button onClick={onClose} style={{height:32,padding:"0 14px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12.5}}>Отмена</button>
          <button onClick={()=>onDone(fmt.toUpperCase())} style={{height:32,padding:"0 16px",border:"1px solid var(--navy)",borderRadius:6,background:"var(--navy)",color:"var(--on-accent)",cursor:"pointer",fontSize:12.5,fontWeight:600}}>Скачать</button>
        </footer>
      </div>
    </Backdrop>
  );
}

/* ────────────────── TIME TRAVEL ────────────────── */

const TT_VERSIONS = [
  {v:"1.0.0", date:"2025-01-12", time:"14:22", rev:412, label:"initial seed",            by:"e.morozov", recs:78,  kind:"major"},
  {v:"1.1.0", date:"2025-03-04", time:"09:11", rev:418, label:"+ Sentinel-2 series",     by:"a.korovin", recs:94,  kind:"minor"},
  {v:"1.2.0", date:"2025-05-22", time:"16:48", rev:423, label:"переименование операторов", by:"e.morozov", recs:94,  kind:"minor"},
  {v:"1.3.0", date:"2025-07-18", time:"11:03", rev:431, label:"+ поле design_life",       by:"v.petrov",  recs:96,  kind:"minor"},
  {v:"1.4.0", date:"2025-09-30", time:"19:27", rev:445, label:"deprecation Sentinel-1B",  by:"a.korovin", recs:101, kind:"minor"},
  {v:"1.5.0", date:"2025-12-05", time:"08:54", rev:459, label:"+ ground_stations",        by:"e.morozov", recs:112, kind:"minor"},
  {v:"1.6.0", date:"2026-02-14", time:"13:30", rev:478, label:"merge feature/orbits",     by:"v.petrov",  recs:119, kind:"minor"},
  {v:"1.7.0", date:"2026-04-02", time:"10:15", rev:492, label:"+ raw_json column",        by:"e.morozov", recs:124, kind:"minor"},
  {v:"1.8.0", date:"2026-04-30", time:"09:11", rev:508, label:"current",                  by:"e.morozov", recs:127, kind:"current"},
];

const TT_DIFFS = {
  // diff from version index → current
  0: {added:[["SNT-1A","Sentinel-1A"],["SNT-1B","Sentinel-1B"],["SNT-2A","Sentinel-2A"],["SNT-2B","Sentinel-2B"],["LST-9","Landsat 9"],["KAN-V-5","Канопус-В №5"],["KAN-V-6","Канопус-В №6"]],
      changed:[["RES-P-1","статус: planned → operational"],["RES-P-2","оператор: РКА → Роскосмос"],["KAN-V-3","+ instruments[3]"],["LST-8","mass_kg: 2070 → 2071"]],
      removed:[["AQUA-1","Aqua (legacy)"],["TERRA-1","Terra (legacy)"]]},
  3: {added:[["KAN-V-6","Канопус-В №6"],["SNT-2B","Sentinel-2B"],["LST-9","Landsat 9"]],
      changed:[["SNT-1B","статус: operational → decommissioned"],["RES-P-3","+ instruments[1]"]],
      removed:[]},
  6: {added:[["LST-9","Landsat 9"]],
      changed:[["RES-P-1","описание (ru)"],["KAN-V-4","launch_date уточнение"]],
      removed:[]},
  7: {added:[],
      changed:[["RES-P-2","norad → 39634"],["SNT-2A","+ products[1]"]],
      removed:[]},
  8: {added:[],changed:[],removed:[]},
};
function ttDiff(i){
  // pick closest known diff
  if(TT_DIFFS[i]) return TT_DIFFS[i];
  const keys = Object.keys(TT_DIFFS).map(Number).sort((a,b)=>a-b);
  const near = keys.reduce((best,k)=> Math.abs(k-i)<Math.abs(best-i)?k:best, keys[0]);
  return TT_DIFFS[near];
}

function TimeTravelModal({onClose, onApply}){
  const last = TT_VERSIONS.length-1;
  const [i,setI] = useState(last);
  const [granularity,setGranularity] = useState("version"); // "version" | "day"
  const [tab,setTab] = useState("diff"); // diff | records | meta
  const cur = TT_VERSIONS[i];
  const now = TT_VERSIONS[last];
  const diff = ttDiff(i);
  const totalDelta = (diff.added.length + diff.changed.length + diff.removed.length);

  const jumpRel = (which) => {
    if(which==="seed") setI(0);
    if(which==="year") setI(Math.max(0,last-8));
    if(which==="month") setI(Math.max(0,last-1));
    if(which==="week") setI(Math.max(0,last)); // same point in this mock
    if(which==="now") setI(last);
  };

  return (
    <Backdrop onClose={onClose}>
      <div style={{width:"min(1180px, calc(100vw - 48px))",height:"min(760px, calc(100vh - 48px))",background:"var(--surface)",borderRadius:10,boxShadow:"0 24px 60px -20px rgba(15,17,21,.4),0 0 0 1px var(--line)",display:"flex",flexDirection:"column",overflow:"hidden"}}>

        {/* Header */}
        <header style={{padding:"14px 20px",borderBottom:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:12,flexShrink:0}}>
          <div style={{width:28,height:28,borderRadius:6,background:"var(--navy)",color:"var(--surface)",display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:14}}>⟲</div>
          <div style={{minWidth:0}}>
            <div className="cap" style={{fontSize:10,color:"var(--mute)"}}>time-travel · satellites</div>
            <div style={{fontSize:14,fontWeight:600,marginTop:2,color:"var(--ink)"}}>Состояние справочника на момент времени</div>
          </div>
          <div style={{flex:1}}/>
          <button onClick={onClose} style={{border:"none",background:"transparent",color:"var(--mute)",cursor:"pointer",fontSize:20,padding:0,width:24,height:24}}>×</button>
        </header>

        {/* Big readout: "сейчас" ↔ "тогда" */}
        <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",borderBottom:"1px solid var(--line-2)",flexShrink:0}}>
          <div style={{padding:"14px 20px",borderRight:"1px solid var(--line-2)"}}>
            <div className="cap" style={{fontSize:10,color:"var(--mute)"}}>сейчас</div>
            <div style={{display:"flex",alignItems:"baseline",gap:10,marginTop:4}}>
              <span className="mono" style={{fontSize:20,color:"var(--ink)",fontWeight:600}}>v{now.v}</span>
              <span className="mono" style={{fontSize:12,color:"var(--mute)"}}>{now.date} · {now.time}</span>
            </div>
            <div style={{fontSize:12,color:"var(--ink-2)",marginTop:4}}>{now.recs} записей · автор: <span className="mono">{now.by}</span></div>
          </div>
          <div style={{padding:"14px 20px",background: i===last ? "#fff" : "#fff8ec"}}>
            <div className="cap" style={{fontSize:10,color: i===last?"var(--mute)":"var(--amber)"}}>{i===last ? "точка во времени" : "точка во времени · отличается"}</div>
            <div style={{display:"flex",alignItems:"baseline",gap:10,marginTop:4}}>
              <span className="mono" style={{fontSize:20,color: i===last?"var(--ink)":"var(--amber)",fontWeight:600}}>v{cur.v}</span>
              <span className="mono" style={{fontSize:12,color:"var(--mute)"}}>{cur.date} · {cur.time}</span>
              {cur.kind!=="current" && <span className="mono" style={{fontSize:10.5,padding:"1px 6px",borderRadius:3,background:"var(--amber-bg, #fef3c7)",color:"var(--amber, #b45309)"}}>r{cur.rev}</span>}
            </div>
            <div style={{fontSize:12,color:"var(--ink-2)",marginTop:4}}>{cur.recs} записей · «{cur.label}» · <span className="mono">{cur.by}</span></div>
          </div>
        </div>

        {/* Scrubber */}
        <div style={{padding:"16px 20px 8px",borderBottom:"1px solid var(--line-2)",flexShrink:0,background:"var(--surface-2)"}}>
          <div style={{display:"flex",alignItems:"center",gap:10,marginBottom:10,flexWrap:"wrap"}}>
            <span className="cap" style={{fontSize:10,color:"var(--mute)"}}>быстро</span>
            {[["now","сейчас"],["week","неделя назад"],["month","месяц назад"],["year","год назад"],["seed","при создании"]].map(([k,l])=>(
              <button key={k} onClick={()=>jumpRel(k)} style={{height:24,padding:"0 9px",borderRadius:12,border:"1px solid var(--line)",background:"var(--surface)",color:"var(--ink-2)",fontSize:11.5,cursor:"pointer"}}>{l}</button>
            ))}
            <div style={{flex:1}}/>
            <span className="cap" style={{fontSize:10,color:"var(--mute)"}}>шаг</span>
            <div style={{display:"inline-flex",border:"1px solid var(--line)",borderRadius:6,overflow:"hidden"}}>
              {[["version","версия"],["day","день"]].map(([k,l])=>(
                <button key={k} onClick={()=>setGranularity(k)} style={{height:24,padding:"0 10px",border:"none",background: granularity===k?"var(--navy)":"var(--surface)",color: granularity===k?"#fff":"var(--ink-2)",fontSize:11.5,cursor:"pointer"}}>{l}</button>
              ))}
            </div>
            <button onClick={()=>setI(Math.max(0,i-1))} disabled={i===0} style={{height:24,width:28,border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color: i===0?"var(--line)":"var(--ink-2)",cursor: i===0?"default":"pointer",fontSize:12}}>‹</button>
            <button onClick={()=>setI(Math.min(last,i+1))} disabled={i===last} style={{height:24,width:28,border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color: i===last?"var(--line)":"var(--ink-2)",cursor: i===last?"default":"pointer",fontSize:12}}>›</button>
          </div>

          {/* Track */}
          <div style={{position:"relative",padding:"22px 6px 26px"}}>
            <div style={{position:"absolute",left:6,right:6,top:30,height:3,borderRadius:2,background:"var(--line-2)"}}/>
            <div style={{position:"absolute",left:6,top:30,height:3,borderRadius:2,background:"var(--accent)",width:`calc(${(i/last)*100}% - ${(i/last)*12}px + 6px)`}}/>
            <input type="range" min={0} max={last} step={1} value={i} onChange={e=>setI(Number(e.target.value))}
              style={{position:"absolute",left:0,right:0,top:22,width:"100%",appearance:"none",background:"transparent",height:20,cursor:"pointer",margin:0,padding:0,zIndex:3}}/>
            <style>{`
              input[type=range]::-webkit-slider-thumb{appearance:none;width:18px;height:18px;border-radius:50%;background:var(--navy);border:3px solid #fff;box-shadow:0 0 0 1px var(--navy),0 4px 10px rgba(15,17,21,.25);cursor:grab}
              input[type=range]:active::-webkit-slider-thumb{cursor:grabbing}
              input[type=range]::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--navy);border:3px solid #fff;box-shadow:0 0 0 1px var(--navy)}
            `}</style>
            {/* Version markers */}
            <div style={{position:"absolute",left:6,right:6,top:0,height:30,pointerEvents:"none"}}>
              {TT_VERSIONS.map((v,idx)=>{
                const pct = (idx/last)*100;
                const isCur = idx===i;
                return (
                  <div key={v.v} style={{position:"absolute",left:`${pct}%`,transform:"translateX(-50%)",textAlign:"center"}}>
                    <div className="mono" style={{fontSize:10,color: isCur?"var(--accent)":"var(--mute)",fontWeight: isCur?600:400,whiteSpace:"nowrap"}}>v{v.v}</div>
                    <div style={{width:1,height:6,background: isCur?"var(--accent)":"var(--line)",margin:"3px auto 0"}}/>
                  </div>
                );
              })}
            </div>
            {/* Date markers below */}
            <div style={{position:"absolute",left:6,right:6,top:42,height:14,pointerEvents:"none"}}>
              {TT_VERSIONS.map((v,idx)=>{
                if(idx!==0 && idx!==last && idx!==i && idx%2!==0) return null;
                const pct = (idx/last)*100;
                const isCur = idx===i;
                return (
                  <div key={v.v} className="mono" style={{position:"absolute",left:`${pct}%`,transform:"translateX(-50%)",fontSize:9.5,color: isCur?"var(--ink-2)":"var(--mute)",whiteSpace:"nowrap"}}>{v.date.slice(5)}</div>
                );
              })}
            </div>
          </div>
        </div>

        {/* Tabs */}
        <div style={{display:"flex",borderBottom:"1px solid var(--line)",flexShrink:0,padding:"0 12px"}}>
          {[["diff","Что изменилось", totalDelta],["records","Записи на момент", cur.recs],["meta","Структура и поля", null]].map(([k,l,n])=>(
            <button key={k} onClick={()=>setTab(k)} style={{
              border:"none",background:"transparent",padding:"10px 12px",fontSize:12.5,cursor:"pointer",
              color: tab===k?"var(--ink)":"var(--mute)",fontWeight: tab===k?600:500,
              borderBottom: tab===k?"2px solid var(--navy)":"2px solid transparent",marginBottom:-1,
              display:"inline-flex",alignItems:"center",gap:6
            }}>{l}{n!=null && <span className="mono" style={{fontSize:10.5,color:"var(--mute)",padding:"0 4px",background:"var(--bg)",borderRadius:3}}>{n}</span>}</button>
          ))}
        </div>

        {/* Body */}
        <div style={{flex:1,overflow:"auto",padding:"14px 20px",minHeight:0}}>
          {tab==="diff" && (
            <div>
              {totalDelta===0 ? (
                <div style={{textAlign:"center",color:"var(--mute)",padding:"40px 0",fontSize:13}}>Между этими версиями нет изменений.</div>
              ) : (
                <>
                  <div style={{display:"grid",gridTemplateColumns:"repeat(3,1fr)",gap:10,marginBottom:14}}>
                    {[["+ добавлено",diff.added.length,"var(--green)","#e6f6ec"],
                      ["~ изменено",diff.changed.length,"var(--amber, #b45309)","var(--warn-bg)"],
                      ["− удалено",diff.removed.length,"var(--pink)","var(--pink-bg)"]].map(([l,n,c,bg])=>(
                      <div key={l} style={{padding:"10px 12px",borderRadius:7,background:bg,border:"1px solid "+c+"33"}}>
                        <div className="cap" style={{color:c,fontSize:10}}>{l}</div>
                        <div className="mono" style={{fontSize:22,color:c,fontWeight:600,marginTop:2}}>{n}</div>
                      </div>
                    ))}
                  </div>
                  <div style={{border:"1px solid var(--line)",borderRadius:7,overflow:"hidden"}}>
                    {diff.added.map(([id,name])=>(
                      <div key={"a"+id} style={{display:"grid",gridTemplateColumns:"30px 130px 1fr",alignItems:"center",padding:"7px 12px",borderBottom:"1px solid var(--line-2)",fontSize:12.5}}>
                        <span style={{color:"var(--green)",fontWeight:600}}>+</span>
                        <span className="mono" style={{color:"var(--green)"}}>{id}</span>
                        <span style={{color:"var(--ink)"}}>{name} <span style={{color:"var(--mute)",fontSize:11.5,marginLeft:8}}>появилась в v{TT_VERSIONS[last].v}</span></span>
                      </div>
                    ))}
                    {diff.changed.map(([id,note])=>(
                      <div key={"c"+id} style={{display:"grid",gridTemplateColumns:"30px 130px 1fr",alignItems:"center",padding:"7px 12px",borderBottom:"1px solid var(--line-2)",fontSize:12.5}}>
                        <span style={{color:"var(--amber, #b45309)",fontWeight:600}}>~</span>
                        <span className="mono" style={{color:"var(--accent)"}}>{id}</span>
                        <span style={{color:"var(--ink-2)"}}>{note}</span>
                      </div>
                    ))}
                    {diff.removed.map(([id,name])=>(
                      <div key={"r"+id} style={{display:"grid",gridTemplateColumns:"30px 130px 1fr",alignItems:"center",padding:"7px 12px",borderBottom:"1px solid var(--line-2)",fontSize:12.5,textDecoration:"line-through",opacity:.75}}>
                        <span style={{color:"var(--pink)",fontWeight:600,textDecoration:"none"}}>−</span>
                        <span className="mono" style={{color:"var(--pink)"}}>{id}</span>
                        <span style={{color:"var(--ink-2)"}}>{name}</span>
                      </div>
                    ))}
                  </div>
                </>
              )}
            </div>
          )}

          {tab==="records" && (
            <div>
              <div style={{display:"flex",alignItems:"center",gap:10,marginBottom:10}}>
                <input placeholder="Поиск по записям на момент…" style={{...inp,height:30,flex:1,maxWidth:320}}/>
                <span className="cap" style={{fontSize:10,color:"var(--mute)"}}>показано первые 10 из {cur.recs}</span>
              </div>
              <table style={{width:"100%",borderCollapse:"collapse",fontSize:12.5}}>
                <thead><tr>{["ID","Название","Статус","Орбита","Источник"].map(h=>(
                  <th key={h} className="cap" style={{textAlign:"left",padding:"7px 10px",fontSize:10,color:"var(--mute)",borderBottom:"1px solid var(--line)",fontWeight:600}}>{h}</th>
                ))}</tr></thead>
                <tbody>
                  {RECORDS.slice(0, Math.min(10, cur.recs)).map(r=>(
                    <tr key={r[0]} style={{borderBottom:"1px solid var(--line-2)",background:"var(--surface)"}}
                        onMouseEnter={e=>e.currentTarget.style.background="var(--surface-2)"}
                        onMouseLeave={e=>e.currentTarget.style.background="var(--surface)"}>
                      <td className="mono" style={{padding:"6px 10px",color:"var(--accent)"}}>{r[0]}</td>
                      <td style={{padding:"6px 10px",color:"var(--ink)"}}>{r[1]}</td>
                      <td className="mono" style={{padding:"6px 10px",color:"var(--ink-2)",fontSize:11.5}}>{r[3]}</td>
                      <td className="mono" style={{padding:"6px 10px",color:"var(--ink-2)",fontSize:11.5}}>{r[4]}</td>
                      <td className="mono" style={{padding:"6px 10px",color:"var(--mute)",fontSize:11.5}}>v{cur.v}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}

          {tab==="meta" && (
            <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:14}}>
              <div>
                <div className="cap" style={{marginBottom:8}}>поля справочника на v{cur.v}</div>
                <div style={{border:"1px solid var(--line)",borderRadius:7,overflow:"hidden",fontSize:12}}>
                  {["id","name_ru","name_en","operator","status","orbit_type","launch_date",
                    ...(i>=3?["design_life"]:[]),
                    ...(i>=5?["ground_stations"]:[]),
                    ...(i>=7?["raw_json"]:[])].map((f,idx,arr)=>(
                    <div key={f} className="mono" style={{padding:"6px 10px",borderBottom: idx<arr.length-1?"1px solid var(--line-2)":"none",color:"var(--ink-2)"}}>{f}</div>
                  ))}
                </div>
              </div>
              <div>
                <div className="cap" style={{marginBottom:8}}>история этой версии</div>
                <div style={{fontSize:12.5,lineHeight:1.6,color:"var(--ink-2)"}}>
                  <div><span className="cap">commit</span> <span className="mono" style={{color:"var(--accent)"}}>r{cur.rev}</span></div>
                  <div><span className="cap">автор</span> <span className="mono">{cur.by}</span></div>
                  <div><span className="cap">дата</span> <span className="mono">{cur.date} · {cur.time}</span></div>
                  <div style={{marginTop:8,color:"var(--ink)"}}>«{cur.label}»</div>
                </div>
              </div>
            </div>
          )}
        </div>

        {/* Footer */}
        <footer style={{padding:"12px 20px",borderTop:"1px solid var(--line-2)",display:"flex",alignItems:"center",gap:10,background:"var(--surface-2)",flexShrink:0}}>
          <span className="cap" style={{fontSize:10,color:"var(--mute)"}}>snapshot не меняет данные — только показывает</span>
          <div style={{flex:1}}/>
          <button onClick={onClose} style={{height:32,padding:"0 14px",border:"1px solid var(--line)",borderRadius:6,background:"var(--surface)",color:"var(--ink-2)",cursor:"pointer",fontSize:12.5}}>Закрыть</button>
          <button disabled={i===last} onClick={()=>onApply("v"+cur.v)} style={{height:32,padding:"0 16px",border:"1px solid var(--navy)",borderRadius:6,background: i===last?"var(--line-2)":"var(--navy)",color: i===last?"var(--mute)":"var(--surface)",cursor: i===last?"default":"pointer",fontSize:12.5,fontWeight:600}}>Открыть v{cur.v} как readonly</button>
        </footer>
      </div>
    </Backdrop>
  );
}

/* ────────────────── APP ────────────────── */

function App(){
  const [view,setView]=useState("catalog");
  const [modal,setModal]=useState(null); // "create" | null
  const [editing,setEditing]=useState(null); // record tuple
  const [confirming,setConfirming]=useState(null);
  const [toast,setToast]=useState(null);
  const [tt,setTt]=useState(false);
  const [exp,setExp]=useState(false);
  return (
    <Shell view={view} setView={setView} onCreate={()=>setModal("create")}>
      {view==="catalog" && <Catalog onOpen={()=>setView("editor")} onCreate={()=>setModal("create")}/>}
      {view==="editor"  && <Editor onEdit={setEditing} onCreate={()=>setEditing(["NEW","","","planned","SSO",""])} onTimeTravel={()=>setTt(true)} onExport={()=>setExp(true)}/>}
      {tt && <TimeTravelModal onClose={()=>setTt(false)} onApply={(v)=>{setTt(false);setToast("Открыт snapshot "+v+" (readonly)");}}/>}
      {exp && <ExportModal onClose={()=>setExp(false)} onDone={(fmt)=>{setExp(false);setToast("Экспорт "+fmt+" поставлен в очередь");}}/>}
      {modal==="create" && <CreateDictModal onClose={()=>{setModal(null);setToast("Справочник создан как draft v0.1.0");}}/>}
      <RecordDrawer rec={editing} onClose={()=>setEditing(null)} onDelete={()=>{setConfirming(editing);}}/>
      <ConfirmDialog rec={confirming} onClose={()=>setConfirming(null)} onConfirm={()=>{setConfirming(null);setEditing(null);setToast("Запись помечена удалённой");}}/>
      <Toast msg={toast} onClose={()=>setToast(null)}/>
    </Shell>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
</script>
</body></html>
