Update subscription.template.ts

black theme and theme chooser
This commit is contained in:
iqubik
2026-03-28 19:32:57 +03:00
parent 629a1683b5
commit 9fdbde3cab
@@ -104,7 +104,7 @@ export function generateSubscriptionHtmlWithQr(
transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease; transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease;
} }
button { .action-btn {
background-color: var(--button-bg); background-color: var(--button-bg);
color: white; color: white;
border: none; border: none;
@@ -120,8 +120,37 @@ export function generateSubscriptionHtmlWithQr(
gap: 8px; gap: 8px;
} }
button:hover { background-color: var(--button-hover); } .action-btn:hover { background-color: var(--button-hover); }
button:active { transform: scale(0.98); } .action-btn:active { transform: scale(0.98); }
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background-color: var(--bg-paper);
color: var(--text-primary);
border: 1px solid var(--border-color);
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: background-color 0.3s, color 0.3s, border-color 0.3s;
padding: 0;
z-index: 1000;
}
.theme-toggle:hover {
background-color: var(--link-box-bg);
}
.theme-toggle svg {
width: 24px;
height: 24px;
}
.note { .note {
margin-top: 20px; margin-top: 20px;
@@ -130,23 +159,35 @@ export function generateSubscriptionHtmlWithQr(
transition: color 0.3s ease; transition: color 0.3s ease;
} }
.error-icon { .header-icon {
width: 64px; width: 64px;
height: 64px; height: 64px;
margin: 0 auto 20px; margin: 0 auto 20px;
color: var(--error-color); color: var(--button-bg);
} }
#subscription-links { display: none; } #subscription-links { display: none; }
</style> </style>
</head> </head>
<body> <body>
<!-- Кнопка смены темы -->
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Переключить тему">
<svg id="icon-sun" style="display: none;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg id="icon-moon" style="display: none;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>
<div class="card"> <div class="card">
<svg class="error-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="header-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v8m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" />
</svg> </svg>
<h2>${subscriptionName}</h2> <h2>${subscriptionName}</h2>
<p style="color: var(--text-secondary);">Отсканируйте QR-код в приложении Happ, v2RayTun или Streisand</p> <p style="color: var(--text-secondary); line-height: 1.5; margin-bottom: 12px;">
Отсканируйте QR-код в приложениях<br>Happ, v2RayTun или Streisand
</p>
<div class="qr-box"> <div class="qr-box">
<img src="${qrDataUrl}" alt="QR Code" /> <img src="${qrDataUrl}" alt="QR Code" />
@@ -154,40 +195,60 @@ export function generateSubscriptionHtmlWithQr(
<div class="link-box" id="link-text">${currentUrl}</div> <div class="link-box" id="link-text">${currentUrl}</div>
<button onclick="copyLink()"> <button class="action-btn" onclick="copyLink()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="white"><path d="M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z"/></svg> <svg width="20" height="20" viewBox="0 0 24 24" fill="white"><path d="M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z"/></svg>
Копировать ссылку Копировать ссылку
</button> </button>
<div class="note">Для автоматического обновления конфигов используйте эту ссылку</div> <div class="note">Для автоматического обновления конфигов<br>используйте эту ссылку</div>
</div> </div>
<textarea id="subscription-links">${base64Config}</textarea> <textarea id="subscription-links">${base64Config}</textarea>
<script> <script>
// Синхронизация темы с основным приложением через localStorage // Функция применения темы
(function() { function applyTheme() {
function applyTheme() { let themeMode = localStorage.getItem('themeMode');
const themeMode = localStorage.getItem('themeMode');
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches; // ТЁМНАЯ ТЕМА ПО УМОЛЧАНИЮ, если значение не задано
if (!themeMode) {
// 3DP-MANAGER использует themeMode: light, dark, system themeMode = 'dark';
if (themeMode === 'dark' || (themeMode === 'system' && systemDark)) { localStorage.setItem('themeMode', 'dark');
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.setAttribute('data-theme', 'light');
}
} }
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const isDark = themeMode === 'dark' || (themeMode === 'system' && systemDark);
if (isDark) {
document.documentElement.setAttribute('data-theme', 'dark');
document.getElementById('icon-sun').style.display = 'block';
document.getElementById('icon-moon').style.display = 'none';
} else {
document.documentElement.setAttribute('data-theme', 'light');
document.getElementById('icon-sun').style.display = 'none';
document.getElementById('icon-moon').style.display = 'block';
}
}
// Глобальная функция переключения темы по кнопке
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
localStorage.setItem('themeMode', newTheme);
applyTheme();
}
// Инициализация при загрузке
(function() {
applyTheme(); applyTheme();
// Слушаем изменения темы в localStorage // Слушаем изменения темы из других вкладок
window.addEventListener('storage', (e) => { window.addEventListener('storage', (e) => {
if (e.key === 'themeMode') { if (e.key === 'themeMode') {
applyTheme(); applyTheme();
} }
}); });
// Слушаем изменения системной темы // Слушаем системные настройки (если выбрана системная тема)
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
const themeMode = localStorage.getItem('themeMode'); const themeMode = localStorage.getItem('themeMode');
if (themeMode === 'system') { if (themeMode === 'system') {
@@ -199,10 +260,10 @@ export function generateSubscriptionHtmlWithQr(
function copyLink() { function copyLink() {
const link = document.getElementById('link-text').innerText; const link = document.getElementById('link-text').innerText;
navigator.clipboard.writeText(link).then(() => { navigator.clipboard.writeText(link).then(() => {
const btn = document.querySelector('button'); const btn = document.querySelector('.action-btn');
const originalText = btn.innerHTML; const originalText = btn.innerHTML;
btn.innerHTML = 'Скопировано!'; btn.innerHTML = 'Скопировано!';
btn.style.backgroundColor = '#2e7d32'; btn.style.backgroundColor = 'var(--button-success)';
setTimeout(() => { setTimeout(() => {
btn.innerHTML = originalText; btn.innerHTML = originalText;
btn.style.backgroundColor = 'var(--button-bg)'; btn.style.backgroundColor = 'var(--button-bg)';
@@ -317,6 +378,35 @@ export function generateErrorHtml(
opacity: 0.9; opacity: 0.9;
} }
.theme-toggle {
position: fixed;
top: 20px;
right: 20px;
background-color: var(--bg-paper);
color: var(--text-primary);
border: 1px solid var(--border-color);
width: 44px;
height: 44px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: background-color 0.3s, color 0.3s, border-color 0.3s;
padding: 0;
z-index: 1000;
}
.theme-toggle:hover {
background-color: var(--error-bg);
}
.theme-toggle svg {
width: 24px;
height: 24px;
}
.note { .note {
margin-top: 20px; margin-top: 20px;
font-size: 12px; font-size: 12px;
@@ -326,6 +416,16 @@ export function generateErrorHtml(
</style> </style>
</head> </head>
<body> <body>
<!-- Кнопка смены темы -->
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Переключить тему">
<svg id="icon-sun" style="display: none;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
<svg id="icon-moon" style="display: none;" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
</svg>
</button>
<div class="card"> <div class="card">
<svg class="error-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg class="error-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v8m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v8m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
@@ -337,19 +437,40 @@ export function generateErrorHtml(
</div> </div>
<script> <script>
// Синхронизация темы с основным приложением через localStorage // Функция применения темы
(function() { function applyTheme() {
function applyTheme() { let themeMode = localStorage.getItem('themeMode');
const themeMode = localStorage.getItem('themeMode');
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches; // ТЁМНАЯ ТЕМА ПО УМОЛЧАНИЮ
if (!themeMode) {
if (themeMode === 'dark' || (themeMode === 'system' && systemDark)) { themeMode = 'dark';
document.documentElement.setAttribute('data-theme', 'dark'); localStorage.setItem('themeMode', 'dark');
} else {
document.documentElement.setAttribute('data-theme', 'light');
}
} }
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const isDark = themeMode === 'dark' || (themeMode === 'system' && systemDark);
if (isDark) {
document.documentElement.setAttribute('data-theme', 'dark');
document.getElementById('icon-sun').style.display = 'block';
document.getElementById('icon-moon').style.display = 'none';
} else {
document.documentElement.setAttribute('data-theme', 'light');
document.getElementById('icon-sun').style.display = 'none';
document.getElementById('icon-moon').style.display = 'block';
}
}
// Глобальная функция переключения темы по кнопке
function toggleTheme() {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
localStorage.setItem('themeMode', newTheme);
applyTheme();
}
// Инициализация при загрузке
(function() {
applyTheme(); applyTheme();
window.addEventListener('storage', (e) => { window.addEventListener('storage', (e) => {