// Shared UI primitives, data, and helpers for Artifex De Color site

// ---------- i18n ----------
// Language is persisted in localStorage ("artifex-lang", "ru" | "en") and
// kept in sync with document.documentElement.lang. Switching the language
// re-runs refreshArtifexData() so EN defaults replace RU data without
// touching the CMS-overridable RU content.

const LANG_STORAGE_KEY = "artifex-lang";
const SUPPORTED_LANGS = ["ru", "en"];

function _readLang() {
  try {
    const v = localStorage.getItem(LANG_STORAGE_KEY);
    if (SUPPORTED_LANGS.includes(v)) return v;
  } catch {}
  return "ru";
}

let CURRENT_LANG = _readLang();

function getLang() {
  return CURRENT_LANG;
}

function setLang(lang) {
  if (!SUPPORTED_LANGS.includes(lang) || lang === CURRENT_LANG) return;
  CURRENT_LANG = lang;
  try { localStorage.setItem(LANG_STORAGE_KEY, lang); } catch {}
  try { document.documentElement.lang = lang; } catch {}
  // Reload data under the new language and notify the app so it re-renders.
  if (typeof refreshArtifexData === "function") refreshArtifexData();
  window.dispatchEvent(new Event("artifex:lang"));
  window.dispatchEvent(new Event("artifex:data"));
}

function useLang() {
  const [lang, setLangState] = React.useState(getLang);
  React.useEffect(() => {
    const onChange = () => setLangState(getLang());
    window.addEventListener("artifex:lang", onChange);
    return () => window.removeEventListener("artifex:lang", onChange);
  }, []);
  return lang;
}

