(() => { const hero = document.querySelector('.dbc-particle-hero#home'); if (!hero || hero.querySelector('.v284-release-rail')) return; const previewCatalog = { product: { schemaVersion: 1, enabled: true, status: 'approved', id: 'preview-bioten-product-update', kind: 'product', publishAt: '2026-07-18T00:00:00+08:00', expiresAt: '2099-12-31T23:59:59+08:00', labelCn: '产品更新', labelEn: 'Update', titleCn: 'BIOTEN 水性阻隔乳液资料更新', titleEn: 'BIOTEN | Barrier emulsion', actionCn: '查看详情', actionEn: 'View update', href: 'bioten-products.html' }, news: { schemaVersion: 1, enabled: true, status: 'approved', id: 'preview-casu-basf-news', kind: 'news', publishAt: '2026-06-12T00:00:00+08:00', expiresAt: '2099-12-31T23:59:59+08:00', labelCn: '最新动态', labelEn: 'News', titleCn: '中科联化 × 巴斯夫|水性粘合剂协同', titleEn: 'CASU × BASF | Binder update', actionCn: '阅读全文', actionEn: 'Read article', href: 'news-2026-06-12-casu-basf-water-based-battery-binder.html' } }; const requiredTextFields = [ 'id', 'kind', 'publishAt', 'expiresAt', 'labelCn', 'labelEn', 'titleCn', 'titleEn', 'actionCn', 'actionEn', 'href' ]; function readReleaseConfig() { const source = document.getElementById('dbc-home-release-config'); if (!source) return null; try { return JSON.parse(source.textContent || ''); } catch (error) { return null; } } function hasCompleteFields(release) { return release && release.schemaVersion === 1 && requiredTextFields.every((field) => typeof release[field] === 'string' && release[field].trim()); } function hasTimezone(value) { return /^\d{4}-\d{2}-\d{2}T.+(?:Z|[+-]\d{2}:\d{2})$/.test(value); } function isSafeRelativeHtmlPath(value) { if (typeof value !== 'string') return false; const href = value.trim(); const path = href.split(/[?#]/, 1)[0]; if (!href || !path || href.startsWith('/') || href.startsWith('//') || href.includes('\\')) return false; if (/^[a-z][a-z\d+.-]*:/i.test(href)) return false; if (path.split('/').includes('..')) return false; return /\.html?$/i.test(path); } function isApprovedAndActive(release) { if (!hasCompleteFields(release)) return false; if (release.enabled !== true || release.status !== 'approved') return false; if (!['product', 'news', 'resource'].includes(release.kind)) return false; if (!hasTimezone(release.publishAt) || !hasTimezone(release.expiresAt)) return false; if (!isSafeRelativeHtmlPath(release.href)) return false; const publishAt = Date.parse(release.publishAt); const expiresAt = Date.parse(release.expiresAt); const now = Date.now(); return Number.isFinite(publishAt) && Number.isFinite(expiresAt) && publishAt < expiresAt && now >= publishAt && now < expiresAt; } const searchParams = new URLSearchParams(window.location.search); const requestedPreview = searchParams.get('release'); if (requestedPreview === 'off') return; const isLocalPreview = window.location.protocol === 'file:' || /^(?:localhost|127(?:\.\d{1,3}){3}|\[::1\])$/i.test(window.location.hostname); const previewRelease = isLocalPreview ? previewCatalog[requestedPreview] : null; const release = previewRelease || readReleaseConfig(); if (!previewRelease && !isApprovedAndActive(release)) return; if (previewRelease && !hasCompleteFields(release)) return; const rail = document.createElement('aside'); rail.className = 'v284-release-rail'; rail.dataset.releaseKind = release.kind; rail.dataset.releaseId = release.id; rail.dataset.ariaCn = release.labelCn; rail.dataset.ariaEn = release.labelEn; const link = document.createElement('a'); link.className = 'v284-release-rail__link'; link.href = release.href; const meta = document.createElement('span'); meta.className = 'v284-release-rail__meta'; const label = document.createElement('span'); label.dataset.navCn = release.labelCn; label.dataset.navEn = release.labelEn; const time = document.createElement('time'); time.dateTime = release.publishAt; time.textContent = release.publishAt.slice(0, 10).replaceAll('-', '.'); meta.append(label, time); const title = document.createElement('strong'); title.className = 'v284-release-rail__title'; title.dataset.navCn = release.titleCn; title.dataset.navEn = release.titleEn; const action = document.createElement('span'); action.className = 'v284-release-rail__action'; const actionText = document.createElement('span'); actionText.dataset.navCn = release.actionCn; actionText.dataset.navEn = release.actionEn; const arrow = document.createElement('i'); arrow.className = 'v284-release-rail__arrow'; arrow.setAttribute('aria-hidden', 'true'); arrow.textContent = '↗'; action.append(actionText, arrow); function syncLanguage() { const language = document.documentElement.lang === 'en' ? 'en' : 'cn'; rail.setAttribute('aria-label', language === 'en' ? release.labelEn : release.labelCn); label.textContent = language === 'en' ? release.labelEn : release.labelCn; title.textContent = language === 'en' ? release.titleEn : release.titleCn; actionText.textContent = language === 'en' ? release.actionEn : release.actionCn; } link.append(meta, title, action); rail.append(link); document.body.dataset.v284ReleasePreview = 'true'; document.body.dataset.homeReleaseEnabled = 'true'; hero.append(rail); syncLanguage(); new MutationObserver(syncLanguage).observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] }); })();