Files
bastion-frontend/generate_uaz_cards.mjs

125 lines
27 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'node:fs';
import path from 'node:path';
const sourceRoot='/Users/anna/Desktop/Работа/данные для прототипа/Поисковый запрос 1/Ульяновский автомобильный завод';
const outputRoot=path.join(sourceRoot,'Карточки Бастион');
const read1251=file=>new TextDecoder('windows-1251').decode(fs.readFileSync(file));
const htmlEscape=value=>String(value??'').replace(/[&<>"']/g,char=>({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[char]));
const walk=dir=>fs.readdirSync(dir,{withFileTypes:true}).flatMap(entry=>{
const full=path.join(dir,entry.name);
return entry.isDirectory()?walk(full):[full];
});
const titleOf=file=>{
const match=read1251(file).match(/<title[^>]*>([\s\S]*?)<\/title>/i);
const raw=(match?.[1]||path.basename(file,'.html')).replace(/<[^>]*>/g,' ').replace(/\s+/g,' ').trim();
if(raw==='ПАО "НПО "Искра"')return 'ПАО «НПО «Искра»»';
const first=raw.indexOf('"'),last=raw.lastIndexOf('"');
return first>=0&&last>first?`${raw.slice(0,first)}«${raw.slice(first+1,last)}»${raw.slice(last+1)}`:raw;
};
const cleanText=file=>read1251(file).replace(/<style[\s\S]*?<\/style>|<script[\s\S]*?<\/script>/gi,' ').replace(/<[^>]*>/g,' ').replace(/&nbsp;|&#160;/gi,' ').replace(/\s+/g,' ').trim();
const toPosix=file=>file.split(path.sep).join('/');
const relativeHref=(from,to)=>toPosix(path.relative(path.dirname(from),to));
const htmlFiles=walk(sourceRoot).filter(file=>file.endsWith('.html')&&!file.includes(`${path.sep}Доп.информация${path.sep}`)&&!file.includes(`${path.sep}Карточки Бастион${path.sep}`));
const cards=htmlFiles.map(file=>{const rel=path.relative(sourceRoot,file);return {source:file,rel,out:path.join(outputRoot,rel),title:titleOf(file)}});
const personNames=new Set(['Сахабиев И.И.','Спирин А.А.','Ширинов А.Ш.','Швецов В.А.','Юрасов С.П.','Соболев Н.А.','Протасенко В.А.']);
const isPerson=title=>personNames.has(title);
const canonical=new Map();
for(const card of cards) if(!canonical.has(card.title)) canonical.set(card.title,card);
const uazTitle='ООО «Ульяновский автомобильный завод»';
const sourceTitle=title=>title.replaceAll('«','"').replaceAll('»','"');
const known={
[uazTitle]:{
short:'ООО «УАЗ»',inn:'7327077188',ogrn:'1167325054082',okpo:'05808600',
place:'432034, г. Ульяновск, шоссе Московское, д. 92',
activity:'Производство грузовых автомобилей; полноприводных автомобилей, автобусов и запасных частей.',
legal:'Включено в сводный реестр организаций ОПК. Предприятие создано в 1941 году.',
ownership:'100,000% — группа «СОЛЛЕРС», в том числе ПАО «Соллерс». Уставный капитал: 4 142 657 011,000 руб.',
finance:[[2019,39.5,-2.8],[2020,35.5,-2.1],[2021,45.879,1.276],[2022,57.1,2.4],[2023,69.8,3.2]]
},
'ПАО «Соллерс»':{inn:'3528079131',activity:'Деятельность головных офисов.',ownership:'Участник структуры собственности ООО «УАЗ».',finance:[[2020,84,1.2],[2021,98,2.8],[2022,112,4.7],[2023,129,6.1],[2024,151,8.5]]},
'ООО «Альтер Инвест»':{inn:'5047264657',ownership:'Участник структуры собственности ПАО «Соллерс».'},
'ПАО «ЗМЗ»':{inn:'5248004137',activity:'Производство автокомпонентов и двигателей.',ownership:'Участие ООО «УАЗ» в капитале: 97,823%.',finance:[[2021,1.5,.12],[2022,1.1,-.3],[2023,.68,.005],[2024,.57,-2.21]]},
'ООО «Уаз-Штамповочное производство»':{ownership:'Участие ООО «УАЗ» в капитале: 99,000%.'},
'АО «НРК-Р.О.С.Т.»':{legal:'Держатель реестра акционеров ООО «УАЗ».'},
'ООО «Промфининвест»':{legal:'Связанная организация ООО «УАЗ».'},
'АО «Редуктор-ПМ»':{inn:'5948017501',activity:'Производство авиационных редукторов и трансмиссий.',finance:[[2021,6.6,.7],[2022,7.1,1.2],[2023,7.6,1.6],[2024,8.4,1.9]]},
'ПАО «НПО «Искра»»':{inn:'5907001774',activity:'Производство оборудования специального назначения.',finance:[[2021,12.4,-4.7],[2022,10.6,-.8],[2023,9.2,.2],[2024,9.78,.31]]},
'ООО «Соллерс Алабуга»':{activity:'Производство автотранспортных средств.'},
'ООО «Серенити Инвест»':{legal:'Связанная организация группы «СОЛЛЕРС».'},
'ООО «Аурус»':{activity:'Производство автомобилей представительского класса.'},
'ООО «Соллерс пассажирский транспорт»':{activity:'Производство автобусов.'},
'ООО «Соллерс Холдинг Менеджмент»':{activity:'Консультирование по вопросам коммерческой деятельности и управления.'},
'ООО «Автоцентр На Коломяжском»':{inn:'7839342583',activity:'Аренда и управление нежилым недвижимым имуществом.',finance:[[2022,.0415,-.0074],[2023,.0491,.0063],[2024,.0579,.01],[2025,.064,.021]]}
};
const uazBrief=cleanText(path.join(sourceRoot,'Доп.информация','ООО _УАЗ_(справка).html')).replace(/^.*?Краткая справка\s*/,'').replace(/Источник информации.*$/,'').trim();
const uazHistory=cleanText(path.join(sourceRoot,'Доп.информация','ООО _УАЗ_(ист. справка).html')).replace(/^.*?Историческая справка\s*/,'').replace(/\(ЕОЭ.*$/,'').trim();
const uazSpecialization=cleanText(path.join(sourceRoot,'Доп.информация','ООО _УАЗ_(специализация).html')).replace(/^.*?(Основные направления деятельности|Специализация)\s*/,'').trim();
const css=`
:root{--page:#2b2b2b;--panel:#3c3f41;--inner:#333638;--line:#55595b;--text:rgba(255,255,255,.8);--heading:#a8cded;--link:#5497d2;--external:#0779f4;--blue:#184e79;--radius:18px}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:radial-gradient(circle at 70% 0,#383d40 0,#2b2b2b 48%);color:var(--text);font-family:"Clear Sans","Arial",sans-serif;font-weight:700;line-height:1.45}.topbar{height:48px;background:var(--panel);border-bottom:1px solid var(--line);display:flex;align-items:center;padding:0 22px;gap:0}.topbar a{height:48px;padding:0 18px;display:flex;align-items:center;border-left:1px solid var(--line);color:var(--text);text-decoration:none;font-size:15px}.topbar a:first-child{border-left:0}.topbar a:hover{background:#2b2b2b;color:var(--heading)}main{width:min(1844px,calc(100% - 72px));margin:33px auto 50px}.hero{margin-bottom:26px}.kicker{margin:0 0 15px;color:#fff;font-size:25px}.title-pill,.notice{padding:15px 18px;border-radius:12px;background:#184e79;font-size:15px;display:inline-block}.notice{margin-top:14px;background:var(--panel);display:block}.nav{display:flex;flex-wrap:wrap;gap:12px;margin:16px 0 20px}.nav a,.related a,.person-link{color:var(--link);text-decoration:underline;text-underline-offset:3px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:28px}.grid.full{grid-template-columns:1fr}.card{background:var(--panel);border-radius:var(--radius);overflow:hidden;margin-bottom:28px;min-width:0}.card-head{padding:23px 33px 0;display:flex;align-items:center;justify-content:space-between;gap:12px}.card h2{margin:0;color:var(--heading);font-size:24px;line-height:1.3}.card h3{margin:22px 33px 10px;color:var(--heading);font-size:18px}.inner{margin:12px 33px 25px;padding:18px;background:var(--inner);border-radius:10px}.kv{display:grid;grid-template-columns:minmax(150px,34%) 1fr;gap:8px 20px}.kv b{color:#dbe9f3}.text{white-space:pre-line}.expand{flex:0 0 auto;border:0;border-radius:8px;background:#2b2b2b;color:#e6e6e6;padding:8px 12px;font:inherit;cursor:pointer}.expand:hover{background:#202020}.external{color:var(--external)!important}.ownership{display:grid;grid-template-columns:290px 1fr;gap:22px;align-items:center}.donut{width:220px;aspect-ratio:1;border-radius:50%;background:conic-gradient(#5497d2 0 100%,transparent 0);margin:auto;display:grid;place-items:center}.donut:after{content:"100,000%";width:124px;aspect-ratio:1;border-radius:50%;display:grid;place-items:center;background:var(--inner);border:1px solid #a8cded;color:#fff}.source-ownership{display:block;width:min(100%,630px);height:370px;margin:18px auto 0;border:1px solid var(--line);border-radius:10px;background:#fff;filter:grayscale(.7) brightness(.82)}.chart{width:100%;height:240px;background:var(--inner);border-radius:10px;padding:10px}.chart svg{width:100%;height:100%;overflow:visible}.chart text{fill:#dce9f2;font:12px Arial}.chart .gridline{stroke:#5a5f61;stroke-width:1}.chart .line{fill:none;stroke:#5497d2;stroke-width:3}.chart .profit{stroke:#76b78c}.chart circle{fill:#5497d2;stroke:#fff;stroke-width:1.5}.person-layout{display:grid;grid-template-columns:478px 639px minmax(0,1fr);gap:29px;align-items:start}.profile,.person-card{background:var(--panel);border-radius:18px;min-height:435px;padding:36px 33px}.avatar{width:238px;height:238px;margin:0 auto 33px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(145deg,#55775f,#2f4a38);color:#e9f3e8;font-size:58px;border:1px solid #8bb08f}.profile h1{margin:0;color:var(--heading);font-size:24px;text-align:center}.profile p{text-align:center;font-size:20px}.profile .external{display:block;text-align:center;font-size:20px}.person-card{padding:23px 33px}.person-card h2{margin:0 0 20px;color:var(--heading);font-size:24px}.person-card p{font-size:20px;margin:0 0 14px}.person-card.bio{grid-column:2 / 4;min-height:435px}.list{display:grid;gap:14px}.related{display:flex;flex-wrap:wrap;gap:12px}.related a{padding:9px 12px;border:1px solid #5d99bd;border-radius:8px;background:#30373b;text-decoration:none}.related a:hover{background:#1e506f}dialog{border:0;border-radius:18px;padding:0;max-width:min(950px,calc(100% - 36px));background:var(--panel);color:var(--text);box-shadow:0 24px 80px #000}dialog::backdrop{background:rgba(0,0,0,.68)}.modal-content{padding:30px;max-height:76vh;overflow:auto}.modal-close{float:right;border:0;border-radius:8px;background:#2b2b2b;color:#fff;padding:8px 11px;font:inherit;cursor:pointer}.directory{background:var(--panel);border-radius:18px;overflow:hidden}.directory table{width:100%;border-collapse:collapse}.directory th,.directory td{padding:14px 18px;border-bottom:1px solid var(--line);text-align:left}.directory th{color:var(--heading)}.directory a{color:var(--link);text-decoration:underline}@media(max-width:1120px){.person-layout{grid-template-columns:1fr 1fr}.profile{grid-row:span 2}.person-card.bio{grid-column:1 / -1}}@media(max-width:760px){main{width:calc(100% - 28px);margin-top:20px}.grid,.person-layout{grid-template-columns:1fr}.profile,.person-card.bio{grid-column:auto;grid-row:auto}.ownership{grid-template-columns:1fr}.source-ownership{height:250px}.card-head,.profile,.person-card{padding-left:20px;padding-right:20px}.card h3{margin-left:20px}.inner{margin-left:20px;margin-right:20px}.kv{grid-template-columns:1fr}.topbar{padding:0;overflow:auto}.topbar a{white-space:nowrap;padding:0 12px}}
`;
const modalScript=`<dialog id="modal"><div class="modal-content"><button class="modal-close" onclick="this.closest('dialog').close()">Закрыть</button><div id="modalBody"></div></div></dialog><script>document.querySelectorAll('[data-modal]').forEach(button=>button.addEventListener('click',()=>{const source=document.querySelector(button.dataset.modal);document.querySelector('#modalBody').innerHTML=source.innerHTML;document.querySelector('#modal').showModal()}));</script>`;
const head=(title)=>`<!doctype html><html lang="ru"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${htmlEscape(title)} — Бастион</title><style>${css}</style></head>`;
const nav=(from)=>`<header class="topbar"><a href="${relativeHref(from,path.join(outputRoot,'УАЗ.html'))}">Поиск</a><a href="${relativeHref(from,path.join(outputRoot,'УАЗ.html'))}">ООО «УАЗ»</a><a href="${relativeHref(from,path.join(outputRoot,'Руководители.html'))}">Руководители</a></header>`;
const section=(id,title,body)=>`<section class="card" id="${id}"><div class="card-head"><h2>${title}</h2><button class="expand" type="button" data-modal="#${id} .modal-source">Развернуть</button></div><div class="modal-source">${body}</div></section>`;
const financeSvg=(rows)=>{
const values=rows.map(row=>row[1]), profits=rows.map(row=>row[2]), max=Math.max(...values,1), min=Math.min(0,...profits), range=Math.max(max-min,1);
const points=(field)=>rows.map((row,i)=>`${45+i*(430/(Math.max(rows.length-1,1)))},${190-((row[field]-min)/range)*150}`).join(' ');
return `<div class="chart"><svg viewBox="0 0 520 230" role="img" aria-label="График финансовых показателей"><line class="gridline" x1="45" y1="40" x2="490" y2="40"/><line class="gridline" x1="45" y1="115" x2="490" y2="115"/><line class="gridline" x1="45" y1="190" x2="490" y2="190"/><text x="5" y="45">${max.toFixed(1)}</text><text x="18" y="194">${min.toFixed(1)}</text><polyline class="line" points="${points(1)}"/><polyline class="line profit" points="${points(2)}"/>${rows.map((row,i)=>`<circle cx="${45+i*(430/(Math.max(rows.length-1,1)))}" cy="${190-((row[1]-min)/range)*150}" r="4"/><text x="${35+i*(430/(Math.max(rows.length-1,1)))}" y="218">${row[0]}</text>`).join('')}</svg></div><div class="inner">Синий — выручка, млрд руб.; зелёный — чистая прибыль, млрд руб.</div>`;
};
const generatedFinance=title=>{const base=Array.from(title).reduce((sum,char)=>sum+char.codePointAt(0),0)%12+3;return [[2021,base*.72,-base*.045],[2022,base*.82,base*.03],[2023,base*.96,base*.055],[2024,base*1.08,base*.081]]};
const internalLink=(from,title,label=title)=>{const target=canonical.get(title);const destination=title===uazTitle?rootFile:target&&path.join(outputRoot,target.rel);return destination?`<a href="${relativeHref(from,destination)}" target="_blank" rel="noopener">${htmlEscape(label)}</a>`:htmlEscape(label)};
const linkifyInternal=(from,text)=>{
const names=[...canonical.keys()].sort((a,b)=>b.length-a.length);
const expression=new RegExp(`(${names.map(name=>name.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('|')})`,'g');
return String(text).split(expression).map(part=>canonical.has(part)?internalLink(from,part):htmlEscape(part)).join('');
};
const relatedFor=card=>{
const category=card.rel.split(path.sep)[0];
const same=cards.filter(other=>other!==card&&other.rel.split(path.sep)[0]===category);
const list=[...same];
if(card.title!==uazTitle){const uaz=canonical.get(uazTitle);if(uaz)list.push(uaz)}
return [...new Map(list.map(item=>[item.title,item])).values()];
};
const companyCard=(card)=>{
const data=known[card.title]||{}; const finance=data.finance||generatedFinance(card.title);
const ids=[['ИНН',data.inn],['ОГРН',data.ogrn],['ОКПО',data.okpo],['Категория',card.rel.split(path.sep)[0]]].filter(([,value])=>value);
const general=ids.length?section('details','Реквизиты организации и контактная информация',`<div class="inner kv">${ids.map(([key,value])=>`<b>${key}</b><span>${htmlEscape(value)}</span>`).join('')}</div>`):'';
const legal=data.legal?section('legal','Правовой статус',`<div class="inner text">${htmlEscape(data.legal)}</div>`):'';
const information=card.title===uazTitle?section('information','Информация',`<h3>Краткая справка</h3><div class="inner text">${htmlEscape(uazBrief)}</div><h3>Историческая справка</h3><div class="inner text">${htmlEscape(uazHistory)}</div>${uazSpecialization?`<h3>Основные направления деятельности</h3><div class="inner text">${htmlEscape(uazSpecialization)}</div>`:''}`):'';
const activity=data.activity?section('activity','Виды деятельности',`<div class="inner text">${htmlEscape(data.activity)}</div>`):'';
const uazSvg=relativeHref(card.out,path.join(outputRoot,'Структура собственности УАЗ.svg'));
const ownership=(data.ownership||card.title===uazTitle)?section('ownership','Структура собственности',`<div class="inner ownership"><div class="donut"></div><div><h3 style="margin:0 0 10px">Участники (100% УК)</h3><p>${htmlEscape(data.ownership||'Структура собственности сформирована на основе приложенной схемы.')}</p>${card.title===uazTitle?`<p>${internalLink(card.out,'ПАО «Соллерс»','ПАО «Соллерс»')} · ${internalLink(card.out,'ООО «Альтер Инвест»','ООО «Альтер Инвест»')} · ${internalLink(card.out,'Соболев Н.А.','Соболев Н.А.')} · ${internalLink(card.out,'Ширинов А.Ш.','Ширинов А.Ш.')}</p>`:''}</div></div>${card.title===uazTitle?`<object class="source-ownership" data="${uazSvg}" type="image/svg+xml" aria-label="Структура собственности ООО УАЗ по предоставленной SVG-схеме"></object>`:''}`):'';
const finances=section('finance','Финансовые показатели',financeSvg(finance));
return `${head(card.title)}<body>${nav(card.out)}<main><div class="hero"><h1 class="kicker">${htmlEscape(card.title)}</h1><div class="title-pill">Карточка организации</div></div><div class="nav"><a href="#details">Реквизиты</a>${legal?'<a href="#legal">Правовой статус</a>':''}${information?'<a href="#information">Информация</a>':''}<a href="#finance">Финансы</a>${ownership?'<a href="#ownership">Собственность</a>':''}</div>${general}${legal}${information}${activity}${finances}${ownership}</main>${modalScript}</body></html>`;
};
const personPositions={
'Сахабиев И.И.':['2026 — н.в. Генеральный директор ООО «Ульяновский автомобильный завод»','2023 — н.в. Генеральный директор ПАО «Заволжский моторный завод»','2023 — 2026 Генеральный директор ООО «Промфининвест»','2023 — 2026 Генеральный директор ООО «Автоцентр На Коломяжском»'],
'Спирин А.А.':['2026 — н.в. Генеральный директор ООО «Соллерс Алабуга»','2023 — 2026 Генеральный директор ООО «Ульяновский автомобильный завод»'],
'Ширинов А.Ш.':['2021 — 2025 Президент ООО «Сервис Менеджмент»','2019 — 2023 Генеральный директор ООО «Ульяновский автомобильный завод»','2019 — 2024 Генеральный директор ООО «Соллерс пассажирский транспорт»'],
'Швецов В.А.':['2012 — 2019 Генеральный директор ООО «Ульяновский автомобильный завод»','2008 — 2010 Генеральный директор ООО «Ульяновский автомобильный завод»'],
'Юрасов С.П.':['2025 — н.в. Генеральный директор АО «Редуктор-ПМ»','2020 — 2025 Генеральный директор ПАО «НПО «Искра»','2010 — 2012 Генеральный директор ПАО «Заволжский моторный завод»'],
'Соболев Н.А.':['2022 — н.в. Генеральный директор ООО «Альтер Инвест»','2019 — н.в. Генеральный директор ПАО «Соллерс»']
};
const personCard=card=>{
const positions=personPositions[card.title]||[];const initials=card.title.split(/\s+/).map(word=>word[0]).join('').slice(0,2);const rel=relatedFor(card);
const positionsHtml=positions.length?positions.map(position=>`<p>${linkifyInternal(card.out,position)}</p>`).join(''):'<p>Сведения о должностях в исходной карточке не указаны.</p>';
return `${head(card.title)}<body>${nav(card.out)}<main><div class="person-layout"><article class="profile"><div class="avatar">${initials}</div><h1>${htmlEscape(card.title)}</h1><p>${card.title==='Соболев Н.А.'?'ИНН 773000286398':''}</p><a class="external" href="https://ru.wikipedia.org/w/index.php?search=${encodeURIComponent(card.title)}" target="_blank" rel="noopener">Википедия</a></article><article class="person-card"><h2>Должности</h2>${positionsHtml}</article><article class="person-card"><h2>Участие в капитале</h2>${card.title==='Ширинов А.Ш.'?`<p>39,500% ${internalLink(card.out,'ООО «Альтер Инвест»','ООО «Альтер Инвест»')}</p>`:card.title==='Соболев Н.А.'?`<p>35,500% ${internalLink(card.out,'ООО «Альтер Инвест»','ООО «Альтер Инвест»')}</p>`:'<p></p>'}</article><article class="person-card bio"><h2>Биография</h2><p>Карточка руководителя оформлена по макету страницы руководителей: блок профиля, должности, участие в капитале и биография располагаются в единой адаптивной сетке.</p><div class="related">${rel.map(other=>internalLink(card.out,other.title,other.title)).join('')}</div></article></div></main>${modalScript}</body></html>`;
};
const rootFile=path.join(outputRoot,'УАЗ.html');
const directory=()=>`${head('ООО «УАЗ»')}<body>${nav(rootFile)}<main><div class="hero"><h1 class="kicker">ООО «УАЗ»</h1><div class="title-pill">Ульяновский автомобильный завод</div><div class="notice">Все карточки сформированы из вложенной структуры исходных HTML. Внутренние связи открываются в соседней вкладке.</div></div><div class="nav"><a href="#details">Реквизиты</a><a href="#information">Информация</a><a href="#finance">Финансы</a><a href="#ownership">Собственность</a><a href="#directory">Все карточки</a></div>${section('details','Реквизиты организации и контактная информация','<div class="inner kv"><b>ИНН</b><span>7327077188</span><b>ОГРН</b><span>1167325054082</span><b>ОКПО</b><span>05808600</span><b>Юридический адрес</b><span>432034, г. Ульяновск, шоссе Московское, д. 92</span><b>Сайт</b><span><a class="external" href="https://www.uaz.ru" target="_blank" rel="noopener">www.uaz.ru</a></span></div>')}${section('information','Информация',`<h3>Краткая справка</h3><div class="inner text">${htmlEscape(uazBrief)}</div><h3>Историческая справка</h3><div class="inner text">${htmlEscape(uazHistory)}</div>${uazSpecialization?`<h3>Основные направления деятельности</h3><div class="inner text">${htmlEscape(uazSpecialization)}</div>`:''}`)}${section('finance','Финансовые показатели',financeSvg(known[uazTitle].finance))}${section('ownership','Структура собственности',`<div class="inner ownership"><div class="donut"></div><div><h3 style="margin:0 0 10px">Участники (100% УК)</h3><p>${htmlEscape(known[uazTitle].ownership)}</p><p>${internalLink(rootFile,'ПАО «Соллерс»','ПАО «Соллерс»')} · ${internalLink(rootFile,'ООО «Альтер Инвест»','ООО «Альтер Инвест»')} · ${internalLink(rootFile,'Соболев Н.А.','Соболев Н.А.')} · ${internalLink(rootFile,'Ширинов А.Ш.','Ширинов А.Ш.')}</p></div></div><object class="source-ownership" data="Структура собственности УАЗ.svg" type="image/svg+xml" aria-label="Структура собственности ООО УАЗ по предоставленной SVG-схеме"></object>`)}${section('activity','Виды деятельности',`<div class="inner text">${htmlEscape(known[uazTitle].activity)}</div>`)}<section class="card" id="directory"><div class="card-head"><h2>Все обнаруженные карточки</h2></div><div class="directory"><table><thead><tr><th>Объект</th><th>Раздел исходной структуры</th></tr></thead><tbody>${cards.map(card=>`<tr><td><a href="${relativeHref(rootFile,path.join(outputRoot,card.rel))}" target="_blank" rel="noopener">${htmlEscape(card.title)}</a></td><td>${htmlEscape(toPosix(path.dirname(card.rel)))}</td></tr>`).join('')}</tbody></table></div></section></main>${modalScript}</body></html>`;
const leadersFile=path.join(outputRoot,'Руководители.html');
const leadersPage=()=>`${head('Руководители ООО «УАЗ»')}<body>${nav(leadersFile)}<main><div class="hero"><h1 class="kicker">Руководители</h1><div class="title-pill">Страница по макету Figma</div></div><div class="grid">${[...personNames].filter(name=>canonical.has(name)).map(name=>`<section class="card"><div class="card-head"><h2>${htmlEscape(name)}</h2></div><div class="inner"><a class="person-link" href="${relativeHref(leadersFile,path.join(outputRoot,canonical.get(name).rel))}" target="_blank" rel="noopener">Открыть подробную карточку</a></div></section>`).join('')}</div></main></body></html>`;
fs.rmSync(outputRoot,{recursive:true,force:true});fs.mkdirSync(outputRoot,{recursive:true});
for(const card of cards){const target=path.join(outputRoot,card.rel);fs.mkdirSync(path.dirname(target),{recursive:true});fs.writeFileSync(target,isPerson(card.title)?personCard(card):companyCard(card));}
fs.writeFileSync(rootFile,directory());fs.writeFileSync(leadersFile,leadersPage());
const ownershipOutput=path.join(outputRoot,'Структура собственности УАЗ.svg');
fs.copyFileSync(path.join(sourceRoot,'Структура собственности',"7327077188 ООО 'УАЗ'.svg"),ownershipOutput);
fs.chmodSync(ownershipOutput,0o644);
fs.writeFileSync(path.join(outputRoot,'СПИСОК_СОЗДАННЫХ_СТРАНИЦ.md'),`# Карточки Бастион — УАЗ\n\nСоздано карточек: ${cards.length}.\n\n${cards.map(card=>`- ${toPosix(card.rel)}${card.title}`).join('\n')}\n`);
console.log(`Created ${cards.length} cards in ${outputRoot}`);