// UI string dictionary. Keys are namespaced (e.g. "nav.home"). Keep values
// short — longer marketing copy lives in DEFAULT_TEXTS / DEFAULT_TEXTS_EN.
const UI_STRINGS = {
  ru: {
    "nav.home": "Главная",
    "nav.catalog": "Каталог",
    "nav.production": "Производство",
    "nav.advantages": "Преимущества",
    "nav.partnership": "Партнёрство",
    "nav.applications": "Применение",
    "nav.objects": "Объекты",
    "nav.insights": "Инсайты",
    "nav.about": "О нас",
    "nav.contacts": "Контакты",
    "nav.menu": "Меню",
    "nav.switchLang": "Переключить язык",
    "footer.navigation": "Навигация",
    "footer.products": "Продукты",
    "footer.contacts": "Контакты",
    "footer.aboutCompany": "О компании",
    "footer.developedBy": "Разработано в",

    "notFound.title": "Страница не найдена",
    "notFound.home": "На главную →",
    "notFound.product.title": "Продукт не найден",
    "notFound.product.back": "В каталог →",

    "home.fullCatalog": "Весь каталог →",
    "home.allObjects": "Все объекты →",
    "home.contactCta": "Связаться →",
    "home.numbersEyebrow": "Компания",
    "home.numbersTitle": "Artifex De Color в цифрах",
    "home.moreAbout": "О компании →",
    "home.advantagesMore": "Подробнее о преимуществах →",
    "home.partnerEyebrow": "Партнёрство",
    "home.partnerTitle": "Станьте нашим партнёром",
    "home.partnerDesc": "Дилеры, дистрибьюторы, девелоперы. Долгосрочные условия, Private Label, территориальная защита и экспортная поддержка.",
    "home.partnerBtn": "Условия партнёрства →",
    "home.partnerContact": "Оставить заявку",
    "home.insightsEyebrow": "Журнал",
    "home.insightsTitle": "Последние инсайты",
    "home.insightsAll": "Все инсайты →",

    "insights.title": "Инсайты",
    "insights.lede": "Проекты, материалы и идеи о современных декоративных покрытиях",
    "insights.cat.all": "Все",
    "insights.cat.projects": "Проекты",
    "insights.cat.materials": "Материалы",
    "insights.cat.news": "Новости",
    "insights.cat.inspiration": "Вдохновение",
    "insights.readingTime": "мин чтения",
    "insights.empty": "Пока в этой категории нет публикаций. Загляните позже или посмотрите другие разделы.",
    "insights.showMore": "Показать ещё",
    "insights.crumbs": "Инсайты",
    "insights.related": "Читайте также",
    "insights.author": "Автор",
    "insights.cta.projects.title": "Хотите реализовать похожий проект?",
    "insights.cta.projects.btn": "Связаться",
    "insights.cta.materials.title": "Нужна консультация по материалам?",
    "insights.cta.materials.btn": "Техподдержка",
    "insights.cta.news.title": "Следите за нами",
    "insights.cta.news.btn": "В соцсетях",
    "insights.cta.inspiration.title": "Посмотрите наши покрытия",
    "insights.cta.inspiration.btn": "Каталог",
    "insights.notFound.title": "Публикация не найдена",
    "insights.notFound.back": "Все инсайты →",
    "insights.back": "К инсайтам",
    "insights.partnerLabel": "Проект",

    "catalog.countSuffix": "фактур",
    "catalog.filter.app": "Применение",
    "catalog.filter.all": "Все",
    "catalog.filter.interior": "Интерьер",
    "catalog.filter.exterior": "Фасад",
    "catalog.filter.base": "Основа",
    "catalog.filter.baseAny": "Любая",
    "catalog.filter.baseMineral": "Минеральная",
    "catalog.filter.baseAcrylic": "Акрил",
    "catalog.filter.effect": "Эффект",
    "catalog.sort": "Сортировка",
    "catalog.sort.default": "По умолчанию",
    "catalog.sort.name": "По названию (А–Я)",
    "catalog.sort.base": "По основе",
    "catalog.search.placeholder": "Поиск по эффекту или названию",
    "catalog.search.clear": "Очистить",
    "catalog.results.shown": "Показано",
    "catalog.results.of": "из",
    "catalog.results.total": "фактур",
    "catalog.results.reset": "Сбросить фильтры",
    "catalog.empty.title": "Ничего не найдено",
    "catalog.empty.body": "По выбранным фильтрам нет подходящих фактур. Попробуйте изменить параметры поиска.",
    "catalog.empty.reset": "Сбросить фильтры →",
    "catalog.tag.mineral": "Минеральная",
    "catalog.tag.acrylic": "Акрил",
    "catalog.view.grid": "Сетка",
    "catalog.view.list": "Список",

    "product.crumbs.home": "Главная",
    "product.crumbs.catalog": "Каталог",
    "product.calc.eyebrow": "Калькулятор расхода",
    "product.calc.area": "Площадь",
    "product.calc.resultLabel": "Потребуется материала",
    "product.calc.resultHint": "Точный расход зависит от состояния основания и техники нанесения",
    "product.calc.empty": "Введите площадь для расчёта",
    "product.cta.add": "+ В заявку",
    "product.cta.inQuote": "✓ В заявке",
    "product.cta.contact": "Связаться",
    "product.cta.share": "↗ Поделиться",
    "product.cta.copied": "✓ Скопировано",
    "product.cta.shareAria": "Поделиться",
    "product.specs.eyebrow": "Технические характеристики",
    "product.specs.binder": "Основа",
    "product.specs.consumption": "Расход",
    "product.specs.drying": "Высыхание",
    "product.specs.layers": "Слои",
    "product.specs.tools": "Инструмент",
    "product.specs.application": "Применение",
    "product.application.both": "Интерьер и фасад",
    "product.application.interior": "Интерьер",
    "product.application.exterior": "Фасад",
    "product.safety.eyebrow": "Безопасность и условия",
    "product.safety.eco.title": "Экологичная формула",
    "product.safety.eco.desc": "Без вредных летучих соединений, класс эмиссии A+.",
    "product.safety.fire.title": "Пожарная безопасность",
    "product.safety.fire.desc": "Соответствует нормам КМ1 для интерьерного применения.",
    "product.safety.temp.title": "Температурный режим",
    "product.safety.temp.desc": "Нанесение: +10°…+30°C. Эксплуатация: −40°…+60°C.",
    "product.safety.moisture.title": "Влагостойкость",
    "product.safety.moisture.desc": "После полной полимеризации поверхность устойчива к влаге.",
    "product.detail.fragment": "Фрагмент фактуры, свет 45°",
    "product.detail.closeup": "Крупный план",
    "product.detail.interior": "В интерьере",
    "product.gallery.eyebrow": "Галерея",
    "product.gallery.title": "Примеры текстур",
    "product.gallery.open": "Открыть фото",
    "product.gallery.close": "Закрыть",
    "product.gallery.prev": "Предыдущее фото",
    "product.gallery.next": "Следующее фото",
    "product.nav.prev": "← Предыдущий",
    "product.nav.next": "Следующий →",
    "product.related": "Похожие фактуры",
    "product.badge.mineralAbbr": "МИН",
    "product.badge.acrylicAbbr": "АКР",
    "product.badge.mineralFull": "Минеральная основа",
    "product.badge.acrylicFull": "Акриловая основа",

    "apps.matrix.eyebrow": "Матрица подбора",
    "apps.matrix.title": "Продукт под задачу",
    "apps.matrix.col.name": "Фактура",
    "apps.matrix.col.interior": "Интерьер",
    "apps.matrix.col.exterior": "Фасад",
    "apps.matrix.col.base": "Основа",
    "apps.matrix.col.effect": "Эффект",
    "apps.matrix.mineralShort": "Мин.",
    "apps.matrix.acrylicShort": "Акр.",
    "apps.recommended": "Рекомендуем:",

    "objects.filter.type": "Тип",
    "objects.filter.year": "Год",
    "objects.filter.all": "Все",
    "objects.tile.textures": "Фактуры:",
    "objects.tile.type": "Тип",
    "objects.tile.year": "Год",
    "objects.tile.location": "Локация",
    "objects.back": "К объектам",
    "objects.galleryEyebrow": "Галерея",
    "objects.galleryTitle": "Фотографии объекта",
    "objects.ctaTitle": "Ваш объект — следующий",
    "objects.ctaDesc": "Поможем подобрать фактуру под вашу задачу — от частных интерьеров до общественных пространств.",
    "objects.ctaBtn": "Связаться с нами",
    "objects.empty.title": "Под выбранные фильтры ничего не нашлось",
    "objects.empty.reset": "Сбросить",

    "contacts.reach.call": "Позвонить",
    "contacts.reach.write": "Написать",
    "contacts.reach.telegram": "Telegram",
    "contacts.reach.telegramDesc": "Чат поддержки",
    "contacts.reach.whatsapp": "WhatsApp",
    "contacts.reach.whatsappDesc": "Быстрый ответ",
    "contacts.info.address": "Адрес",
    "contacts.info.hours": "Время работы",
    "contacts.form.eyebrow": "Оставить заявку",
    "form.name": "Имя *",
    "form.email": "E-mail *",
    "form.phone": "Телефон *",
    "form.phoneOpt": "Телефон",
    "form.emailOpt": "E-mail",
    "form.projectType": "Тип объекта",
    "form.projectType.residential": "Жилой интерьер",
    "form.projectType.facade": "Фасад",
    "form.projectType.horeca": "HoReCa",
    "form.projectType.office": "Офис",
    "form.projectType.retail": "Ретейл",
    "form.projectType.public": "Общественный",
    "form.projectType.other": "Другое",
    "form.area": "Площадь, м²",
    "form.areaPlaceholder": "примерно",
    "form.message": "О проекте",
    "form.messagePlaceholder": "Интересующие фактуры, особенности объекта",
    "form.commentLabel": "Комментарий",
    "form.commentPlaceholder": "Особенности объекта, пожелания",
    "form.errContacts": "Заполните имя, телефон и e-mail",
    "form.errNamePhone": "Заполните имя и телефон",
    "form.errPhone": "Введите корректный номер телефона",
    "form.errEmail": "Введите корректный email",
    "form.errRateLimited": "Слишком много заявок подряд — подождите немного.",
    "form.errSend": "Не удалось отправить. Позвоните нам по телефону выше.",
    "form.errSendRetry": "Не удалось отправить. Попробуйте ещё раз или позвоните нам.",
    "form.sending": "Отправляем...",
    "form.submit": "Отправить →",
    "form.submitQuote": "Отправить заявку →",
    "form.success.title": "Заявка отправлена",
    "form.success.body": "Мы свяжемся с вами в ближайшее время.",
    "form.success.again": "Отправить ещё",
    "contacts.map.title": "Карта проезда",

    "quote.eyebrow": "Заявка",
    "quote.title": "Оформление",
    "quote.titleItalic": "заявки",
    "quote.lede": "Укажите интересующие продукты и площадь — мы рассчитаем точный расход, подготовим образцы и коммерческое предложение.",
    "quote.items.title": "Выбранные продукты",
    "quote.items.clear": "Очистить список",
    "quote.items.empty": "Список пуст.",
    "quote.items.openCatalog": "Открыть каталог →",
    "quote.item.area": "Площадь, м²",
    "quote.item.calcLabel": "Расчётный расход:",
    "quote.item.calcEmpty": "Укажите площадь для расчёта",
    "quote.total": "Суммарный расход",
    "quote.contactData": "Контактные данные",
    "quote.timeline": "Сроки",
    "quote.timeline.asap": "Срочно",
    "quote.timeline.week": "В течение недели",
    "quote.timeline.month": "В течение месяца",
    "quote.timeline.quarter": "В течение квартала",
    "quote.timeline.planning": "Только планирую",
    "quote.totalArea": "Общая площадь, м²",
    "quote.totalAreaPlaceholder": "если отличается от суммы выше",
    "quote.note": "Нажимая кнопку, вы соглашаетесь с политикой обработки данных.",
    "quote.addHint1": "Добавьте хотя бы один продукт из",
    "quote.addHint2": "каталога",
    "quote.success.title": "Заявка отправлена",
    "quote.success.body1": "Мы свяжемся с вами в течение часа в рабочее время.",
    "quote.success.body2": "Обычно — быстрее.",
    "quote.success.continue": "Продолжить выбор →",
    "quote.success.home": "На главную",
    "quote.fab.label": "Заявка",
    "quote.fab.ariaOpen": "Открыть заявку",
    "quote.toggle.add": "Добавить в заявку",
    "quote.toggle.remove": "Убрать из заявки",
    "quote.toggle.inQuote": "В заявке",
    "quote.toggle.toAdd": "В заявку",

    "remove": "Убрать",
  },
  en: {
    "nav.home": "Home",
    "nav.catalog": "Catalog",
    "nav.production": "Production",
    "nav.advantages": "Advantages",
    "nav.partnership": "Partnership",
    "nav.applications": "Applications",
    "nav.objects": "Projects",
    "nav.insights": "Insights",
    "nav.about": "About",
    "nav.contacts": "Contacts",
    "nav.menu": "Menu",
    "nav.switchLang": "Switch language",
    "footer.navigation": "Navigation",
    "footer.products": "Products",
    "footer.contacts": "Contacts",
    "footer.aboutCompany": "About the company",
    "footer.developedBy": "Crafted by",

    "notFound.title": "Page not found",
    "notFound.home": "Back to home →",
    "notFound.product.title": "Product not found",
    "notFound.product.back": "Back to catalog →",

    "home.fullCatalog": "Full catalog →",
    "home.allObjects": "All projects →",
    "home.contactCta": "Get in touch →",
    "home.numbersEyebrow": "Company",
    "home.numbersTitle": "Artifex De Color in numbers",
    "home.moreAbout": "About the company →",
    "home.advantagesMore": "More on our advantages →",
    "home.partnerEyebrow": "Partnership",
    "home.partnerTitle": "Partner with us",
    "home.partnerDesc": "Dealers, distributors, developers. Long-term terms, Private Label, territory protection and export support.",
    "home.partnerBtn": "Partnership terms →",
    "home.partnerContact": "Send request",
    "home.insightsEyebrow": "Journal",
    "home.insightsTitle": "Latest insights",
    "home.insightsAll": "All insights →",

    "insights.title": "Insights",
    "insights.lede": "Projects, materials and ideas on contemporary decorative finishes",
    "insights.cat.all": "All",
    "insights.cat.projects": "Projects",
    "insights.cat.materials": "Materials",
    "insights.cat.news": "News",
    "insights.cat.inspiration": "Inspiration",
    "insights.readingTime": "min read",
    "insights.empty": "Nothing in this category yet. Check back later or browse other sections.",
    "insights.showMore": "Show more",
    "insights.crumbs": "Insights",
    "insights.related": "Read next",
    "insights.author": "Author",
    "insights.cta.projects.title": "Looking to deliver a similar project?",
    "insights.cta.projects.btn": "Get in touch",
    "insights.cta.materials.title": "Need a materials consultation?",
    "insights.cta.materials.btn": "Tech support",
    "insights.cta.news.title": "Follow our updates",
    "insights.cta.news.btn": "On social",
    "insights.cta.inspiration.title": "Browse our finishes",
    "insights.cta.inspiration.btn": "Catalog",
    "insights.notFound.title": "Publication not found",
    "insights.notFound.back": "All insights →",
    "insights.back": "Back to insights",
    "insights.partnerLabel": "Project",

    "catalog.countSuffix": "finishes",
    "catalog.filter.app": "Application",
    "catalog.filter.all": "All",
    "catalog.filter.interior": "Interior",
    "catalog.filter.exterior": "Facade",
    "catalog.filter.base": "Base",
    "catalog.filter.baseAny": "Any",
    "catalog.filter.baseMineral": "Mineral",
    "catalog.filter.baseAcrylic": "Acrylic",
    "catalog.filter.effect": "Effect",
    "catalog.sort": "Sort",
    "catalog.sort.default": "Default",
    "catalog.sort.name": "By name (A–Z)",
    "catalog.sort.base": "By base",
    "catalog.search.placeholder": "Search by effect or name",
    "catalog.search.clear": "Clear",
    "catalog.results.shown": "Showing",
    "catalog.results.of": "of",
    "catalog.results.total": "finishes",
    "catalog.results.reset": "Reset filters",
    "catalog.empty.title": "Nothing found",
    "catalog.empty.body": "No finishes match the selected filters. Try adjusting your search.",
    "catalog.empty.reset": "Reset filters →",
    "catalog.tag.mineral": "Mineral",
    "catalog.tag.acrylic": "Acrylic",
    "catalog.view.grid": "Grid",
    "catalog.view.list": "List",

    "product.crumbs.home": "Home",
    "product.crumbs.catalog": "Catalog",
    "product.calc.eyebrow": "Consumption calculator",
    "product.calc.area": "Area",
    "product.calc.resultLabel": "Material required",
    "product.calc.resultHint": "Actual consumption depends on substrate condition and application technique",
    "product.calc.empty": "Enter area to calculate",
    "product.cta.add": "+ Add to quote",
    "product.cta.inQuote": "✓ In quote",
    "product.cta.contact": "Contact us",
    "product.cta.share": "↗ Share",
    "product.cta.copied": "✓ Copied",
    "product.cta.shareAria": "Share",
    "product.specs.eyebrow": "Technical specifications",
    "product.specs.binder": "Binder",
    "product.specs.consumption": "Consumption",
    "product.specs.drying": "Drying",
    "product.specs.layers": "Coats",
    "product.specs.tools": "Tools",
    "product.specs.application": "Application",
    "product.application.both": "Interior and facade",
    "product.application.interior": "Interior",
    "product.application.exterior": "Facade",
    "product.safety.eyebrow": "Safety and conditions",
    "product.safety.eco.title": "Eco-friendly formula",
    "product.safety.eco.desc": "Free of harmful volatile compounds. Class A+ emissions.",
    "product.safety.fire.title": "Fire safety",
    "product.safety.fire.desc": "Complies with KM1 standards for interior use.",
    "product.safety.temp.title": "Temperature range",
    "product.safety.temp.desc": "Application: +10°…+30°C. Operation: −40°…+60°C.",
    "product.safety.moisture.title": "Moisture resistance",
    "product.safety.moisture.desc": "Once fully cured, the surface is moisture-resistant.",
    "product.detail.fragment": "Texture fragment, 45° lighting",
    "product.detail.closeup": "Close-up",
    "product.detail.interior": "In an interior",
    "product.gallery.eyebrow": "Gallery",
    "product.gallery.title": "Texture examples",
    "product.gallery.open": "Open photo",
    "product.gallery.close": "Close",
    "product.gallery.prev": "Previous photo",
    "product.gallery.next": "Next photo",
    "product.nav.prev": "← Previous",
    "product.nav.next": "Next →",
    "product.related": "Related textures",
    "product.badge.mineralAbbr": "MIN",
    "product.badge.acrylicAbbr": "ACR",
    "product.badge.mineralFull": "Mineral base",
    "product.badge.acrylicFull": "Acrylic base",

    "apps.matrix.eyebrow": "Selection matrix",
    "apps.matrix.title": "Product for the task",
    "apps.matrix.col.name": "Texture",
    "apps.matrix.col.interior": "Interior",
    "apps.matrix.col.exterior": "Facade",
    "apps.matrix.col.base": "Base",
    "apps.matrix.col.effect": "Effect",
    "apps.matrix.mineralShort": "Min.",
    "apps.matrix.acrylicShort": "Acr.",
    "apps.recommended": "Recommended:",

    "objects.filter.type": "Type",
    "objects.filter.year": "Year",
    "objects.filter.all": "All",
    "objects.tile.textures": "Textures:",
    "objects.tile.type": "Type",
    "objects.tile.year": "Year",
    "objects.tile.location": "Location",
    "objects.back": "Back to projects",
    "objects.galleryEyebrow": "Gallery",
    "objects.galleryTitle": "Project photos",
    "objects.ctaTitle": "Your project is next",
    "objects.ctaDesc": "We'll help you pick a finish for your task — from private interiors to public spaces.",
    "objects.ctaBtn": "Get in touch",
    "objects.empty.title": "Nothing matches the selected filters",
    "objects.empty.reset": "Reset",

    "contacts.reach.call": "Call",
    "contacts.reach.write": "Email",
    "contacts.reach.telegram": "Telegram",
    "contacts.reach.telegramDesc": "Support chat",
    "contacts.reach.whatsapp": "WhatsApp",
    "contacts.reach.whatsappDesc": "Quick reply",
    "contacts.info.address": "Address",
    "contacts.info.hours": "Business hours",
    "contacts.form.eyebrow": "Submit a request",
    "form.name": "Name *",
    "form.email": "Email *",
    "form.phone": "Phone *",
    "form.phoneOpt": "Phone",
    "form.emailOpt": "Email",
    "form.projectType": "Project type",
    "form.projectType.residential": "Residential interior",
    "form.projectType.facade": "Facade",
    "form.projectType.horeca": "HoReCa",
    "form.projectType.office": "Office",
    "form.projectType.retail": "Retail",
    "form.projectType.public": "Public",
    "form.projectType.other": "Other",
    "form.area": "Area, m²",
    "form.areaPlaceholder": "approximate",
    "form.message": "About the project",
    "form.messagePlaceholder": "Textures of interest, project specifics",
    "form.commentLabel": "Comment",
    "form.commentPlaceholder": "Project specifics, preferences",
    "form.errContacts": "Please fill in name, phone, and email",
    "form.errNamePhone": "Please fill in name and phone",
    "form.errPhone": "Enter a valid phone number",
    "form.errEmail": "Enter a valid email",
    "form.errRateLimited": "Too many submissions in a row — please wait a moment.",
    "form.errSend": "Could not send. Please call us at the number above.",
    "form.errSendRetry": "Could not send. Please try again or call us.",
    "form.sending": "Sending...",
    "form.submit": "Send →",
    "form.submitQuote": "Submit request →",
    "form.success.title": "Request submitted",
    "form.success.body": "We'll get in touch shortly.",
    "form.success.again": "Send another",
    "contacts.map.title": "Directions map",

    "quote.eyebrow": "Request",
    "quote.title": "Submit",
    "quote.titleItalic": "a request",
    "quote.lede": "Select the products and areas of interest — we'll calculate precise consumption, prepare samples, and a commercial offer.",
    "quote.items.title": "Selected products",
    "quote.items.clear": "Clear list",
    "quote.items.empty": "The list is empty.",
    "quote.items.openCatalog": "Open catalog →",
    "quote.item.area": "Area, m²",
    "quote.item.calcLabel": "Estimated consumption:",
    "quote.item.calcEmpty": "Enter area to calculate",
    "quote.total": "Total consumption",
    "quote.contactData": "Contact details",
    "quote.timeline": "Timeline",
    "quote.timeline.asap": "ASAP",
    "quote.timeline.week": "Within a week",
    "quote.timeline.month": "Within a month",
    "quote.timeline.quarter": "Within a quarter",
    "quote.timeline.planning": "Just planning",
    "quote.totalArea": "Total area, m²",
    "quote.totalAreaPlaceholder": "if different from the sum above",
    "quote.note": "By submitting this form you agree to our privacy policy.",
    "quote.addHint1": "Add at least one product from the",
    "quote.addHint2": "catalog",
    "quote.success.title": "Request submitted",
    "quote.success.body1": "We'll contact you within an hour during business hours.",
    "quote.success.body2": "Usually faster.",
    "quote.success.continue": "Continue browsing →",
    "quote.success.home": "Back to home",
    "quote.fab.label": "Request",
    "quote.fab.ariaOpen": "Open request",
    "quote.toggle.add": "Add to quote",
    "quote.toggle.remove": "Remove from quote",
    "quote.toggle.inQuote": "In quote",
    "quote.toggle.toAdd": "To quote",

    "remove": "Remove",
  },
};

