// Restyler — модальное окно настройки темы GetCourse
// Записывает CSS-переменные в Layout [RemakeConfig]Restyler

(function() {
	const TARGET_NAME = '[RemakeConfig]Restyler';
	const SYNC_TARGET = '\u041e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u0435 Remake';

	const BASE_FONTS_IMPORT = "@import url('https://fonts.googleapis.com/css2?family=Golos+Text:wght@400;500;600;700;800;900&family=Manrope:wght@100;200;300;400;500;600;700;800&display=swap');";
	const DEFAULT_REMAKE_CSS = `${BASE_FONTS_IMPORT}

html body {
   /* Ширина контента */
   --content-width: 1200px;

   /* Логотип */
   --m-account-logo: url("/fileservice/file/download/a/447724/sc/335/h/a48ac0e950c7616ae3521fb7fecfd80d.svg");
   --m-account-logo-mob: url('/fileservice/file/download/a/447724/sc/153/h/5510cd5d4948c59edc6a574e1e6d0327.svg');
   --m-account-logo-url: "/";

   /* Шрифты */
   --font-family-1: 'Inter', "Lato", Helvetica, Arial, sans-serif;
   --font-family-2: 'Golos', "Lato", Helvetica, Arial, sans-serif;

   /* Основные цвета, заменить */
   --color-0: #171717; /* цвет текста */
   --color-1: #fff; /* цвет панелей */
   --main-color: #757AFF; /* акцентный цвет */
   --text-on-accent: 255,255,255; /* текст на акцентном фоне */
   --color-3: #656ADF;  /* цвет кнопки */
   --color-3-1: #696FF4; /* кнопка при наведении */
   --color-17: 48, 130, 255; /* link-default */


   /* Второстепенные цвета, можно не менять */
   --color-3-c: var(--color-3);  /* only-color */
   --color-4: #FF7B00;  /* yellow */
   --color-5: #CFACED;  /* link visited */
   --color-6: #40AD6C;  /* green/text */
   --color-7: #cfe7ce;  /* green/bg */
   --color-7-1: #D0DCCF; /* lite-green  */
   --color-8: #F06363;  /* red/bg #F06363 */
   --color-8-1: #FFB2B2; /* lite-red  */
   --color-9: #FF0000;  /* red/text #B20000 */
   --color-10: #323232; /* gray-1 */
   --color-11: #CECECE; /* gray-2 */
   --color-12: #F7F7F7; /* gray-3 */
   --color-13: #A4C6F1; /* light-blue */
   --color-14: #171717; /* gray-4 */
   --color-15: #F3F3F3; /* gray-5 */
   --color-16: #A09D9D; /* gray-6 */

   /* Иконки (раскомментировать перед использованием)*/
   /*
   --default-user-img:  url("");
   --document-circle-icon:  url("");
   --checkmark-circle-icon:  url("");
   --warning-circle-icon:  url("");
   --clock-circle-icon:  url("");
   --sandclock-circle-icon:  url("");
   --cross-circle-icon:  url("");
   --sort-desc-icon:  url("");
   --sort-asc-icon:  url("");
   --mic-icon:  url("");
   --paperclip-icon: url("");
   --smile-icon:  url("");
   --paperplane-icon:  url("");
   --arrow-straight-icon:  url("");
   --checkmark-icon: url("");
   --cross-icon: url("");
   --bell-icon: url("");
   --heart-icon: url("");
   */
}

/* Настройки меню */
html #gcAccountUserMenu {
  --m-bg-color: #F4F4F4;
  --m-item-font-color: #333333;
  --m-item-color: rgba(255,255,255,1);
  --m-item-active: rgba(255,255,255,1);
  --m-item-hover-color: rgba(255,255,255,0.5);
  --m-item-icon-color: #333;
  --m-default-avatar-color: #cecece;
  --m-notify-color: #FF0000;
  --m-custom-btn-color: var(--color-3);
  --m-custom-btn-text: "Тех. поддержка 1";
  --m-custom-btn-url: "javascript://$('.activated-talks-widget').data('gc-talksWidget').showTalksWindow()"; /* "/pl/talks/conversation" */
  /*--m-burger-icon: url("");*/
}

#gcAccountUserMenu {
  --font-family-1a: var(--cslm-menu_font_style_item, var(--font-family-1));
  --font-family-2a: var(--cslm-menu_font_style_label, var(--font-family-2));

  --m-bg-color: var(--cslm-menu_color_bg, #F4F4F4);
  --m-item-font-color: var(--cslm-menu_font_color_label, #333333);
  --m-item-color: var(--cslm-menu_color_bg_item, transparent);
  --m-item-active: var(--cslm-menu_color_bg_item_active, rgba(255,255,255,1));
  --m-item-hover-color: var(--cslm-menu_color_bg_item_hover, rgba(255,255,255,0.5));
  --m-item-icon-color: #757575;
  --m-default-avatar-color: #cecece;
  --m-notify-color: #FF0000;
  --m-custom-btn-color: var(--color-3);
  --m-custom-btn-color-hover: var(--color-3-1);
  --m-custom-btn-text: "Тех. поддержка 2";
  --m-custom-btn-url: "javascript://$('.activated-talks-widget').data('gc-talksWidget').showTalksWindow()";
}`;

	// --- Секции переменных ---
	const SECTIONS = [
		{
			id: 'layout',
			title: 'Ширина контента',
			vars: ['--content-width']
		},
		{
			id: 'logo',
			title: 'Логотип',
			vars: ['--m-account-logo', '--m-account-logo-mob']
		},
		{
			id: 'fonts',
			title: 'Шрифты',
			vars: ['--font-family-1', '--font-family-2']
		},
		{
			id: 'colors-primary',
			title: 'Основные цвета',
			vars: ['--color-0', '--color-1', '--main-color', '--text-on-accent', '--color-3', '--color-3-1', '--color-17']
		},
		{
			id: 'support-btn',
			title: 'Кнопка поддержки',
			desc: 'Кнопка в левой боковой панели',
			vars: ['--m-custom-btn-text', '--m-custom-btn-url']
		},
		{
			id: 'top-menu-btn',
			title: '',
			desc: 'Кнопка в шапке',
			jsVars: true,
			vars: ['_topMenuName', '_topMenuLink', '_topMenuIcon']
		}
	];

	// --- Логирование ---
	var LOG_PREFIX = '[Restyler]';
	function log() { var args = [LOG_PREFIX].concat(Array.prototype.slice.call(arguments)); console.log.apply(console, args); }
	function logErr() { var args = [LOG_PREFIX + ' ERROR'].concat(Array.prototype.slice.call(arguments)); console.error.apply(console, args); }
	function logWarn() { var args = [LOG_PREFIX + ' WARN'].concat(Array.prototype.slice.call(arguments)); console.warn.apply(console, args); }

	// --- Утилиты ---
	function getCookie(name) {
		let matches = document.cookie.match(new RegExp("(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"));
		return matches ? decodeURIComponent(matches[1]) : undefined;
	}

	function unwrapUrl(val) {
		let m = val.match(/url\(['"]?(.*?)['"]?\)/);
		return m ? m[1] : val.replace(/['"]/g, '');
	}

	function esc(str) {
		return String(str).replace(/&/g, '\x26amp;').replace(/</g, '\x26lt;').replace(/>/g, '\x26gt;').replace(/"/g, '\x26quot;').replace(/'/g, '\x26#39;');
	}

	function quoteCssString(value) {
		return '"' + String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\r\n|[\r\n\f]/g, '\\a ') + '"';
	}

	// Извлекает имя первого шрифта из CSS font-family
	function extractFontName(val) {
		var match = String(val || '').match(/^\s*(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|([^,]+))/);
		if (!match) return '';
		var first = match[1] !== undefined ? match[1] : (match[2] !== undefined ? match[2] : match[3].trim());
		return first.replace(/\\(["'\\])/g, '$1');
	}

	// Запасные шрифты не зависят от старого (возможно повреждённого) CSS.
	function reconstructFont(newName, variableName) {
		var name = String(newName || '').trim();
		var fallback = [variableName === '--font-family-2' ? "'Golos'" : "'Inter'", '"Lato"', 'Helvetica', 'Arial', 'sans-serif'];
		return (name ? [quoteCssString(name)] : []).concat(fallback.filter(function(family) {
			return extractFontName(family).toLowerCase() !== name.toLowerCase();
		})).join(', ');
	}

	// Конвертация: "255,255,255" или "48, 130, 255" → "#ffffff" или "#3082ff"
	function rgbToHex(rgbStr) {
		var parts = rgbStr.split(',').map(function(s) { return parseInt(s.trim(), 10); });
		if (parts.length < 3 || parts.some(isNaN)) return '#ffffff';
		return '#' + parts.slice(0, 3).map(function(c) {
			return ('0' + Math.max(0, Math.min(255, c)).toString(16)).slice(-2);
		}).join('');
	}

	// Конвертация: "#3082ff" → "48, 130, 255"
	function hexToRgb(hex) {
		hex = hex.replace('#', '');
		if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
		var r = parseInt(hex.substring(0, 2), 16);
		var g = parseInt(hex.substring(2, 4), 16);
		var b = parseInt(hex.substring(4, 6), 16);
		return r + ', ' + g + ', ' + b;
	}

	// Проверяет, является ли значение голыми RGB числами (без rgb())
	function isRawRgb(value) {
		var parts = value.split(',');
		if (parts.length < 3) return false;
		return parts.every(function(s) { var n = parseInt(s.trim(), 10); return !isNaN(n) && n >= 0 && n <= 255; });
	}

	function varType(name, value) {
		if (name === '--content-width') return 'width';
		if (name.includes('logo') && !name.includes('url')) return 'logo';
		if (name.includes('font-family')) return 'font';
		if (name === '--m-account-logo-url' || name === '--m-custom-btn-text' || name === '--m-custom-btn-url') return 'string';
		if (name === '_topMenuName' || name === '_topMenuLink') return 'string';
		if (name === '_topMenuIcon') return 'logo';
		if (isRawRgb(value)) return 'rgb';
		if (value.includes('#') || value.includes('rgb') || value.trim() === 'white') return 'color';
		if (value.startsWith('var(')) return 'ref';
		return 'text';
	}

	// --- CSS ---
	var STYLES = `
	/* --- Modal override (gcModalFactory) --- */
	.rst-modal .modal-dialog {
		max-width: 720px; width: 90vw;
		max-height: 90vh; height: 90vh;
		margin: 5vh auto;
		display: flex; flex-direction: column;
	}
	.rst-modal .modal-content {
		border: none; border-radius: 20px;
		box-shadow: 0 25px 60px rgba(0,0,0,.12), 0 8px 20px rgba(0,0,0,.08);
		overflow: hidden; flex: 1; display: flex; flex-direction: column;
		background: #fff;
	}
	.rst-modal .modal-header {
		background: #fff; border-bottom: 1px solid #f0f0f0;
		padding: 20px 28px; min-height: auto;
	}
	.rst-modal .modal-header .modal-title {
		font-family: 'Manrope', sans-serif; font-size: 22px; font-weight: 700;
		color: #292929; letter-spacing: -0.5px;
	}
	.rst-modal .modal-header .close {
		opacity: .4; font-size: 28px; font-weight: 300;
		transition: opacity .15s; margin: -4px -8px 0 0;
	}
	.rst-modal .modal-header .close:hover { opacity: .8; }
	.rst-modal .modal-body {
		padding: 0; flex: 1; overflow: hidden;
		display: flex; flex-direction: column;
	}

	.rst-root { --accent: var(--main-color, #8f93ff); --t1: #292929; --t2: #888; --brd: #e5e5e5; font-family: 'Golos Text', 'Golos', sans-serif; color: var(--t1); line-height: 1.4; flex: 1; display: flex; flex-direction: column; overflow: hidden; }
	.rst-root *, .rst-root *::before, .rst-root *::after { box-sizing: border-box; margin: 0; padding: 0; }

	/* --- Toast --- */
	.rst-toast-wrap { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 999999; pointer-events: none; }
	.rst-toast {
		padding: 10px 20px; border-radius: 8px; font-size: 13px; font-weight: 500;
		pointer-events: auto; opacity: 0; transform: translateY(-12px);
		transition: all .25s ease; white-space: nowrap; margin-bottom: 6px;
	}
	.rst-toast.show { opacity: 1; transform: translateY(0); }
	.rst-toast--ok { background: var(--t1); color: #fff; }
	.rst-toast--err { background: #C23B3B; color: #fff; }

	/* --- Scroll --- */
	.rst-scroll { flex: 1; overflow-y: auto; padding: 24px 28px 8px; }
	.rst-scroll::-webkit-scrollbar { width: 4px; }
	.rst-scroll::-webkit-scrollbar-track { background: transparent; }
	.rst-scroll::-webkit-scrollbar-thumb { background: #ddd; border-radius: 2px; }

	/* --- Sections (flat, no accordions) --- */
	.rst-sec { margin-bottom: 28px; }
	.rst-sec-title { font-family: 'Manrope', sans-serif; font-size: 20px; font-weight: 700; color: var(--t1); letter-spacing: -0.6px; line-height: 1.1; margin-bottom: 16px; }
	.rst-sec-desc { font-size: 14px; font-weight: 600; color: var(--t1); line-height: 1.4; margin-bottom: 12px; }

	/* --- Color grid (2 columns) --- */
	.rst-grid { display: flex; flex-wrap: wrap; gap: 16px 24px; }
	.rst-field { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
	.rst-field--half { width: calc(50% - 12px); }
	.rst-font-row { display: flex; gap: 20px; width: 100%; }
	.rst-font-row .rst-field { flex: 1; min-width: 0; width: auto; }
	.rst-font-row .rst-inp { width: 100%; }
	.rst-font-source { margin-bottom: 16px; display: grid; gap: 12px; }
	.rst-font-source label { display: grid; gap: 8px; font-size: 14px; }
	.rst-font-source select, .rst-font-source .rst-font-css-url { width: 100%; }
	.rst-font-custom { display: grid; gap: 8px; }
	.rst-font-source [hidden] { display: none !important; }
	.rst-font-file-row { display: flex; align-items: center; gap: 8px; }
	.rst-font-file-row .rst-inp { min-width: 0; flex: 1; }
	.rst-font-file-row button { flex-shrink: 0; }
	.rst-font-help, .rst-font-upload-status { font-size: 13px; color: var(--t2); line-height: 1.5; }
	.rst-font-upload-status:empty { display: none; }
	.rst-font-upload-status[data-error="true"] { color: #b42318; }
	.rst-font-native { position: absolute; width: 1px; height: 1px; overflow: hidden; opacity: 0; pointer-events: none; }
	.rst-field--full { width: 100%; }
	.rst-field-label { font-size: 14px; color: var(--t1); line-height: 1.1; }

	/* --- Color input (swatch inside border) --- */
	.rst-color-wrap {
		display: flex; align-items: center; gap: 8px;
		height: 44px; border: 1px solid var(--brd); border-radius: 12px;
		padding: 6px 12px 6px 6px; cursor: pointer;
		transition: border-color .15s;
	}
	.rst-color-wrap:hover { border-color: #bbb; }
	.rst-color-wrap:focus-within { border-color: var(--accent); }
	.rst-swatch {
		width: 32px; height: 32px; border-radius: 10px;
		border: 1px solid rgba(0,0,0,.1); flex-shrink: 0;
		position: relative; overflow: hidden; cursor: pointer;
	}
	.rst-swatch input[type="color"] {
		position: absolute; inset: -10px; width: calc(100% + 20px); height: calc(100% + 20px);
		border: none; cursor: pointer; opacity: 0;
	}
	.rst-color-inp {
		border: none; outline: none; background: transparent;
		font-size: 14px; font-family: inherit; color: var(--t1);
		flex: 1; min-width: 0; line-height: 1.1;
	}

	/* --- Text/font/number inputs --- */
	.rst-inp {
		height: 44px; padding: 0 12px;
		font-size: 14px; font-family: inherit;
		background: #fff; color: var(--t1);
		border: 1px solid var(--brd); border-radius: 12px;
		outline: none; transition: border-color .15s;
		width: 170px;
	}
	.rst-inp:focus { border-color: var(--accent); }
	.rst-inp--full { width: 100%; }
	.rst-px { font-size: 12px; color: var(--t2); font-weight: 500; margin-left: 4px; }
	.rst-row-inline { display: flex; align-items: center; gap: 8px; }

	/* --- Logo field (card: preview left + content right) --- */
	.rst-field[data-type="logo"] { width: 100%; }
	.rst-field[data-type="logo"] + .rst-field[data-type="logo"] { margin-top: -4px; }
	.rst-logo-card {
		display: flex; align-items: flex-start; gap: 16px;
		background: #fff; border: 1px solid rgba(0,0,0,.1);
		border-radius: 14px; padding: 17px; width: 100%;
	}
	.rst-logo-preview {
		width: 120px; height: 72px; flex-shrink: 0;
		border-radius: 10px; background: var(--accent, #8f93ff);
		display: flex; align-items: center; justify-content: center;
		overflow: hidden; position: relative;
	}
	.rst-logo-preview img {
		max-width: 80%; max-height: 80%; object-fit: contain;
	}
	.rst-logo-placeholder {
		opacity: .15; width: 30px; height: 30px; color: #fff;
	}
	.rst-logo-preview img[src=""] { display: none; }
	.rst-logo-right {
		flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px;
	}
	.rst-logo-title {
		display: flex; align-items: center; gap: 8px;
		font-size: 14px; font-weight: 400; color: var(--t1); line-height: 20px;
	}
	.rst-logo-title svg { flex-shrink: 0; width: 16px; height: 16px; }
	.rst-logo-desc {
		font-size: 12px; color: #717182; line-height: 16px;
	}
	.rst-logo-actions {
		display: flex; gap: 8px; margin-top: 8px;
	}
	.rst-upload-btn {
		display: inline-flex; align-items: center; gap: 6px;
		padding: 0 10px; height: 32px; border-radius: 8px;
		background: #fff; color: var(--t1);
		font-size: 14px; font-weight: 500; font-family: inherit;
		cursor: pointer;
		border: 1px solid rgba(0,0,0,.1);
		transition: background .15s;
	}
	.rst-upload-btn:hover { background: #f5f5f5; }
	.rst-upload-btn svg { flex-shrink: 0; width: 14px; height: 14px; }
	.rst-logo-delete {
		display: inline-flex; align-items: center; gap: 6px;
		padding: 0 10px; height: 32px; border-radius: 8px;
		background: transparent; color: #d4183d;
		font-size: 14px; font-weight: 500; font-family: inherit;
		cursor: pointer; border: none;
		transition: background .15s;
	}
	.rst-logo-delete:hover { background: rgba(212,24,61,.06); }
	.rst-logo-delete svg { flex-shrink: 0; width: 14px; height: 14px; }
	.rst-upload-box { position: relative; display: inline-flex; }
	/* Скрываем нативные элементы fileWidget (uploadifive) */
	.rst-field[data-type="logo"] .rst-upload-box > div {
		position: absolute !important; width: 0 !important; height: 0 !important;
		overflow: hidden !important; opacity: 0 !important; pointer-events: none !important;
	}
	/* Скрываем текстовое поле URL и стандартный лейбл */
	.rst-field[data-type="logo"] .rst-val { display: none; }
	.rst-field[data-type="logo"] > .rst-field-label { display: none; }

	/* --- Save --- */
	.rst-foot { padding: 16px 28px 24px; display: flex; align-items: center; gap: 16px; border-top: 1px solid #f0f0f0; flex-shrink: 0; }
	.rst-save {
		display: flex; align-items: center; justify-content: center; gap: 8px;
		width: 200px; height: 45px;
		background: var(--accent); color: #fff;
		border: none; border-radius: 6px;
		font-size: 16px; font-weight: 400; cursor: pointer;
		font-family: 'Golos Text', sans-serif;
		transition: opacity .15s;
		flex-shrink: 0;
	}
	.rst-save:hover:not(:disabled) { opacity: .85; }
	.rst-save:active:not(:disabled) { opacity: .7; }
	.rst-save:disabled { opacity: .5; cursor: wait; }
	@keyframes rst-spin { to { transform: rotate(360deg); } }
	.rst-status {
		display: flex; align-items: center; gap: 8px;
		font-size: 13px; color: var(--t2); font-family: 'Golos Text', sans-serif;
		white-space: nowrap;
	}
	.rst-status-spinner {
		width: 16px; height: 16px;
		border: 2px solid var(--brd);
		border-top-color: var(--accent);
		border-radius: 50%;
		animation: rst-spin .7s linear infinite;
		flex-shrink: 0;
	}
	.rst-status--done { color: #40AD6C; }
	.rst-status--err { color: #C23B3B; }

	/* --- Loader --- */
	.rst-loader {
		display: flex; flex-direction: column; align-items: center;
		justify-content: center; padding: 56px 20px; gap: 14px;
	}
	.rst-loader-ring {
		width: 32px; height: 32px; border: 2.5px solid var(--brd);
		border-top-color: var(--a); border-radius: 50%;
		animation: rst-spin .7s linear infinite;
	}
	.rst-loader-t { font-size: 13px; color: var(--t2); }

	/* --- Trigger button --- */
	.rst-trigger {
		display: flex !important; align-items: center; justify-content: center;
		width: 38px; height: 38px; border-radius: 10px;
		background: var(--t1, #2C2925); color: #FAF9F7;
		text-decoration: none !important; cursor: pointer;
		position: relative; flex-shrink: 0;
		transition: background .15s;
	}
	.rst-trigger:hover { background: #1A1816; }
	.rst-trigger svg { display: block; }
	.rst-trigger-tip {
		position: absolute; left: calc(100% + 8px); top: 50%;
		transform: translateY(-50%); white-space: nowrap;
		background: #2C2925; color: #FAF9F7;
		font-size: 12px; font-weight: 500; padding: 5px 10px;
		border-radius: 6px; pointer-events: none;
		opacity: 0; transition: opacity .15s;
	}
	.rst-trigger:hover .rst-trigger-tip { opacity: 1; }
	`;

	// --- Кнопка ---
	function renderBtn() {
		var $anchor = $('.custom-btn').first();
		if (!$anchor.length || $('.rst-trigger').length) return;

		var $btn = $([
			'<a href="javascript:void(0)" class="rst-trigger">',
			'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">',
			'<circle cx="12" cy="12" r="3"/>',
			'<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 01-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06A1.65 1.65 0 0019.4 9a1.65 1.65 0 001.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/>',
			'</svg>',
			'<span class="rst-trigger-tip">Настройка темы</span>',
			'</a>'
		].join(''));
		$anchor.after($btn);
		$btn.on('click', function(e) { e.preventDefault(); e.stopPropagation(); openModal(); });
	}

	// Вставляем стили кнопки сразу (для обоих состояний сайдбара)
	$('<style>').text(STYLES).appendTo('head');

	$(() => {
		$('#gcAccountUserMenu').on('menu-ready', renderBtn);
		if ($('.custom-btn').length) renderBtn();
	});

	// --- Toast ---
	function toast(text, ok) {
		var $wrap = $('.rst-toast-wrap');
		if (!$wrap.length) { $wrap = $('<div class="rst-toast-wrap">'); $('body').append($wrap); }
		var $t = $('<div class="rst-toast ' + (ok ? 'rst-toast--ok' : 'rst-toast--err') + '">' + esc(text) + '</div>');
		$wrap.append($t);
		requestAnimationFrame(function() { $t.addClass('show'); });
		setTimeout(function() { $t.removeClass('show'); setTimeout(function() { $t.remove(); }, 250); }, 2200);
	}

	// --- Layout CRUD ---
	var pendingRestylerLayout = null;

	async function requestLayoutPage(url, action, options) {
		var target = new URL(url, window.location.href);
		var isLayoutUrl = function(value) {
			return value.origin === window.location.origin
				&& /^\/pl\/cms\/layout\/(index|create|update)$/.test(value.pathname);
		};
		if (!isLayoutUrl(target)) throw new Error(action + ': неверный адрес темы');

		var resp = await fetch(target.href, Object.assign({ credentials: 'same-origin' }, options));
		if (!resp.ok) throw new Error(action + ': HTTP ' + resp.status);
		var finalUrl = new URL(resp.url || target.href);
		if (!isLayoutUrl(finalUrl)) {
			throw new Error(action + ': откройте GetCourse под администратором и обновите страницу');
		}
		var html = await resp.text();
		return { response: resp, url: finalUrl, doc: new DOMParser().parseFromString(html, 'text/html') };
	}

	// Берём актуальную форму: вместе с _csrf сохраняем все её остальные поля.
	async function postLayoutForm(url, values, action) {
		var page = await requestLayoutPage(url, action);
		var nameField = page.doc.querySelector('input[name="Layout[name]"]');
		var form = nameField && nameField.form;
		if (!form || form.method.toLowerCase() !== 'post') {
			throw new Error(action + ': форма темы не найдена');
		}
		var target = new URL(url, window.location.href);
		var formUrl = new URL(form.getAttribute('action') || page.url.href, page.url.href);
		if (formUrl.origin !== target.origin || formUrl.pathname !== target.pathname
			|| formUrl.searchParams.get('id') !== target.searchParams.get('id')) {
			throw new Error(action + ': получена форма другой страницы');
		}

		var params = new URLSearchParams();
		new FormData(form).forEach(function(value, key) {
			if (typeof value === 'string') params.append(key, value);
		});
		if (target.pathname === '/pl/cms/layout/create' && !params.get('_csrf')) {
			throw new Error(action + ': в форме отсутствует _csrf. Обновите страницу и попробуйте снова');
		}
		if (!params.get('formParams[clarity_uid]')) {
			var uid = getCookie('x-chtm-uid');
			if (uid) params.set('formParams[clarity_uid]', uid);
		}
		Object.keys(values).forEach(function(key) { params.set(key, values[key]); });

		var result = await requestLayoutPage(formUrl.href, action, {
			method: 'POST',
			headers: { 'content-type': 'application/x-www-form-urlencoded' },
			body: params.toString()
		});
		var hasErrors = Array.from(result.doc.querySelectorAll('.error-summary li, .has-error .help-block'))
			.some(function(el) { return el.textContent.trim().length > 0; });
		if (hasErrors) throw new Error(action + ': GetCourse отклонил данные формы');
		if (target.pathname === '/pl/cms/layout/update'
			&& (result.url.pathname !== target.pathname
				|| result.url.searchParams.get('id') !== target.searchParams.get('id')
				|| !result.doc.querySelector('#content-css'))) {
			throw new Error(action + ': GetCourse не вернул страницу сохранённой темы');
		}
		return result.response;
	}

	// Найти layout по точному имени, без создания и без изменения его содержимого.
	async function findLayoutByName(name) {
		var page = await requestLayoutPage('/pl/cms/layout/index', 'Загрузка списка тем');
		if (!page.doc.querySelector('table.kv-grid-table')) {
			throw new Error('Список тем не найден. Проверьте права администратора и обновите страницу');
		}
		var link = Array.from(page.doc.querySelectorAll('table.kv-grid-table td a'))
			.find(function(el) { return el.textContent.trim() === name; });
		if (!link) return null;
		var url = new URL(link.getAttribute('href'), page.url.href);
		var id = url.searchParams.get('id');
		if (!id || !/^\d+$/.test(id) || url.pathname !== '/pl/cms/layout/update') {
			throw new Error('Не удалось определить адрес темы');
		}
		var edit = await requestLayoutPage(url.href, 'Загрузка темы');
		var nameField = edit.doc.querySelector('input[name="Layout[name]"]');
		var cssField = edit.doc.querySelector('#content-css');
		var jsField = edit.doc.querySelector('#content-javascript');
		if (!cssField || !nameField || nameField.value !== name
			|| edit.url.pathname !== url.pathname || edit.url.searchParams.get('id') !== id) {
			throw new Error('GetCourse не вернул запрошенную тему');
		}
		return { id: id, name: name, css: cssField.value || '', js: jsField ? jsField.value : '' };
	}

	// Одновременные открытия модального окна используют один запрос создания.
	function findOrCreateLayout(statusFn) {
		if (pendingRestylerLayout) return pendingRestylerLayout;
		pendingRestylerLayout = loadRestylerLayout(statusFn).finally(function() {
			pendingRestylerLayout = null;
		});
		return pendingRestylerLayout;
	}

	async function loadRestylerLayout(statusFn) {
		log('findOrCreateLayout: ищем layout', TARGET_NAME);
		var layout = await findLayoutByName(TARGET_NAME);
		if (!layout) {
			log('findOrCreateLayout: layout не найден, создаём');
			if (typeof statusFn === 'function') statusFn('Создание темы...');
			await postLayoutForm('/pl/cms/layout/create', { 'Layout[name]': TARGET_NAME }, 'Создание темы');

			// POST только один; повторяем лишь чтение списка, пока тема не появится.
			for (var attempt = 0; attempt < 3; attempt++) {
				await new Promise(function(resolve) { setTimeout(resolve, 1200); });
				layout = await findLayoutByName(TARGET_NAME);
				if (layout) break;
			}
			if (!layout) {
				throw new Error('GetCourse не подтвердил создание темы. Проверьте список тем перед повторной попыткой');
			}
		}

		log('findOrCreateLayout: найден layout id=' + layout.id);
		if (layout.css.length < 50) {
			log('findOrCreateLayout: CSS пуст, используем дефолтный');
			await saveLayout(layout.id, DEFAULT_REMAKE_CSS);
			layout.css = DEFAULT_REMAKE_CSS;
		}
		return layout;
	}

	async function saveLayout(id, css, name) {
		log('saveLayout: id=' + id, 'name=' + (name || TARGET_NAME), 'css length=' + css.length);
		return postLayoutForm('/pl/cms/layout/update?id=' + encodeURIComponent(id), {
			'Layout[name]': name || TARGET_NAME,
			'Content[css]': css,
			'Layout[ACCOUNT_GLOBAL]': '0',
			'ParamsObject[is_disabled_default_fonts]': '0',
			'ParamsObject[is_disabled_default_styles]': '0'
		}, 'Сохранение CSS темы');
	}

	// Сохранить JS, сохраняя CSS и остальные поля актуальной формы.
	async function saveLayoutJs(id, js, name) {
		log('saveLayoutJs: id=' + id, 'name=' + (name || ''), 'js length=' + js.length);
		return postLayoutForm('/pl/cms/layout/update?id=' + encodeURIComponent(id), {
			'Layout[name]': name || SYNC_TARGET,
			'Content[javascript]': js,
			'Layout[ACCOUNT_GLOBAL]': '0',
			'ParamsObject[is_disabled_default_fonts]': '0',
			'ParamsObject[is_disabled_default_styles]': '0'
		}, 'Сохранение JS темы');
	}


	// Парсинг remakeTopMenu.links[0] из JS
	function parseTopMenuFromJs(js) {
		var result = { name: '', link: '', icon: '' };
		var nameMatch = js.match(/let\s+remakeTopMenu[\s\S]*?links\s*:\s*\[[\s\S]*?name\s*:\s*"([^"]*)"/);
		var linkMatch = js.match(/let\s+remakeTopMenu[\s\S]*?links\s*:\s*\[[\s\S]*?link\s*:\s*"([^"]*)"/);
		var iconMatch = js.match(/let\s+remakeTopMenu[\s\S]*?links\s*:\s*\[[\s\S]*?icon\s*:\s*"([^"]*)"/);
		if (nameMatch) result.name = nameMatch[1];
		if (linkMatch) result.link = linkMatch[1];
		if (iconMatch) result.icon = iconMatch[1];
		return result;
	}

	// Замена значения свойства в remakeTopMenu.links[0]
	function mergeTopMenuIntoJs(js, prop, newValue) {
		var escaped = newValue.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
		var re = new RegExp(
			'(let\\s+remakeTopMenu[\\s\\S]*?links\\s*:\\s*\\[[\\s\\S]*?' + prop + '\\s*:\\s*)"([^"]*)"'
		);
		if (re.test(js)) {
			return js.replace(re, function(match, g1) {
				return g1 + '"' + escaped + '"';
			});
		}
		logWarn('mergeTopMenuIntoJs: свойство', prop, 'не найдено в JS');
		return js;
	}

	// Автоустановка <link> в настройки аккаунта → Код для BODY
	const LINK_DATA_NAME = 'Ксс-переменные с большим весом';
	async function installLinkToBody(layoutId) {
		log('installLinkToBody: layoutId=' + layoutId);
		var linkTag = '<link id="remake-restyler" data-name="' + LINK_DATA_NAME
			+ '" href="/pl/cms/layout/css?id=' + layoutId + '&hash=&v=' + Date.now()
			+ '" rel="stylesheet"/>';

		// 1. Загружаем страницу настроек
		var resp = await fetch('/pl/saas/account/settings');
		var html = await resp.text();
		var doc = new DOMParser().parseFromString(html, 'text/html');
		var form = doc.querySelector('form');
		if (!form) throw new Error('Форма настроек не найдена');

		// 2. Собираем поля формы вручную (без вставки в DOM — избегаем конфликтов со скриптами страницы)
		var formData = new FormData();
		form.querySelectorAll('input, textarea, select').forEach(function(el) {
			if (!el.name) return;
			if ((el.type === 'checkbox' || el.type === 'radio') && !el.checked) return;

			var val = el.value;
			if (el.name === 'ParamsObject[counters_html]') {
				var tagRegex = /<link[^>]*data-name=["']Ксс-переменные с большим весом["'][^>]*\/?>/gi;
				if (tagRegex.test(val)) {
					val = val.replace(tagRegex, linkTag);
				} else {
					val = val.trimEnd() + '\n' + linkTag;
				}
			}
			formData.append(el.name, val);
		});

		// 3. Отправляем обратно
		await fetch('/pl/saas/account/settings', {
			method: 'POST',
			body: formData
		});
	}

	// Генерация Google Fonts @import из имён шрифтов
	function buildGoogleFontsImport(fontNames) {
		var families = fontNames.map(function(name) {
			return 'family=' + encodeURIComponent(name).replace(/%20/g, '+') + ':wght@100;200;300;400;500;600;700;800;900';
		});
		return "@import url('https://fonts.googleapis.com/css2?" + families.join('&') + "&display=swap');";
	}

	function readFontSettings(css) {
		var match = css.match(/\/\* Restyler fonts:start ([^\r\n]*?) \*\//);
		if (match) {
			try {
				var settings = JSON.parse(decodeURIComponent(match[1]));
				if (settings.source === 'custom' && typeof settings.url === 'string') {
					return { source: 'custom', url: settings.url };
				}
			} catch (err) { logWarn('Не удалось прочитать источник шрифтов'); }
		}
		return { source: 'google', url: '' };
	}

	function normalizeFontCssUrl(value) {
		var text = String(value || '').trim();
		if (!text || /[\r\n\u0000]/.test(text)) throw new Error('Загрузите CSS-файл со шрифтами или укажите ссылку на него');
		var url;
		try { url = new URL(text, window.location.href); }
		catch (err) { throw new Error('Проверьте ссылку на CSS-файл со шрифтами'); }
		if (!/^https?:$/.test(url.protocol)) throw new Error('Для CSS-файла нужна ссылка http или https');
		if (url.username || url.password) throw new Error('Используйте ссылку на CSS-файл без логина и пароля');
		if (!/^(?:https?:\/\/|\/)/i.test(text)) throw new Error('Укажите полный адрес CSS-файла или путь, начинающийся с /');
		return url.href;
	}

	// Меняем только управляемый блок шрифтов и импорты Google Fonts.
	// Остальные импорты и CSS сохраняются; @charset остаётся первым.
	function updateFontImports(css, settings, fontNames) {
		var body = css.replace(/\/\* Restyler fonts:start [^\r\n]*? \*\/[\s\S]*?\/\* Restyler fonts:end \*\/(?:\r?\n)?/g, '');
		var importOrComment = /\/\*[\s\S]*?\*\/|@import\s+(?:url\(\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^)\s]+)\s*\)|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')\s*[^;]*;[ \t]*(?:\r?\n)?/gi;
		body = body.replace(importOrComment, function(statement) {
			if (statement.indexOf('/*') === 0) return statement;
			return /^@import\s+(?:url\(\s*)?["']?(?:https?:)?\/\/fonts\.googleapis\.com\/css(?:2)?(?:[?"'\s)]|$)/i.test(statement) ? '' : statement;
		});
		var cleanSettings = settings.source === 'custom'
			? { source: 'custom', url: normalizeFontCssUrl(settings.url) }
			: { source: 'google', url: '' };
		var statement = cleanSettings.source === 'custom'
			? '@import url(' + quoteCssString(cleanSettings.url) + ');'
			: buildGoogleFontsImport(Array.from(new Set(fontNames)));
		var block = '/* Restyler fonts:start ' + encodeURIComponent(JSON.stringify(cleanSettings)) + ' */\n'
			+ statement + '\n/* Restyler fonts:end */\n';
		var prefix = '';
		if (body.charAt(0) === '\uFEFF') { prefix = '\uFEFF'; body = body.slice(1); }
		var charset = body.match(/^@charset\s+["'][^"']+["']\s*;[^\S\r\n]*(?:\r?\n)?/i);
		if (charset) { prefix += charset[0]; body = body.slice(charset[0].length); }
		return prefix + block + BASE_FONTS_IMPORT + '\n' + body;
	}

	function buildFontSource(settings) {
		var custom = settings.source === 'custom';
		return '<div class="rst-font-source">'
			+ '<label>Источник шрифтов<select class="rst-inp rst-font-source-select">'
			+ '<option value="google"' + (!custom ? ' selected' : '') + '>Google Fonts</option>'
			+ '<option value="custom"' + (custom ? ' selected' : '') + '>Свой CSS-файл</option></select></label>'
			+ '<div class="rst-font-google rst-font-help"' + (custom ? ' hidden' : '') + '>Укажите ниже названия семейств из <a href="https://fonts.google.com/" target="_blank" rel="noopener noreferrer">Google Fonts</a> — они подключатся автоматически.</div>'
			+ '<div class="rst-font-custom"' + (!custom ? ' hidden' : '') + '>'
			+ '<div class="rst-field-label">CSS-файл со шрифтами</div><div class="rst-font-file-row">'
			+ '<input type="text" inputmode="url" aria-label="CSS-файл со шрифтами" class="rst-inp rst-font-css-url" placeholder="https://…/fonts.css" value="' + esc(settings.url) + '">'
			+ '<button type="button" class="rst-upload-btn rst-font-upload">Загрузить CSS</button></div>'
			+ '<div class="rst-font-help">Один CSS-файл может подключать оба шрифта через @font-face. Укажите ниже названия font-family точно как в этом файле. Ссылки на файлы шрифтов должны быть доступны из загруженного CSS.</div>'
			+ '<div class="rst-font-upload-status" role="status" aria-live="polite"></div>'
			+ '<div class="rst-font-native" aria-hidden="true"></div>'
			+ '</div></div>';
	}

	function collectFontSettings($m) {
		var $source = $m.find('.rst-font-source');
		if ($source.data('uploading')) throw new Error('Дождитесь завершения загрузки CSS-файла');
		var custom = $source.find('.rst-font-source-select').val() === 'custom';
		return { source: custom ? 'custom' : 'google', url: custom ? normalizeFontCssUrl($source.find('.rst-font-css-url').val()) : '' };
	}

	function bindFontSource($m) {
		var $source = $m.find('.rst-font-source');
		var $select = $source.find('.rst-font-source-select');
		var $url = $source.find('.rst-font-css-url');
		var $native = $source.find('.rst-font-native');
		var $upload = $source.find('.rst-font-upload');
		var $status = $source.find('.rst-font-upload-status');
		var generation = 0;
		var timeout = null;
		var $hidden = null;
		var selectionHandler = null;
		function finish(message, error) {
			clearTimeout(timeout);
			$source.data('uploading', false);
			$select.add($url).add($upload).prop('disabled', false);
			$status.text(message || '').attr('data-error', error ? 'true' : 'false');
		}
		function clearUploadStatus() {
			if (!$source.data('uploading')) $status.empty().attr('data-error', 'false');
		}
		$url.on('input change', clearUploadStatus);
		$select.on('change', function() {
			clearUploadStatus();
			var custom = $(this).val() === 'custom';
			$source.find('.rst-font-custom').prop('hidden', !custom);
			$source.find('.rst-font-google').prop('hidden', custom);
		});
		$upload.on('click', function() {
			if ($source.data('uploading')) return;
			if (!$.fn.fileWidget) { finish('Загрузчик GetCourse недоступен. Можно вставить ссылку на уже загруженный CSS.', true); return; }
			if (!$hidden) {
				var current = ++generation;
				if (selectionHandler) $native[0].removeEventListener('change', selectionHandler, true);
				$native.empty();
				var $input = $('<input type="hidden">').appendTo($native);
				$hidden = $input;
				function complete() {
					if (current !== generation || !document.contains($source[0]) || !$source.data('uploading')) return;
					var value = $input.val();
					if (!value) return;
					try {
						$url.val(normalizeFontCssUrl(value.indexOf('/') !== -1 ? value : '/fileservice/file/download/h/' + value));
						finish('CSS-файл загружен. Укажите названия семейств и сохраните тему.');
					} catch (err) { finish(err.message, true); }
				}
				$input.on('change', complete);
				try { $input.fileWidget({ onComplete: complete }); }
				catch (err) { $hidden = null; finish('Не удалось открыть загрузчик. Можно вставить ссылку на CSS.', true); return; }
				selectionHandler = function(event) {
					var input = event.target;
					if (current !== generation || input.type !== 'file' || !input.files || !input.files.length) return;
					$source.data('uploading', true);
					$select.add($url).add($upload).prop('disabled', true);
					$status.text('Загрузка CSS-файла…').attr('data-error', 'false');
					clearTimeout(timeout);
					timeout = setTimeout(function() {
						if (current !== generation) return;
						generation++;
						$native[0].removeEventListener('change', selectionHandler, true);
						$native.empty();
						$hidden = null;
						finish('Не удалось подтвердить загрузку. Прежняя ссылка сохранена — попробуйте ещё раз.', true);
					}, 60000);
				};
				$native[0].addEventListener('change', selectionHandler, true);
			}
			var $file = $native.find('input[type="file"]').last();
			if (!$file.length) {
				$native.find('a.dotted-link').first().trigger('click');
				$file = $native.find('input[type="file"]').last();
			}
			if (!$file.length) { finish('Загрузчик ещё не готов. Нажмите «Загрузить CSS» повторно.', true); return; }
			$file.attr('accept', '.css,text/css').prop('multiple', false);
			$file[0].click();
		});
	}

	// Заменить значения CSS-переменных в существующем CSS
	// varsMap: { '--color-0': '#171717', '--font-family-1': '...' }
	function mergeVariablesIntoCss(targetCss, varsMap) {
		var result = targetCss;
		var inserted = {};
		for (var varName in varsMap) {
			// Ищем --var-name: <value>; с возможным комментарием
			var re = new RegExp(
				'(' + varName.replace(/[-]/g, '\\-') + '\\s*:\\s*)([^;]*)(;)',
				'g'
			);
			if (re.test(result)) {
				re.lastIndex = 0;
				var newVal = varsMap[varName];
				result = result.replace(re, function(match, g1, g2, g3) {
					return g1 + newVal + g3;
				});
			} else if (varsMap[varName]) {
				inserted[varName] = varsMap[varName];
			}
		}
		// Вставляем новые переменные перед закрывающей } первого блока с переменными
		var newVarsStr = '';
		for (var k in inserted) {
			newVarsStr += '\n   ' + k + ': ' + inserted[k] + ';';
			log('mergeVariablesIntoCss: добавлена новая переменная', k);
		}
		if (newVarsStr) {
			// Пробуем :root, затем html body
			var didInsert = false;
			result = result.replace(/(:root\s*\{[^}]*)(})/, function(m, p1, p2) { didInsert = true; return p1 + newVarsStr + '\n' + p2; });
			if (!didInsert) {
				result = result.replace(/(html\s+body\s*\{[^}]*)(})/, function(m, p1, p2) { didInsert = true; return p1 + newVarsStr + '\n' + p2; });
			}
			if (!didInsert) {
				logWarn('mergeVariablesIntoCss: не найден :root или html body для вставки переменных');
			}
		}
		return result;
	}

	// --- Парсинг CSS ---
	// [ \t]* вместо \s* после ; — чтобы не ловить комментарии со следующей строки
	function parseCSSVariables(css) {
		var content = (css && css.length > 50) ? css : DEFAULT_REMAKE_CSS;
		var vars = [];
		var regex = /(--[\w-]+)\s*:\s*([^;]*?)\s*;[ \t]*(?:\/\*\s*(.*?)\s*\*\/)?/g;
		var m;
		while ((m = regex.exec(content)) !== null) {
			vars.push({ name: m[1].trim(), value: m[2].trim(), description: m[3] ? m[3].trim() : '' });
		}
		return vars;
	}

	// Человекочитаемые подписи переменных
	var LABELS = {
		'--content-width': 'Ширина контента',
		'--m-account-logo': 'Логотип для комп.версии',
		'--m-account-logo-mob': 'Логотип для моб.версии',
		'--m-account-logo-url': 'Ссылка с логотипа',
		'--font-family-1': 'Шрифт заголовков',
		'--font-family-2': 'Шрифт текста',
		'--color-0': 'Цвет текста',
		'--color-1': 'Цвет панелей',
		'--main-color': 'Акцентный цвет',
		'--text-on-accent': 'Текст на акцентном фоне',
		'--color-3': 'Цвет кнопки',
		'--color-3-1': 'Кнопка при наведении',
		'--color-17': 'Цвет ссылки',
		'--m-custom-btn-text': 'Текст кнопки',
		'--m-custom-btn-url': 'Ссылка / действие',
		'_topMenuName': 'Текст кнопки',
		'_topMenuLink': 'Ссылка / действие',
		'_topMenuIcon': 'Иконка'
	};

	function humanName(v) {
		if (LABELS[v.name]) return LABELS[v.name];
		if (v.description) return v.description;
		return v.name.replace(/^--/, '').replace(/[-_]/g, ' ');
	}

	// --- Модальное окно ---
	async function openModal() {
		log('openModal: запуск');
		if (!window.gcModalFactory) { logErr('gcModalFactory не найден'); return alert('gcModalFactory не найден'); }
		var modal = window.gcModalFactory.create({ width: '720px' });
		modal.setTitle('Настройка темы');
		modal.show();
		// Добавляем кастомный класс на .modal обёртку
		try { modal.getContentEl().closest('.modal').addClass('rst-modal'); } catch(e) {}
		modal.setContent('<div class="rst-root"><div class="rst-loader"><div class="rst-loader-ring"></div><div class="rst-loader-t">Загрузка настроек...</div></div></div>');

		try {
			var layoutData = await findOrCreateLayout(function(text) {
				modal.getContentEl().find('.rst-loader-t').text(text);
			});
			var variables = parseCSSVariables(layoutData.css);
			log('openModal: найдено', variables.length, 'переменных');

			// Загружаем JS из «Оформление Remake» для полей top-menu
			var topMenuData = { name: '', link: '', icon: '' };
			try {
				var syncLayout = await findLayoutByName(SYNC_TARGET);
				if (syncLayout && syncLayout.js) {
					topMenuData = parseTopMenuFromJs(syncLayout.js);
					log('openModal: topMenu загружен', topMenuData);
				}
			} catch (jsErr) {
				logWarn('openModal: не удалось загрузить JS:', jsErr);
			}

			renderEditor(modal, layoutData.id, variables, topMenuData, readFontSettings(layoutData.css));
		} catch (err) {
			logErr('openModal:', err);
			modal.setContent('<div class="rst-root" style="padding:40px;text-align:center;color:#C23B3B;font-size:14px;">' + esc(err.message) + '</div>');
		}
	}

	// --- Рендер ---
	function renderEditor(modal, layoutId, variables, topMenuData, fontSettings) {
		var h = '<div class="rst-root">';
		h += '<div class="rst-scroll">';

		// Маппинг JS-переменных на значения из topMenuData
		var jsVarValues = {
			'_topMenuName': topMenuData ? topMenuData.name : '',
			'_topMenuLink': topMenuData ? topMenuData.link : '',
			'_topMenuIcon': topMenuData ? topMenuData.icon : ''
		};

		SECTIONS.forEach(function(sec) {
			var secVars = [];
			if (sec.jsVars) {
				// JS-переменные: значения из topMenuData
				sec.vars.forEach(function(varName) {
					secVars.push({ name: varName, value: jsVarValues[varName] || '' });
				});
			} else {
				// CSS-переменные: из CSS + добавляем отсутствующие с дефолтом
				sec.vars.forEach(function(varName) {
					var found = variables.filter(function(v) { return v.name === varName; })[0];
					if (found) { secVars.push(found); }
					else { secVars.push({ name: varName, value: '' }); log('renderEditor: переменная', varName, 'не найдена в CSS, добавлена с пустым значением'); }
				});
			}
			if (!secVars.length) return;

			h += '<div class="rst-sec" data-sec="' + sec.id + '">'
				+ (sec.title ? '<div class="rst-sec-title">' + esc(sec.title) + '</div>' : '')
				+ (sec.desc ? '<div class="rst-sec-desc">' + sec.desc + '</div>' : '')
				+ (sec.id === 'fonts' ? buildFontSource(fontSettings) : '')
				+ '<div class="rst-grid">';
			var fontBuf = [];
			function flushFonts() {
				if (!fontBuf.length) return;
				h += '<div class="rst-font-row">';
				fontBuf.forEach(function(fv) { h += buildRow(fv); });
				h += '</div>';
				fontBuf = [];
			}
			secVars.forEach(function(v) {
				var t = varType(v.name, v.value);
				if (t === 'font') { fontBuf.push(v); }
				else { flushFonts(); h += buildRow(v); }
			});
			flushFonts();
			h += '</div></div>';
		});

		h += '</div>';

		// Save
		h += '<div class="rst-foot">'
			+ '<button class="rst-save" id="rst-save">'
			+ '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6L9 17l-5-5"/></svg>'
			+ 'Сохранить'
			+ '</button>'
			+ '<div class="rst-status" id="rst-status"></div>'
			+ '</div></div>';

		modal.setContent(h);
		bindEvents(modal, layoutId);
	}

	function buildRow(v) {
		var type = varType(v.name, v.value);
		var name = esc(v.name);
		var label = esc(humanName(v));
		var val = v.value;
		var isHalf = (type === 'color' || type === 'ref' || type === 'rgb');
		var fieldClass = 'rst-field' + (isHalf ? ' rst-field--half' : ' rst-field--full');
		var inp = '';

		if (type === 'width') {
			var widthNum = parseInt(val) || '';
			inp = '<div class="rst-row-inline">'
				+ '<input type="number" class="rst-inp rst-val" value="' + widthNum + '">'
				+ '<span class="rst-px">px</span></div>';
		} else if (type === 'logo') {
			var url = unwrapUrl(val);
			var hasImg = url && url.length > 1;
			var logoSvg = '<svg class="rst-logo-placeholder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>';
			var isMob = name.indexOf('mob') !== -1;
			var deviceIcon = isMob
				? '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>'
				: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>';
			var uploadSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>';
			var trashSvg = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2"/></svg>';
			inp = '<div class="rst-logo-card">'
				+ '<div class="rst-logo-preview">'
				+ (hasImg ? '<img src="' + esc(url) + '" alt="preview">' : logoSvg)
				+ '</div>'
				+ '<div class="rst-logo-right">'
				+ '<input type="text" class="rst-inp rst-inp--full rst-val" value="' + esc(url) + '">'
				+ '<div class="rst-logo-title">' + deviceIcon + ' ' + label + '</div>'
				+ '<div class="rst-logo-desc">По высоте 60px, по ширине до 200px. Формат: svg или png.</div>'
				+ '<div class="rst-logo-actions">'
				+ '<div class="rst-upload-box"><input type="hidden" class="rst-fh">'
				+ '<button type="button" class="rst-upload-btn rst-upload-click">' + uploadSvg + 'Загрузить</button></div>'
				+ '<button type="button" class="rst-logo-delete">' + trashSvg + 'Удалить</button>'
				+ '</div>'
				+ '</div>'
				+ '</div>';
		} else if (type === 'rgb') {
			var hexVal = rgbToHex(val);
			inp = '<div class="rst-color-wrap">'
				+ '<div class="rst-swatch" style="background:rgb(' + esc(val) + ')"><input type="color" value="' + esc(hexVal) + '"></div>'
				+ '<input type="text" class="rst-color-inp rst-val" value="' + esc(val) + '">'
				+ '</div>';
		} else if (type === 'color') {
			// Нормализуем именованные цвета в hex
			if (val.trim().toLowerCase() === 'white') val = '#ffffff';
			if (val.trim().toLowerCase() === 'black') val = '#000000';
			var cv = val.startsWith('#') ? val : '#ffffff';
			// input[type=color] требует 6-знаковый hex
			if (cv.length === 4) cv = '#' + cv[1]+cv[1] + cv[2]+cv[2] + cv[3]+cv[3];
			inp = '<div class="rst-color-wrap">'
				+ '<div class="rst-swatch" style="background:' + esc(val) + '"><input type="color" value="' + esc(cv) + '"></div>'
				+ '<input type="text" class="rst-color-inp rst-val" value="' + esc(val) + '">'
				+ '</div>';
		} else if (type === 'font') {
			var fontName = extractFontName(val);
			inp = '<input type="text" class="rst-inp rst-val" value="' + esc(fontName) + '">';
		} else if (type === 'string') {
			var strVal = val.replace(/^["']|["']$/g, '');
			inp = '<input type="text" class="rst-inp rst-inp--full rst-val" value="' + esc(strVal) + '">';
			if (name === '--m-custom-btn-url' || name === '_topMenuLink') {
				inp += '<a href="javascript:void(0)" class="rst-hint-link rst-insert-gc-chat" style="font-size:12px;color:#3082ff;margin-top:-4px;display:inline-block;cursor:pointer;text-decoration:none;">Вставить открытие чата GetCourse</a>';
			}
		} else {
			inp = '<input type="text" class="rst-inp rst-inp--full rst-val" value="' + esc(val) + '">';
		}

		return '<div class="' + fieldClass + '" data-var="' + name + '" data-type="' + type + '">'
			+ '<div class="rst-field-label">' + label + '</div>'
			+ inp + '</div>';
	}

	// --- События ---
	function bindEvents(modal, layoutId) {
		var $m = modal.getContentEl();
		bindFontSource($m);

		// File upload
		$m.find('.rst-field[data-type="logo"]').each(function() {
			var $field = $(this);
			var varName = $field.data('var');
			var $hidden = $field.find('.rst-fh');
			var $inp = $field.find('.rst-val');
			log('fileWidget init: var=' + varName, 'hidden found:', $hidden.length);

			try {
				$hidden.fileWidget({ onComplete: function() {
					log('fileWidget onComplete: var=' + varName, 'val=' + $hidden.val());
				} });
				log('fileWidget init OK: var=' + varName);
			} catch(e) {
				logErr('fileWidget init FAIL: var=' + varName, e);
			}

			$hidden.on('change', function() {
				var val = $(this).val();
				log('fileWidget change: var=' + varName, 'val=' + val);
				if (val) {
					var path = val.includes('/') ? val : '/fileservice/file/download/h/' + val;
					$inp.val(path);
					$field.find('.rst-logo-preview').html('<img src="' + path + '" alt="preview">');
				}
			});

			// Удалить логотип
			$field.on('click', '.rst-logo-delete', function() {
				$inp.val('');
				$field.find('.rst-logo-preview').html('<svg class="rst-logo-placeholder" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></svg>');
			});

			// Сразу при инициализации кликаем «Изменить» чтобы uploadifive создался
			setTimeout(function() {
				var $dotted = $field.find('a.dotted-link').first();
				if ($dotted.length) {
					log('fileWidget pre-click «Изменить»: var=' + varName);
					$dotted[0].click();
				}
			}, 300);

			$field.find('.rst-upload-click').on('click', function(e) {
				e.preventDefault();
				e.stopPropagation();
				log('upload click: var=' + varName);

				// После pre-click уже должен быть input[type=file] внутри .uploadifive-button
				var $fileInput = $field.find('.uploadifive-button input[type="file"]');
				if ($fileInput.length) {
					log('upload click: нашли input[type=file] в uploadifive-button, кликаем');
					$fileInput[0].click();
					return;
				}

				// Fallback: любой input[type=file]
				var $anyFile = $field.find('input[type="file"]');
				if ($anyFile.length) {
					log('upload click: нашли input[type=file], кликаем');
					$anyFile[0].click();
					return;
				}

				logWarn('upload click: input[type=file] не найден, пробуем клик по «Изменить»');
				var $dotted = $field.find('a.dotted-link').first();
				if ($dotted.length) {
					$dotted[0].click();
					// Retry через 500мс после создания uploadifive
					setTimeout(function() {
						var $retry = $field.find('.uploadifive-button input[type="file"]');
						if ($retry.length) {
							log('upload click: retry — нашли input[type=file], кликаем');
							$retry[0].click();
						} else {
							logWarn('upload click: retry — всё ещё не найден input[type=file]');
						}
					}, 500);
				}
			});
		});

		// Color picker
		$m.on('click', '.rst-swatch', function(e) {
			if (e.target.type !== 'color') $(this).find('input[type="color"]').click();
		});
		$m.on('input', '.rst-swatch input[type="color"]', function() {
			var c = $(this).val();
			var $field = $(this).closest('.rst-field');
			var type = $field.data('type');
			$(this).closest('.rst-swatch').css('background', c);
			// Для rgb-полей конвертируем hex → "R, G, B"
			if (type === 'rgb') {
				$field.find('.rst-val').val(hexToRgb(c)).trigger('input');
			} else {
				$field.find('.rst-val').val(c).trigger('input');
			}
		});
		$m.on('input', '.rst-val', function() {
			var val = $(this).val();
			var $field = $(this).closest('.rst-field');
			var type = $field.data('type');
			var $sw = $field.find('.rst-swatch');
			if ($sw.length) {
				if (type === 'rgb') {
					$sw.css('background', 'rgb(' + val + ')');
				} else if (val.includes('#') || val.includes('rgb')) {
					$sw.css('background', val);
				}
			}
		});

		// Insert GC chat link
		$m.on('click', '.rst-insert-gc-chat', function(e) {
			e.preventDefault();
			$(this).closest('.rst-field').find('.rst-val').val("javascript://$('.activated-talks-widget').data('gc-talksWidget').showTalksWindow()");
		});

		// Save
		$m.find('#rst-save').on('click', function() { doSave($m, $(this), layoutId); });
	}

	// Собрать карту изменённых CSS-переменных из UI (пропускает JS-переменные)
	function collectVarsMap($m) {
		var varsMap = {};
		$m.find('.rst-field[data-var]').each(function() {
			var name = $(this).data('var');
			if (name.charAt(0) === '_') return; // JS-переменные пропускаем
			var type = $(this).data('type');
			var $valInp = $(this).find('.rst-val');
			var val = $valInp.val();
			if (type === 'width') val = val ? val + 'px' : '';
			if (type === 'logo') val = val ? 'url("' + val + '")' : 'url("/")';
			if (type === 'font') val = reconstructFont(val, name);
			if (type === 'string') val = '"' + val + '"';
			varsMap[name] = val;
		});
		return varsMap;
	}

	// Собрать значения JS-переменных top menu из UI
	function collectTopMenuVars($m) {
		var result = {};
		$m.find('.rst-field[data-var^="_topMenu"]').each(function() {
			var name = $(this).data('var');
			var val = $(this).find('.rst-val').val();
			result[name] = val;
		});
		return result;
	}

	async function doSave($m, $btn, layoutId) {
		log('doSave: начало, layoutId=' + layoutId);
		var $status = $m.find('#rst-status');
		function setStatus(text, cls) {
			$status.attr('class', 'rst-status' + (cls ? ' rst-status--' + cls : ''))
				.html(cls ? text : '<div class="rst-status-spinner"></div>' + text);
		}
		$btn.prop('disabled', true);
		setStatus('Сохранение...');

		try {
			var fontSettings = collectFontSettings($m);
			var fontNames = [];
			$m.find('.rst-field[data-type="font"]').each(function() {
				var name = $(this).find('.rst-val').val().trim();
				if (!name || /[;{}\r\n\f]/.test(name)) throw new Error('Укажите корректные названия шрифтов для заголовков и текста');
				fontNames.push(name);
			});
			var varsMap = collectVarsMap($m);
			log('doSave: varsMap =', varsMap);

			// Загружаем актуальный CSS перед мержем (чтобы не затереть чужие изменения)
			var freshLayout = await findOrCreateLayout(function() {});
			var freshCss = freshLayout.css;
			log('doSave: загружен актуальный CSS, length=' + freshCss.length);

			var finalCss = mergeVariablesIntoCss(freshCss, varsMap);
			log('doSave: finalCss length=' + finalCss.length);

			finalCss = updateFontImports(finalCss, fontSettings, fontNames);

			await saveLayout(layoutId, finalCss);
			setStatus('Подключение тега...');

			// Автоустановка link-тега в настройки аккаунта
			try {
				await installLinkToBody(layoutId);
				log('doSave: тег подключён');
			} catch (installErr) {
				logErr('doSave: installLinkToBody:', installErr);
				toast('Ошибка установки тега: ' + installErr.message, false);
			}

			setStatus('Синхронизация...');

			// Синхронизация CSS-переменных в тему «Оформление Remake»
			try {
				var syncLayout = await findLayoutByName(SYNC_TARGET);
				if (syncLayout) {
					var mergedCss = mergeVariablesIntoCss(syncLayout.css, varsMap);
					mergedCss = updateFontImports(mergedCss, fontSettings, fontNames);
					await saveLayout(syncLayout.id, mergedCss, SYNC_TARGET);
					log('doSave: CSS синхронизирован с', SYNC_TARGET);
				} else {
					logWarn('doSave: тема', SYNC_TARGET, 'не найдена');
					toast('Тема «' + SYNC_TARGET + '» не найдена', false);
				}
			} catch (syncErr) {
				logErr('doSave: sync CSS:', syncErr);
				toast('Ошибка синхронизации CSS: ' + syncErr.message, false);
			}

			// Сохранение JS-переменных (top menu) в тему «Оформление Remake»
			var topMenuVars = collectTopMenuVars($m);
			if (Object.keys(topMenuVars).length) {
				setStatus('Сохранение кнопки в шапке...');
				try {
					var syncLayoutJs = await findLayoutByName(SYNC_TARGET);
					if (syncLayoutJs && syncLayoutJs.js) {
						var updatedJs = syncLayoutJs.js;
						if (topMenuVars._topMenuName !== undefined) updatedJs = mergeTopMenuIntoJs(updatedJs, 'name', topMenuVars._topMenuName);
						if (topMenuVars._topMenuLink !== undefined) updatedJs = mergeTopMenuIntoJs(updatedJs, 'link', topMenuVars._topMenuLink);
						if (topMenuVars._topMenuIcon !== undefined) updatedJs = mergeTopMenuIntoJs(updatedJs, 'icon', topMenuVars._topMenuIcon);
						await saveLayoutJs(syncLayoutJs.id, updatedJs, SYNC_TARGET);
						log('doSave: JS top menu сохранён');
					}
				} catch (jsErr) {
					logErr('doSave: JS save:', jsErr);
					toast('Ошибка сохранения кнопки в шапке: ' + jsErr.message, false);
				}
			}

			$btn.prop('disabled', false);
			setStatus('Сохранено', 'done');
			log('doSave: сохранение завершено успешно');
			toast('Тема сохранена', true);
			setTimeout(function() { location.reload(); }, 1200);
		} catch (err) {
			logErr('doSave:', err);
			$btn.prop('disabled', false);
			setStatus('Ошибка: ' + err.message, 'err');
			toast(err.message, false);
		}
	}
})();