mod: css и js вынесены во внешние файлы (будут кашироваться, что ускорит загрузку)

This commit is contained in:
2026-01-16 17:06:16 +03:00
parent b50a3202a8
commit a0d7392cf7
5 changed files with 421 additions and 379 deletions

View File

@@ -13,184 +13,10 @@
<link rel="manifest" href="{% static 'site.webmanifest' %}" />
{# Bootstrap 5 CSS #}<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
{# Bootstrap Icons #}<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" />
{# Custom CSS #}<link href="{% static 'css/etpgrf.css' %}" rel="stylesheet" />
{# HTMX #}<script src="https://unpkg.com/htmx.org@1.9.10"></script>
{# Alpine.js #}<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
{# Polyfill for Import Maps #}<script async src="https://ga.jspm.io/npm:es-module-shims@1.10.0/dist/es-module-shims.js"></script>
<style>
/* === ЦВЕТОВАЯ СХЕМА === */
:root {
/* Светлая тема: Темно-серый с легкой желтизной (Warm Charcoal) */
--bs-body-bg: #f8f8f2;
--bs-body-color: #1f1f19;
--bs-primary: #4a4a44;
--bs-primary-rgb: 74, 74, 68;
--bs-link-color: #4a4a44;
--bs-link-hover-color: #2e2e2a;
--bs-focus-ring-color: rgba(74, 74, 68, 0.25);
/* Фон навбара в светлой теме */
--bs-navbar-bg: #b8b8d055; /* Тот же, что и body, или чуть темнее */
--bs-navbar-color: #1f1f19;
}
[data-bs-theme="dark"] {
/* Темная тема: Глубокий черный фон и Стальной акцент */
--bs-body-bg: #151111;
--bs-body-color: #eceff1;
/* Акцент: Светлый серо-голубой */
--bs-primary: #b0bec5;
--bs-primary-rgb: 176, 190, 197;
--bs-link-color: #90caf9;
--bs-link-hover-color: #bbdefb;
--bs-border-color: #37474f;
--bs-border-color-translucent: rgba(255, 255, 255, 0.15);
/* Цвет фокуса для полей ввода */
--bs-focus-ring-color: rgba(176, 190, 197, 0.25);
/* Фон навбара в темной теме */
--bs-navbar-bg: #55558555;
--bs-navbar-color: #b0bec5;
}
/* Небольшие стили для красоты */
body { background-color: var(--bs-body-bg); }
/* Навбар: используем переменную для фона */
.navbar {
background-color: var(--bs-navbar-bg) !important;
border-bottom: 1px solid var(--bs-border-color);
padding: 0; /* Убираем отступы у навбара */
position: sticky;
top: 0;
height: 105px;
z-index: 1000;
backdrop-filter: blur(4px); /* Эффект размытия */
box-shadow: 0 -25px 30px 15px var(--bs-border-color);
}
.navbar-brand {
padding: 0; /* Убираем отступы у бренда */
}
/* Стили для скролла */
.navbar-scrolled {
height: 55px;
}
/* Логотип */
.logo-img {
width: 70%;
margin-left: -3%; /* Немного сдвигаем влево, чтобы буквы ETPGRF логотипа выровнять */
height: 151px; /* Ограничиваем высоту */
object-fit: contain; /* Вписываем, сохраняя пропорции */
}
/* Уменьшаем логотип при скролле */
.navbar-scrolled .logo-img {
height: 78px; /* Компактная высота */
margin-left: -5%;
}
/* === ПЕРЕОПРЕДЕЛЕНИЕ КОМПОНЕНТОВ BOOTSTRAP === */
/* Кнопки Primary */
.btn-primary {
--bs-btn-bg: var(--bs-primary);
--bs-btn-border-color: var(--bs-primary);
--bs-btn-hover-bg: var(--bs-link-hover-color);
--bs-btn-hover-border-color: var(--bs-link-hover-color);
--bs-btn-active-bg: var(--bs-link-hover-color);
--bs-btn-active-border-color: var(--bs-link-hover-color);
}
/* В темной теме текст на кнопке должен быть темным */
[data-bs-theme="dark"] .btn-primary {
--bs-btn-color: #151111;
--bs-btn-hover-color: #151111;
--bs-btn-active-color: #151111;
}
/* Чекбоксы и Радио */
.form-check-input:checked {
background-color: var(--bs-primary);
border-color: var(--bs-primary);
}
/* В темной теме галочка должна быть темной */
[data-bs-theme="dark"] .form-check-input:checked {
/* SVG галочки черного цвета (закодирован в base64) */
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23151111' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
}
/* Фокус на полях ввода */
.form-control:focus, .form-select:focus, .form-check-input:focus {
border-color: var(--bs-primary);
box-shadow: 0 0 0 0.25rem var(--bs-focus-ring-color);
}
.result-box {
background: var(--bs-body-bg);
color: var(--bs-body-color);
border: 1px solid var(--bs-border-color);
border-radius: 0.375rem;
padding: 1rem;
min-height: 300px;
padding-left: 1.5rem;
padding-right: 1.5rem;
white-space: pre-wrap;
font-family: inherit;
}
.cm-editor {
border: 1px solid var(--bs-border-color);
border-radius: 0.375rem;
height: 300px;
}
/* --- Висячая пунктуация (Hanging Punctuation) --- */
.result-box .etp-laquo { margin-left: -0.44em; }
.result-box .etp-ldquo { margin-left: -0.44em; }
.result-box .etp-lpar { margin-left: -0.3em; }
.result-box .etp-lsqb { margin-left: -0.3em; }
.result-box .etp-lcub { margin-left: -0.3em; }
.result-box .etp-raquo { margin-right: -0.44em; }
.result-box .etp-rdquo { margin-right: -0.44em; }
.result-box .etp-rpar { margin-right: -0.3em; }
.result-box .etp-rsqb { margin-right: -0.3em; }
.result-box .etp-rcub { margin-right: -0.3em; }
.result-box .etp-r-dot { margin-right: -0.2em; }
.result-box .etp-r-comma { margin-right: -0.2em; }
.result-box .etp-r-colon { margin-right: -0.2em; }
/* --- Стили для Cookie Banner --- */
#cookie-banner {
backdrop-filter: blur(10px);
border-top: 1px solid var(--bs-border-color);
z-index: 1050;
background-color: var(--bs-navbar-bg);
color: var(--bs-navbar-color); /* Используем цвет навбара для текста */
}
#cookie-banner a {
color: var(--bs-primary);
text-decoration: none;
border-bottom: 1px dotted var(--bs-primary);
}
#cookie-banner a:hover {
border-bottom-style: solid;
color: var(--bs-link-hover-color);
}
#cookie-accept {
color: var(--bs-primary);
border: 1px dashed var(--bs-primary);
background: transparent;
}
#cookie-accept:hover {
background: rgba(var(--bs-primary-rgb), 0.1);
}
</style>
</head>
<body>
@@ -226,108 +52,8 @@
</div>
</div>
{# Bootstrap JS #}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{# Логика куки-баннера и счетчиков #}
<script>
(function () {
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
const logoImg = document.getElementById('logo-img');
const navbar = document.getElementById('main-navbar');
// --- АВТОМАТИЧЕСКОЕ ПЕРЕКЛЮЧЕНИЕ ТЕМЫ (Dark/Light) ---
function updateTheme(e) {
const theme = e.matches ? 'dark' : 'light';
document.documentElement.setAttribute('data-bs-theme', theme);
}
// --- ОБНОВЛЕНИЕ ЛОГОТИПА ПРИ СКРОЛЛЕ И СМЕНЕ ТЕМЫ ---
function updateLogo() {
const isDark = darkModeMediaQuery.matches;
// Используем getBoundingClientRect для определения позиции контента
if (document.getElementById('content-container').getBoundingClientRect().top < 78) {
navbar.classList.add('navbar-scrolled');
logoImg.src = isDark ? logoImg.dataset.srcDarkCompact : logoImg.dataset.srcLightCompact;
} else {
navbar.classList.remove('navbar-scrolled');
logoImg.src = isDark ? logoImg.dataset.srcDark : logoImg.dataset.srcLight;
}
}
// Инициализация
updateTheme(darkModeMediaQuery);
updateLogo();
document.addEventListener('DOMContentLoaded', updateLogo);
// Слушаем скролл
window.addEventListener('scroll', updateLogo);
// Слушаем смену темы
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', updateLogo);
// --- КУКИ ---
const COOKIE_KEY = 'cookie_consent';
const TTL_MS = 60 * 1000; // 1 минута для отладки (потом поставить 90 дней: 90 * 24 * 60 * 60 * 1000 = 7776000000)
const banner = document.getElementById('cookie-banner');
const acceptButton = document.getElementById('cookie-accept');
function loadCounters() {
console.log("Загрузка счетчиков (Яндекс, Google)...");
// Код Яндекс.Метрики
// (function(m,e,t,r,i,k,a){...})(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
// ym(XXXXXX, "init", {...});
// Код Google Analytics
// window.dataLayer = window.dataLayer || [];
// function gtag(){dataLayer.push(arguments);}
// gtag('js', new Date());
// gtag('config', 'G-XXXXXXXXXX');
// Код Top.Mail.Ru
// (function(w, d, c) { ... })(window, document, "topmailru");
// и т.д.
// alert("Отладка. Счетчики загружены (здесь должен быть реальный код счетчиков).");
}
function checkConsent() {
const stored = localStorage.getItem(COOKIE_KEY);
if (!stored) return false;
try {
const data = JSON.parse(stored);
const now = Date.now();
// Проверяем, не истек ли срок
if (now - data.timestamp > TTL_MS) {
localStorage.removeItem(COOKIE_KEY);
return false;
}
return true;
} catch (e) {
return false;
}
}
if (checkConsent()) {
loadCounters();
} else {
banner.style.display = 'block';
}
acceptButton.addEventListener('click', function () {
const data = {
value: true,
timestamp: Date.now()
};
localStorage.setItem(COOKIE_KEY, JSON.stringify(data));
banner.style.display = 'none';
loadCounters();
});
})();
</script>
{# Bootstrap JS #}<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{# Custom JS #}<script src="{% static 'js/base.js' %}" defer></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@@ -249,105 +249,8 @@
<div id="result-area" style="display: none;"></div>
</div>
</div>
<script type="module">
// Импортируем из локального бандла
import {
EditorView,
EditorState,
lineNumbers,
highlightActiveLineGutter,
highlightWhitespace,
highlightTrailingWhitespace,
drawSelection,
keymap,
highlightSpecialChars,
html,
oneDark,
syntaxHighlighting,
defaultHighlightStyle,
bracketMatching,
defaultKeymap,
Compartment
} from "{% static 'codemirror/editor.js' %}";
const resultWrapper = document.getElementById('cm-result-wrapper');
const themeCompartment = new Compartment();
function getTheme() {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? oneDark : [];
}
// Словарь названий для спецсимволов
const charNames = {
0x00A0: "NoBreakable Space (неразрывный пробел — &nbsp;)",
0x00AD: "Soft Hyphen (мягкий перенос — &shy;)",
0x2002: "En Space (полужирный пробел — &ensp;)",
0x2003: "Em Space (жирный пробел — &emsp;)",
0x2007: "Figure Space (цифровой пробел — &numsp;)",
0x2008: "Punctuation Space (пунктуационный пробел — &puncsp;)",
0x2009: "Thin Space (тонкий пробел — &thinsp;)",
0x200A: "Hair Space (толщина волоса — &hairsp;)",
0x200B: "Negative Space (негативный пробел — &NegativeThinSpace;)",
0x200C: "Zero Width Non-Joiner (пробел нулевой ширины, без объединения — &zwj;)",
0x200D: "Zero Width Joiner (пробел нулевой ширины, с объединением — &zwnj;)",
0x200E: "Left-to-Right Mark (изменить направление текста на слева-направо — &lrm;)",
0x200F: "Right-to-Left Mark (изменить направление текста на справа-налево — &rlm;)",
0x205F: "Medium Mathematical Space (средний пробел — &MediumSpace;)",
0x2060: "NoBreak (без разрыва — &NoBreak;)",
0x2062: "Invisible Times (невидимое умножение для семантической разметки математических выражений — &InvisibleTimes;)",
0x2063: "Invisible Comma (невидимая запятая для семантической разметки математических выражений — &InvisibleComma;)",
};
const resultState = EditorState.create({
doc: "Здесь появится результат...",
extensions: [
lineNumbers(),
highlightActiveLineGutter(),
// Подсветка NBSP и других специальных пробелов
highlightSpecialChars({
specialChars: /[\u2002\u00AD\u2003\u2007\u2009\u00a0\u200A\u200B\u200C\u200D\u200E\u200F\u205F\u2060\u2062\u2063]/,
addSpecialChars: true,
render: (code) => {
let span = document.createElement("span");
span.textContent = "•";
span.style.background = "#ff000044"; // Полупрозрачный красный фон
span.style.color = "#ffff00"; // Желтый цвет точки
// Используем словарь для title
span.title = "U+" + code.toString(16).toUpperCase().padStart(4, '0') + " / " + (charNames[code] || "Special Char");
return span;
}
}),
highlightWhitespace(),
highlightTrailingWhitespace(),
drawSelection(),
syntaxHighlighting(defaultHighlightStyle, {fallback: true}),
bracketMatching(),
keymap.of(defaultKeymap),
html(),
themeCompartment.of(getTheme()),
EditorState.readOnly.of(true)
]
});
const resultView = new EditorView({
state: resultState,
parent: resultWrapper
});
document.body.addEventListener('htmx:afterSwap', function (evt) {
if (evt.detail.target.id === 'result-area') {
const newContent = evt.detail.xhr.response;
resultView.dispatch({
changes: {from: 0, to: resultView.state.doc.length, insert: newContent}
});
}
});
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
resultView.dispatch({
effects: themeCompartment.reconfigure(getTheme())
});
});
</script>
{% endblock %}
{% block scripts %}
<script type="module" src="{% static 'js/index.js' %}" defer></script>
{% endblock %}