function t(key) {
  const lang = getLang();
  const table = UI_STRINGS[lang] || UI_STRINGS.ru;
  if (key in table) return table[key];
  // Fallback: RU value, then the raw key — so missing translations are
  // visible but never crash the UI.
  if (key in UI_STRINGS.ru) return UI_STRINGS.ru[key];
  return key;
}
// Decorative swatch generator — CSS-only textured placeholders per product
function Swatch({ product, size = "md", className = "" }) {
  const [c1, c2, c3, c4] = product.palette;
  const styles = {
    base: {
      position: "relative",
      overflow: "hidden",
      background: c1,
      borderRadius: size === "sm" ? 12 : 18,
    }
  };

  const variants = {
    ikat: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(ellipse 60% 20% at 30% 30%, ${c2} 0%, transparent 60%),
          radial-gradient(ellipse 80% 15% at 70% 70%, ${c4} 0%, transparent 55%),
          radial-gradient(ellipse 40% 30% at 50% 50%, ${c3}80 0%, transparent 70%),
          linear-gradient(115deg, ${c1} 0%, ${c2} 50%, ${c1} 100%)
        `,
        mixBlendMode: "normal",
      }}/>
    ),
    registon: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(circle at 20% 80%, ${c2} 0%, transparent 30%),
          radial-gradient(circle at 80% 20%, ${c4} 0%, transparent 35%),
          repeating-radial-gradient(circle at 50% 50%, ${c1} 0 2px, ${c3}20 2px 4px),
          linear-gradient(160deg, ${c1}, ${c2})
        `
      }}/>
    ),
    eski: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(ellipse at 30% 40%, ${c4} 0%, transparent 50%),
          radial-gradient(ellipse at 70% 60%, ${c2} 0%, transparent 45%),
          linear-gradient(45deg, ${c3} 0%, ${c1} 50%, ${c2} 100%)
        `
      }}/>
    ),
    afro: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(circle at 30% 30%, ${c2} 0%, transparent 40%),
          radial-gradient(circle at 70% 70%, ${c4} 0%, transparent 40%),
          conic-gradient(from 45deg at 50% 50%, ${c1}, ${c3}, ${c1}, ${c2}, ${c1})
        `
      }}/>
    ),
    riviera: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(circle at 15% 25%, ${c3} 0 3px, transparent 4px),
          radial-gradient(circle at 75% 65%, ${c3} 0 2px, transparent 3px),
          radial-gradient(circle at 45% 85%, ${c3} 0 2px, transparent 3px),
          radial-gradient(circle at 85% 15%, ${c3} 0 2px, transparent 3px),
          linear-gradient(135deg, ${c1}, ${c4})
        `
      }}/>
    ),
    beqasam: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `repeating-linear-gradient(90deg, ${c1} 0 18px, ${c2} 18px 22px, ${c3} 22px 28px, ${c4} 28px 32px, ${c1} 32px 50px)`
      }}/>
    ),
    velvetto: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(ellipse at 30% 30%, ${c2} 0%, transparent 50%),
          radial-gradient(ellipse at 80% 80%, ${c4}60 0%, transparent 60%),
          linear-gradient(135deg, ${c3} 0%, ${c1} 100%)
        `
      }}/>
    ),
    cristaria: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          linear-gradient(30deg, ${c1} 0 40%, transparent 40%),
          linear-gradient(-30deg, ${c4} 0 40%, transparent 40%),
          linear-gradient(60deg, ${c2} 0 50%, ${c3} 50%)
        `,
        backgroundSize: "40px 40px, 40px 40px, 100% 100%"
      }}/>
    ),
    mohi: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(circle at 50% 50%, ${c4} 0%, transparent 50%),
          conic-gradient(from 0deg at 50% 50%, ${c1}, ${c2}, ${c3}, ${c1})
        `
      }}/>
    ),
    sogdiana: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(ellipse at 20% 30%, ${c4} 0%, transparent 40%),
          radial-gradient(ellipse at 80% 70%, ${c2} 0%, transparent 40%),
          linear-gradient(160deg, ${c1} 0%, ${c3} 100%)
        `
      }}/>
    ),
    travertino: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(circle at 20% 30%, ${c3}60 0 3px, transparent 4px),
          radial-gradient(circle at 60% 20%, ${c3}40 0 2px, transparent 3px),
          radial-gradient(circle at 40% 70%, ${c3}50 0 3px, transparent 4px),
          radial-gradient(circle at 80% 80%, ${c3}30 0 2px, transparent 3px),
          repeating-linear-gradient(90deg, ${c1}, ${c2} 40px, ${c1} 80px)
        `
      }}/>
    ),
    pakhta: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(circle at 30% 40%, ${c4} 0%, transparent 35%),
          radial-gradient(circle at 70% 60%, ${c2} 0%, transparent 35%),
          radial-gradient(circle at 50% 80%, ${c1} 0%, transparent 40%),
          linear-gradient(180deg, ${c4} 0%, ${c1} 100%)
        `
      }}/>
    ),
    multicolor: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(circle at 22% 28%, ${c2} 0 8px, transparent 9px),
          radial-gradient(circle at 58% 60%, ${c3} 0 7px, transparent 8px),
          radial-gradient(circle at 82% 32%, ${c4} 0 10px, transparent 11px),
          radial-gradient(circle at 38% 78%, ${c3} 0 8px, transparent 9px),
          radial-gradient(circle at 72% 85%, ${c2} 0 6px, transparent 7px),
          radial-gradient(circle at 12% 70%, ${c4} 0 6px, transparent 7px),
          linear-gradient(135deg, ${c1} 0%, ${c4} 100%)
        `
      }}/>
    ),
    primer: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          radial-gradient(ellipse 70% 50% at 50% 50%, ${c4}40 0%, transparent 70%),
          linear-gradient(180deg, ${c1} 0%, ${c2} 100%)
        `
      }}/>
    ),
    emulsion: (
      <div style={{
        position: "absolute", inset: 0,
        backgroundImage: `
          repeating-linear-gradient(95deg, ${c1} 0 28px, ${c2}40 28px 30px, ${c1} 30px 60px),
          linear-gradient(180deg, ${c3} 0%, ${c4}30 100%)
        `
      }}/>
    )
  };

  return (
    <div className={`swatch ${className}`} style={styles.base}>
      {variants[product.swatch]}
      {/* noise overlay */}
      <div style={{
        position: "absolute", inset: 0, opacity: 0.35, mixBlendMode: "overlay",
        backgroundImage: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence baseFrequency='0.9' numOctaves='2' seed='3'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.6 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>")`
      }}/>
      {/* soft vignette */}
      <div style={{
        position: "absolute", inset: 0,
        background: "radial-gradient(ellipse at center, transparent 40%, rgba(0,0,0,0.25) 100%)"
      }}/>
    </div>
  );
}

