diff --git a/frontend/js/labs/lab-glue.js b/frontend/js/labs/lab-glue.js
index ef572a9..3317a05 100644
--- a/frontend/js/labs/lab-glue.js
+++ b/frontend/js/labs/lab-glue.js
@@ -971,6 +971,58 @@
});
}
+ /* ── Контент-движок, Фаза 5: чип «Связано с программой» ──────────────────
+ Подтягивает курикулумные связи симуляции (GET /api/lab/sims/:id/related) и
+ рендерит чипы-ссылки рядом с заголовком симуляции. Самодостаточно: создаёт
+ контейнер #sim-related динамически (без правок lab.html/CSS — меньше риск
+ конфликта с параллельными сессиями). Тихо прячется, если связей нет/ошибка. */
+ var _LAB_LINK_ICON = '';
+ function _labRelEsc(s) {
+ return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
+ return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
+ });
+ }
+ function _ensureRelatedHost() {
+ var host = document.getElementById('sim-related');
+ if (host) return host;
+ host = document.createElement('div');
+ host.id = 'sim-related';
+ host.style.cssText = 'display:none;align-items:center;gap:6px;flex-wrap:wrap;margin-left:14px;min-width:0';
+ var title = document.getElementById('sim-topbar-title');
+ if (title && title.parentNode) title.parentNode.insertBefore(host, title.nextSibling);
+ return host;
+ }
+ function _loadRelated(simId) {
+ var host = _ensureRelatedHost();
+ host.style.display = 'none';
+ host.innerHTML = '';
+ if (!window.LS || !LS.api) return;
+ LS.api('/api/lab/sims/' + encodeURIComponent(simId) + '/related')
+ .then(function (data) {
+ var links = (data && data.links) || {};
+ var all = [].concat(links.textbook || [], links.topic || [], links.kmap || [], links.question || []);
+ if (!all.length) return;
+ var chipBase = 'display:inline-flex;align-items:center;gap:4px;font-size:.72rem;padding:3px 9px;border-radius:999px;';
+ var html = ''
+ + _LAB_LINK_ICON + ' Связано с программой';
+ all.forEach(function (l) {
+ var label = _labRelEsc(l.label || (l.kind + ':' + l.ref_id));
+ if (l.href) {
+ html += '' + label + '';
+ } else {
+ html += '' + label + '';
+ }
+ });
+ host.innerHTML = html;
+ host.style.display = 'flex';
+ if (window.lucide) lucide.createIcons();
+ })
+ .catch(function () { /* нет связей или ошибка — чип просто не показываем */ });
+ }
+ window._loadRelated = _loadRelated;
+
/* ── embed mode + auto-open from ?sim= ── */
const _qp = new URLSearchParams(location.search);
var _embedMode = _qp.get('embed') === '1';
diff --git a/frontend/js/labs/lab-init.js b/frontend/js/labs/lab-init.js
index 3e9d5d1..b48303a 100644
--- a/frontend/js/labs/lab-init.js
+++ b/frontend/js/labs/lab-init.js
@@ -119,6 +119,9 @@
// load theory for this sim
loadTheory(id.includes(':') ? id.split(':')[0] : id);
+ // Фаза 5: чип «Связано с программой» (курикулумные связи симуляции).
+ if (typeof _loadRelated === 'function') _loadRelated(id.includes(':') ? id.split(':')[0] : id);
+
// ── Контент-движок (Фаза 1): диспетчеризация через реестр ──
// Все каталожные симуляции зарегистрированы в _register-all.js.
// Алиасы deep-link (magnetic/coulomb/thinlens/mirrors/refraction) нормализуем