236 lines
9.0 KiB
HTML
236 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta
|
||
name="viewport"
|
||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||
/>
|
||
<title>NoteJS - Система заметок</title>
|
||
|
||
<!-- Предотвращение мерцания темы -->
|
||
<script>
|
||
(function () {
|
||
try {
|
||
// Получаем сохраненную тему
|
||
const savedTheme = localStorage.getItem("theme");
|
||
// Получаем системные предпочтения
|
||
const systemPrefersDark = window.matchMedia(
|
||
"(prefers-color-scheme: dark)"
|
||
).matches;
|
||
|
||
// Определяем тему: сохраненная или системная
|
||
const theme = savedTheme || (systemPrefersDark ? "dark" : "light");
|
||
|
||
// Функция для конвертации hex в RGB
|
||
function hexToRgb(hex) {
|
||
const cleanHex = hex.replace("#", "");
|
||
const r = parseInt(cleanHex.substring(0, 2), 16);
|
||
const g = parseInt(cleanHex.substring(2, 4), 16);
|
||
const b = parseInt(cleanHex.substring(4, 6), 16);
|
||
return `${r}, ${g}, ${b}`;
|
||
}
|
||
|
||
// Получаем и устанавливаем accentColor
|
||
const savedAccentColor = localStorage.getItem("accentColor");
|
||
const accentColor = savedAccentColor || "#007bff";
|
||
|
||
// Устанавливаем тему и переменные до загрузки CSS
|
||
if (theme === "dark") {
|
||
document.documentElement.setAttribute("data-theme", "dark");
|
||
} else {
|
||
document.documentElement.setAttribute("data-theme", "light");
|
||
}
|
||
|
||
// Устанавливаем CSS переменные для accent цвета
|
||
document.documentElement.style.setProperty("--accent-color", accentColor);
|
||
document.documentElement.style.setProperty("--accent-color-rgb", hexToRgb(accentColor));
|
||
|
||
// Устанавливаем цвет для meta theme-color
|
||
const themeColorMeta = document.querySelector('meta[name="theme-color"]');
|
||
if (themeColorMeta) {
|
||
themeColorMeta.setAttribute(
|
||
"content",
|
||
theme === "dark" ? "#1a1a1a" : accentColor
|
||
);
|
||
}
|
||
} catch (e) {
|
||
// В случае ошибки устанавливаем светлую тему по умолчанию
|
||
document.documentElement.setAttribute("data-theme", "light");
|
||
document.documentElement.style.setProperty("--accent-color", "#007bff");
|
||
document.documentElement.style.setProperty("--accent-color-rgb", "0, 123, 255");
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
<!-- Критические стили темы для предотвращения flash эффекта -->
|
||
<style>
|
||
:root {
|
||
--accent-color: #007bff;
|
||
|
||
/* Светлая тема (по умолчанию) */
|
||
--bg-primary: #f5f5f5;
|
||
--text-primary: #333333;
|
||
--border-primary: #e0e0e0;
|
||
--shadow-light: rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
/* Темная тема */
|
||
[data-theme="dark"] {
|
||
--bg-primary: #1a1a1a;
|
||
--text-primary: #ffffff;
|
||
--border-primary: #404040;
|
||
--shadow-light: rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
/* Применяем стили сразу к body для предотвращения flash */
|
||
body {
|
||
background-color: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
transition: background-color 0.3s ease, color 0.3s ease;
|
||
}
|
||
</style>
|
||
|
||
<!-- PWA Meta Tags -->
|
||
<meta
|
||
name="description"
|
||
content="NoteJS - современная система заметок с поддержкой Markdown, изображений, тегов и календаря"
|
||
/>
|
||
<meta name="theme-color" content="#007bff" />
|
||
<meta name="mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
<meta
|
||
name="apple-mobile-web-app-status-bar-style"
|
||
content="black-translucent"
|
||
/>
|
||
<meta name="apple-mobile-web-app-title" content="NoteJS" />
|
||
<meta name="apple-touch-fullscreen" content="yes" />
|
||
<meta name="msapplication-TileColor" content="#007bff" />
|
||
<meta name="msapplication-config" content="/browserconfig.xml" />
|
||
<meta name="msapplication-TileImage" content="/icons/icon-144x144.png" />
|
||
<meta name="application-name" content="NoteJS" />
|
||
<meta name="format-detection" content="telephone=no" />
|
||
|
||
<!-- Icons -->
|
||
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
|
||
<link
|
||
rel="icon"
|
||
type="image/png"
|
||
sizes="32x32"
|
||
href="/icons/icon-32x32.png"
|
||
/>
|
||
<link
|
||
rel="icon"
|
||
type="image/png"
|
||
sizes="16x16"
|
||
href="/icons/icon-16x16.png"
|
||
/>
|
||
<link rel="apple-touch-icon" sizes="57x57" href="/icons/icon-48x48.png" />
|
||
<link rel="apple-touch-icon" sizes="60x60" href="/icons/icon-48x48.png" />
|
||
<link rel="apple-touch-icon" sizes="72x72" href="/icons/icon-72x72.png" />
|
||
<link rel="apple-touch-icon" sizes="76x76" href="/icons/icon-72x72.png" />
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="114x114"
|
||
href="/icons/icon-128x128.png"
|
||
/>
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="120x120"
|
||
href="/icons/icon-128x128.png"
|
||
/>
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="144x144"
|
||
href="/icons/icon-144x144.png"
|
||
/>
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="152x152"
|
||
href="/icons/icon-152x152.png"
|
||
/>
|
||
<link
|
||
rel="apple-touch-icon"
|
||
sizes="180x180"
|
||
href="/icons/icon-192x192.png"
|
||
/>
|
||
<link rel="mask-icon" href="/icon.svg" color="#007bff" />
|
||
|
||
<!-- Manifest -->
|
||
<link rel="manifest" href="/manifest.json" />
|
||
<script type="module" crossorigin src="/assets/index-42KwbWCP.js"></script>
|
||
<link rel="stylesheet" crossorigin href="/assets/index-DK8OUj6L.css">
|
||
<link rel="manifest" href="/manifest.webmanifest"><script id="vite-plugin-pwa:register-sw" src="/registerSW.js"></script></head>
|
||
<body>
|
||
<div id="root">
|
||
<!-- Индикатор загрузки до монтирования React -->
|
||
<div id="initial-loading" style="
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 9999;
|
||
">
|
||
<style>
|
||
#initial-loading-spinner {
|
||
width: 50px;
|
||
height: 50px;
|
||
border: 4px solid transparent;
|
||
border-top: 4px solid var(--accent-color, #007bff);
|
||
border-right: 4px solid var(--accent-color, #007bff);
|
||
border-bottom: 4px solid transparent;
|
||
border-left: 4px solid transparent;
|
||
border-radius: 50%;
|
||
animation: initial-loading-spin 0.8s linear infinite;
|
||
opacity: 0.8;
|
||
}
|
||
@keyframes initial-loading-spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
</style>
|
||
<div id="initial-loading-spinner"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Скрываем индикатор загрузки сразу после загрузки DOM
|
||
// React удалит этот элемент при первом рендере через createRoot
|
||
(function() {
|
||
// Используем MutationObserver для отслеживания изменений в #root
|
||
const observer = new MutationObserver(function(mutations) {
|
||
mutations.forEach(function(mutation) {
|
||
// Если React начал добавлять элементы в #root, удаляем индикатор
|
||
if (mutation.addedNodes.length > 0) {
|
||
const loadingEl = document.getElementById('initial-loading');
|
||
if (loadingEl && loadingEl.parentNode) {
|
||
loadingEl.parentNode.removeChild(loadingEl);
|
||
}
|
||
observer.disconnect();
|
||
}
|
||
});
|
||
});
|
||
|
||
// Начинаем наблюдение за изменениями в #root
|
||
const root = document.getElementById('root');
|
||
if (root) {
|
||
observer.observe(root, { childList: true, subtree: true });
|
||
|
||
// Фолбэк: если через 2 секунды элемент все еще есть, удаляем вручную
|
||
setTimeout(function() {
|
||
const loadingEl = document.getElementById('initial-loading');
|
||
if (loadingEl && loadingEl.parentNode) {
|
||
loadingEl.parentNode.removeChild(loadingEl);
|
||
}
|
||
observer.disconnect();
|
||
}, 2000);
|
||
}
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|