// Photo with graceful fallback to a CSS-textured Swatch if the image fails
function PhotoOrSwatch({ product, photo, alt, className = "" }) {
  const [failed, setFailed] = React.useState(false);
  if (!photo || failed) return <Swatch product={product} className={className}/>;
  return (
    <img
      className={`photo ${className}`}
      src={photo}
      alt={alt || product.name}
      loading="lazy"
      onError={() => setFailed(true)}
    />
  );
}

// Card visual: show product.cover (uploaded via Decap) if present,
// otherwise fall back to the CSS-generated Swatch. Used on home +
// catalog + related tiles so editor covers replace swatches everywhere.
function ProductCoverOrSwatch({ product, className = "" }) {
  const [failed, setFailed] = React.useState(false);
  if (product.cover && !failed) {
    return (
      <img
        className={`product-cover ${className}`}
        src={product.cover}
        alt={product.name}
        loading="lazy"
        onError={() => setFailed(true)}
      />
    );
  }
  return <Swatch product={product} className={className}/>;
}

// Simple hash router
function useRoute() {
  const [route, setRoute] = React.useState(() => window.location.hash.slice(1) || "/");
  React.useEffect(() => {
    const onHash = () => {
      setRoute(window.location.hash.slice(1) || "/");
      window.scrollTo({ top: 0, behavior: "instant" });
    };
    window.addEventListener("hashchange", onHash);
    return () => window.removeEventListener("hashchange", onHash);
  }, []);
  return route;
}

function Link({ to, children, className, style, onClick }) {
  return (
    <a href={`#${to}`} className={className} style={style} onClick={onClick}>
      {children}
    </a>
  );
}

function Logo({ variant = "default", className = "" }) {
  const [failed, setFailed] = React.useState(false);
  React.useEffect(() => { setFailed(false); }, [COMPANY.logo, COMPANY.logoLight]);
  const src = variant === "light" && COMPANY.logoLight ? COMPANY.logoLight : COMPANY.logo;
  if (src && !failed) {
    return <img
      src={src}
      alt="Artifex De Color"
      className={`logo-img ${className}`}
      onError={() => setFailed(true)}
    />;
  }
  return (
    <span className={`logo-fallback ${className}`}>
      <span className="logo-mark">
        <svg viewBox="0 0 40 40" width="32" height="32">
          <defs>
            <radialGradient id="gold" cx="35%" cy="30%">
              <stop offset="0%" stopColor="#f7e8c2"/>
              <stop offset="40%" stopColor="#d4a558"/>
              <stop offset="100%" stopColor="#6b4a1e"/>
            </radialGradient>
          </defs>
          <circle cx="20" cy="20" r="18" fill="url(#gold)"/>
          <circle cx="13" cy="14" r="3" fill="#1a1612"/>
          <circle cx="26" cy="12" r="2.5" fill="#1a1612"/>
          <circle cx="28" cy="23" r="3" fill="#1a1612"/>
          <circle cx="15" cy="26" r="2.5" fill="#1a1612"/>
          <circle cx="22" cy="20" r="2" fill="#1a1612"/>
        </svg>
      </span>
      <span className="logo-text">
        <span className="logo-a">ARTIFEX</span>
        <span className="logo-b">DE COLOR</span>
      </span>
    </span>
  );
}

function NavSocial({ mobile = false }) {
  const s = COMPANY.social || {};
  const items = [
    s.telegram && {
      href: s.telegram, label: "Telegram",
      icon: (
        <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
          <path fill="currentColor" d="M9.04 15.38 8.84 18.6c.29 0 .41-.12.56-.27l1.34-1.27 2.78 2.02c.51.28.88.14 1.01-.47l1.84-8.63c.17-.77-.28-1.08-.78-.9L4.27 12.3c-.75.29-.74.71-.13.9l2.72.84 6.31-3.97c.3-.19.57-.09.35.13z"/>
        </svg>
      )
    },
    s.instagram && {
      href: s.instagram, label: "Instagram",
      icon: (
        <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true" fill="none" stroke="currentColor" strokeWidth="1.8">
          <rect x="3" y="3" width="18" height="18" rx="5"/>
          <circle cx="12" cy="12" r="4"/>
          <circle cx="17.5" cy="6.5" r="1" fill="currentColor" stroke="none"/>
        </svg>
      )
    },
    s.whatsapp && {
      href: s.whatsapp, label: "WhatsApp",
      icon: (
        <svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
          <path fill="currentColor" d="M20.5 3.5A10.4 10.4 0 0 0 3.5 16l-1.4 5.2 5.3-1.4a10.4 10.4 0 0 0 13.1-16.3zM12 20.1a8.2 8.2 0 0 1-4.2-1.2l-.3-.2-3.2.8.9-3.1-.2-.3A8.2 8.2 0 1 1 12 20.1zm4.5-6.1c-.2-.1-1.4-.7-1.6-.8-.2-.1-.4-.1-.6.1s-.7.8-.8 1c-.1.2-.3.2-.5.1-.7-.3-1.3-.7-2-1.3-.5-.5-.9-1-1.3-1.7-.1-.2 0-.4.1-.5.1-.1.2-.3.3-.4.1-.1.2-.2.2-.4.1-.2 0-.3 0-.5 0-.1-.6-1.4-.8-1.9-.2-.5-.4-.4-.6-.4H8c-.2 0-.5.1-.7.3-.2.3-.9.9-.9 2.2 0 1.3.9 2.6 1.1 2.7.1.2 1.8 2.8 4.4 3.9 2.2.9 2.6.7 3.1.7.5 0 1.5-.6 1.7-1.2.2-.6.2-1.1.2-1.2-.1-.1-.2-.2-.4-.3z"/>
        </svg>
      )
    }
  ].filter(Boolean);
  if (!items.length) return null;
  return (
    <div className={`nav-social ${mobile ? "nav-social-mobile" : ""}`} role="group" aria-label="Социальные сети">
      {items.map(it => (
        <a key={it.label} href={it.href} target="_blank" rel="noopener noreferrer"
           className="nav-social-link" aria-label={it.label}>
          {it.icon}
        </a>
      ))}
    </div>
  );
}

function Nav({ current }) {
  const [menuOpen, setMenuOpen] = React.useState(false);
  const lang = useLang();
  const items = [
    { to: "/", label: t("nav.home") },
    { to: "/catalog", label: t("nav.catalog") },
    { to: "/applications", label: t("nav.applications") },
    { to: "/production", label: t("nav.production") },
    { to: "/objects", label: t("nav.objects") },
    { to: "/insights", label: t("nav.insights") },
    { to: "/partnership", label: t("nav.partnership") },
    { to: "/about", label: t("nav.about") },
    { to: "/contacts", label: t("nav.contacts") },
  ];
  const isActive = (to) => {
    if (to === "/") return current === "/";
    return current.startsWith(to);
  };
  React.useEffect(() => { setMenuOpen(false); }, [current]);
  React.useEffect(() => {
    if (typeof document === "undefined") return;
    document.body.classList.toggle("nav-open", menuOpen);
    const onKey = (e) => { if (e.key === "Escape") setMenuOpen(false); };
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("keydown", onKey);
      document.body.classList.remove("nav-open");
    };
  }, [menuOpen]);
  const pickLang = (l) => (e) => { e.preventDefault(); setLang(l); };
  return (
    <header className="nav">
      <Link to="/" className="nav-logo" onClick={() => setMenuOpen(false)}>
        <Logo/>
      </Link>
      <nav className={`nav-links ${menuOpen ? "nav-links-open" : ""}`} aria-hidden={menuOpen ? undefined : "true"}>
        {items.map(item => (
          <Link key={item.to} to={item.to} className={`nav-link ${isActive(item.to) ? "active" : ""}`}>
            {item.label}
          </Link>
        ))}
        <div className="nav-mobile-extras">
          <div className="nav-lang nav-lang-mobile" role="group" aria-label={t("nav.switchLang")}>
            <button
              type="button"
              className={`lang-btn ${lang === "ru" ? "lang-active" : ""}`}
              onClick={pickLang("ru")}
              aria-pressed={lang === "ru"}>RU</button>
            <button
              type="button"
              className={`lang-btn ${lang === "en" ? "lang-active" : ""}`}
              onClick={pickLang("en")}
              aria-pressed={lang === "en"}>EN</button>
          </div>
          <NavSocial mobile/>
        </div>
      </nav>
      <div className="nav-right">
        <NavSocial/>
        <div className="nav-lang" role="group" aria-label={t("nav.switchLang")}>
          <button
            type="button"
            className={`lang-btn ${lang === "ru" ? "lang-active" : ""}`}
            onClick={pickLang("ru")}
            aria-pressed={lang === "ru"}>RU</button>
          <button
            type="button"
            className={`lang-btn ${lang === "en" ? "lang-active" : ""}`}
            onClick={pickLang("en")}
            aria-pressed={lang === "en"}>EN</button>
        </div>
        <button
          className={`nav-burger ${menuOpen ? "open" : ""}`}
          onClick={() => setMenuOpen(o => !o)}
          aria-label={t("nav.menu")}
          aria-expanded={menuOpen}>
          <span/><span/><span/>
        </button>
      </div>
      <div
        className={`nav-overlay ${menuOpen ? "is-open" : ""}`}
        onClick={() => setMenuOpen(false)}
        aria-hidden="true"
      />
    </header>
  );
}

function Footer() {
  useLang();
  const addressLines = (COMPANY.addressShort || "").split("\n");
  return (
    <footer className="footer">
      <div className="footer-grid">
        <div>
          <div className="footer-logo">
            <Logo variant="light"/>
          </div>
          <p className="footer-tag">{COMPANY.tagline}</p>
        </div>
        <div>
          <div className="footer-h">{t("footer.navigation")}</div>
          <Link to="/catalog">{t("nav.catalog")}</Link>
          <Link to="/applications">{t("nav.applications")}</Link>
          <Link to="/objects">{t("nav.objects")}</Link>
          <Link to="/insights">{t("nav.insights")}</Link>
          <Link to="/about">{t("footer.aboutCompany")}</Link>
        </div>
        <div>
          <div className="footer-h">{t("footer.products")}</div>
          {PRODUCTS.slice(0, 6).map(p => (
            <Link key={p.slug} to={`/product/${p.slug}`}>{p.name}</Link>
          ))}
        </div>
        <div>
          <div className="footer-h">{t("footer.contacts")}</div>
          <p>{COMPANY.phone}</p>
          <p>{COMPANY.email}</p>
          <p>{addressLines.map((line, i) => (
            <React.Fragment key={i}>{line}{i < addressLines.length - 1 && <br/>}</React.Fragment>
          ))}</p>
        </div>
      </div>
      <div className="footer-bottom">
        <span>© 2019 — {new Date().getFullYear()} · Artifex De Color</span>
        <span className="footer-credit">
          {t("footer.developedBy")}{" "}
          <a
            href="https://www.maze.uz"
            target="_blank"
            rel="noopener noreferrer"
            className="footer-maze"
            aria-label="MAZE"
          >MAZE</a>
        </span>
      </div>
    </footer>
  );
}

// ============================================================================
// Content loader — single source of truth now lives in /content/*.json,
// edited through Decap CMS. The old hardcoded DEFAULT_* + localStorage
// override model is gone.
//
// Each JSON item carries bilingual fields with `_ru` / `_en` suffixes
// (e.g. { name_ru, name_en, desc_ru, desc_en, slug, palette, ... }).
// At startup we fetch every file in parallel, then project against the
// current language into the live runtime arrays/objects the rest of
// the codebase reads (PRODUCTS, TEAM, TEXTS, etc.).
// ============================================================================

const CONTENT_BASE = "content/";
const CONTENT_FILES = [
  "products", "applications", "objects", "team",
  "plans", "partners", "milestones", "faq",
  "insights", "texts", "company", "photos"
];

const PRODUCTS = [];
const APPLICATIONS = [];
const OBJECTS = [];
const PHOTOS = {};
const COMPANY = {};
const TEAM = [];
const PLANS = [];
const PARTNERS = [];
const MILESTONES = [];
const FAQ = [];
const INSIGHTS = [];
const TEXTS = {};

// Raw bilingual content, kept exactly as fetched. `refreshArtifexData()`
// re-projects from this source whenever the language changes — no
// refetch required.
let _rawContent = null;

async function _fetchContent() {
  const results = await Promise.all(
    CONTENT_FILES.map(name =>
      fetch(CONTENT_BASE + name + ".json", { cache: "no-cache" })
        .then(r => {
          if (!r.ok) throw new Error(`${name}.json → HTTP ${r.status}`);
          return r.json();
        })
    )
  );
  const out = {};
  CONTENT_FILES.forEach((name, i) => {
    const raw = results[i];
    // Collection files are wrapped as { items: [...] } so Decap's list
    // widget can edit them. Scalar-object files (texts / company / photos)
    // are used as-is.
    out[name] = (raw && Array.isArray(raw.items)) ? raw.items : raw;
  });
  return out;
}

// Project a bilingual record { slug, palette, name_ru, name_en, ... }
// into a single-language record { slug, palette, name, ... } by
// stripping the `_ru` / `_en` suffix for the current language and
// dropping the other language's fields. Neutral fields are kept as-is.
function _projectRecord(rec, lang) {
  if (!rec || typeof rec !== "object") return rec;
  if (Array.isArray(rec)) return rec.map(x => _projectRecord(x, lang));
  const wantSuffix = "_" + lang;
  const otherSuffix = lang === "ru" ? "_en" : "_ru";
  const out = {};
  for (const [k, v] of Object.entries(rec)) {
    if (k.endsWith(wantSuffix)) {
      out[k.slice(0, -3)] = v;
    } else if (k.endsWith(otherSuffix)) {
      // fallback: if target language missing the field, opposite wins
      const base = k.slice(0, -3);
      if (!(base in out)) out[base] = v;
    } else if (v && typeof v === "object") {
      out[k] = _projectRecord(v, lang);
    } else {
      out[k] = v;
    }
  }
  return out;
}

function _projectArray(arr, lang) {
  return Array.isArray(arr) ? arr.map(x => _projectRecord(x, lang)) : [];
}

function _projectTexts(textsObj, lang) {
  const out = {};
  for (const [page, fields] of Object.entries(textsObj || {})) {
    out[page] = _projectRecord(fields, lang);
  }
  return out;
}

function _fillArray(target, src) {
  target.length = 0;
  if (Array.isArray(src)) target.push(...src);
}

function _fillObject(target, src) {
  Object.keys(target).forEach(k => delete target[k]);
  Object.assign(target, src || {});
}

function refreshArtifexData() {
  if (!_rawContent) return; // called before fetch finished; no-op
  const lang = getLang();
  _fillArray(PRODUCTS,     _projectArray(_rawContent.products,     lang));
  _fillArray(APPLICATIONS, _projectArray(_rawContent.applications, lang));
  _fillArray(OBJECTS,      _projectArray(_rawContent.objects,      lang));
  _fillArray(TEAM,         _projectArray(_rawContent.team,         lang));
  _fillArray(PLANS,        _projectArray(_rawContent.plans,        lang));
  _fillArray(PARTNERS,     _projectArray(_rawContent.partners,     lang));
  _fillArray(MILESTONES,   _projectArray(_rawContent.milestones,   lang));
  _fillArray(FAQ,          _projectArray(_rawContent.faq,          lang));
  _fillArray(INSIGHTS,     _projectArray(_rawContent.insights,     lang));
  _fillObject(TEXTS,       _projectTexts(_rawContent.texts,        lang));
  _fillObject(COMPANY,     _projectRecord(_rawContent.company,     lang));
  _fillObject(PHOTOS,      _rawContent.photos || {});
  _applyFavicon(COMPANY.favicon);
}

// Swap the <link rel="icon"> href when the editor uploads a custom favicon
// in Decap. The static /favicon.svg shipped with the build stays as the
// pre-fetch fallback so there is no flash of missing icon.
function _applyFavicon(url) {
  if (typeof document === "undefined" || !url) return;
  const link = document.getElementById("site-favicon");
  if (!link || link.getAttribute("href") === url) return;
  link.setAttribute("href", url);
  // Drop the type hint — Decap uploads may be PNG/ICO, not SVG.
  link.removeAttribute("type");
}

// Entry point called by app.jsx before the first React render. Resolves
// once every content file is in memory and the runtime slots are filled.
async function initArtifexData() {
  _rawContent = await _fetchContent();
  refreshArtifexData();
}

Object.assign(window, {
  PRODUCTS, APPLICATIONS, OBJECTS, PHOTOS, COMPANY, TEAM, PLANS, PARTNERS, MILESTONES, FAQ, INSIGHTS, TEXTS,
  Swatch, PhotoOrSwatch, ProductCoverOrSwatch, useRoute, Link, Nav, Footer, Logo,
  refreshArtifexData, initArtifexData,
  getLang, setLang, useLang, t, UI_STRINGS
});
