atualiza versão para 0.0.6; adiciona suporte a temas personalizados e changelog dinâmico na aba Sobre
This commit is contained in:
parent
0fa741c2bf
commit
2479154f63
16
README.md
16
README.md
|
|
@ -9,7 +9,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="https://img.shields.io/badge/VERSION-0.0.5-blue?style=flat-square" />
|
<img src="https://img.shields.io/badge/VERSION-0.0.6-blue?style=flat-square" />
|
||||||
<img src="https://img.shields.io/badge/NODE.JS-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white" />
|
<img src="https://img.shields.io/badge/NODE.JS-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white" />
|
||||||
<img src="https://img.shields.io/badge/DOCKER-ready-2496ED?style=flat-square&logo=docker&logoColor=white" />
|
<img src="https://img.shields.io/badge/DOCKER-ready-2496ED?style=flat-square&logo=docker&logoColor=white" />
|
||||||
<img src="https://img.shields.io/badge/READY-yes-brightgreen?style=flat-square" />
|
<img src="https://img.shields.io/badge/READY-yes-brightgreen?style=flat-square" />
|
||||||
|
|
@ -18,12 +18,24 @@
|
||||||
|
|
||||||
> ⚠️ **AVISO CRÍTICO:** Aplicação em estágio inicial de desenvolvimento. Não use em produção — há risco de perda de dados.
|
> ⚠️ **AVISO CRÍTICO:** Aplicação em estágio inicial de desenvolvimento. Não use em produção — há risco de perda de dados.
|
||||||
|
|
||||||
Versão atual: **0.0.5**
|
Versão atual: **0.0.6**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## <20> Changelog
|
## <20> Changelog
|
||||||
|
|
||||||
|
### [0.0.6] — 2026-05-09
|
||||||
|
|
||||||
|
#### Adicionado
|
||||||
|
- **Seletor de temas:** nova seção na aba Configurações com 11 temas visuais (Padrão, Escuro, Amanhecer, Floresta, Oceano, Púrpura, Rosa, Laranja, Grafite, Safira, Alto Contraste). O tema selecionado é aplicado imediatamente e salvo no navegador.
|
||||||
|
- **Changelog dinâmico:** a aba Sobre agora busca e exibe o changelog diretamente do `README.md` do GitHub, sem necessidade de atualização manual na interface.
|
||||||
|
|
||||||
|
#### Corrigido
|
||||||
|
- **Botões Run/Editar/Excluir:** estilizados usando o sistema de design existente (`.btn`). Run ficou azul/primário, Editar em cinza/secundário e Excluir em vermelho com borda.
|
||||||
|
- **`docker-compose.yml`:** `restart: unless-stopped` descomentado para garantir que o container reinicie automaticamente após uma atualização via botão da aba Sobre.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### [0.0.5] — 2026-05-09
|
### [0.0.5] — 2026-05-09
|
||||||
|
|
||||||
#### Adicionado
|
#### Adicionado
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "dockerbackup-app",
|
"name": "dockerbackup-app",
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"description": "Aplicacao web para backup e restauracao de volumes Docker",
|
"description": "Aplicacao web para backup e restauracao de volumes Docker",
|
||||||
"main": "src/server.js",
|
"main": "src/server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,30 @@ function applyTranslations() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ─── Themes ───────────────────────────────────────────────
|
||||||
|
const VALID_THEMES = new Set([
|
||||||
|
'default','dark','sunrise','forest','ocean','purple','rose','orange','graphite','sapphire','contrast'
|
||||||
|
]);
|
||||||
|
|
||||||
|
function applyTheme(theme) {
|
||||||
|
const safe = VALID_THEMES.has(theme) ? theme : 'default';
|
||||||
|
if (safe === 'default') {
|
||||||
|
document.documentElement.removeAttribute('data-theme');
|
||||||
|
} else {
|
||||||
|
document.documentElement.setAttribute('data-theme', safe);
|
||||||
|
}
|
||||||
|
localStorage.setItem('theme', safe);
|
||||||
|
const sel = document.querySelector('#settingsTheme');
|
||||||
|
if (sel) sel.value = safe;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply saved theme immediately
|
||||||
|
applyTheme(localStorage.getItem('theme') || 'default');
|
||||||
|
|
||||||
|
document.addEventListener('change', (e) => {
|
||||||
|
if (e.target.id === 'settingsTheme') applyTheme(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
// ─── Auth ──────────────────────────────────────────────────
|
// ─── Auth ──────────────────────────────────────────────────
|
||||||
let authToken = localStorage.getItem('authToken') || null;
|
let authToken = localStorage.getItem('authToken') || null;
|
||||||
|
|
||||||
|
|
@ -688,9 +712,9 @@ function backupButtons(profile) {
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<button data-action="run" data-profile-id="${escapeHtml(profile.id)}" class="primary-button small" ${isRunning ? 'disabled' : ''}>${isRunning ? 'Executando...' : 'Run'}</button>
|
<button data-action="run" data-profile-id="${escapeHtml(profile.id)}" class="btn btn--primary btn--sm" ${isRunning ? 'disabled' : ''}>${isRunning ? 'Executando...' : 'Run'}</button>
|
||||||
<button data-action="edit" data-profile-id="${escapeHtml(profile.id)}" class="secondary-button small">Editar</button>
|
<button data-action="edit" data-profile-id="${escapeHtml(profile.id)}" class="btn btn--secondary btn--sm">Editar</button>
|
||||||
<button data-action="delete" data-profile-id="${escapeHtml(profile.id)}" class="ghost-button small">Excluir</button>
|
<button data-action="delete" data-profile-id="${escapeHtml(profile.id)}" class="btn btn--danger btn--sm">Excluir</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
@ -1431,6 +1455,8 @@ function buildLanguageSelect() {
|
||||||
|
|
||||||
async function loadSettingsView() {
|
async function loadSettingsView() {
|
||||||
buildLanguageSelect();
|
buildLanguageSelect();
|
||||||
|
const themeSelect = document.querySelector('#settingsTheme');
|
||||||
|
if (themeSelect) themeSelect.value = localStorage.getItem('theme') || 'default';
|
||||||
try {
|
try {
|
||||||
const settings = await api('/api/settings');
|
const settings = await api('/api/settings');
|
||||||
const select = document.querySelector('#settingsLanguage');
|
const select = document.querySelector('#settingsLanguage');
|
||||||
|
|
@ -1471,17 +1497,66 @@ document.querySelector('#saveSettingsBtn')?.addEventListener('click', async () =
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── About ────────────────────────────────────────────────
|
// ─── About ────────────────────────────────────────────────
|
||||||
|
function markdownInline(raw) {
|
||||||
|
return raw
|
||||||
|
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
||||||
|
.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseChangelogSection(markdown) {
|
||||||
|
// Extract from ## Changelog (or ## 🗂 Changelog etc.) to next ##
|
||||||
|
const start = markdown.search(/^##\s+[^\n]*[Cc]hangelog/m);
|
||||||
|
if (start === -1) return null;
|
||||||
|
const rest = markdown.slice(start);
|
||||||
|
const nextSection = rest.slice(1).search(/^## /m);
|
||||||
|
const section = nextSection === -1 ? rest : rest.slice(0, nextSection + 1);
|
||||||
|
|
||||||
|
const lines = section.split('\n');
|
||||||
|
let html = '';
|
||||||
|
let inList = false;
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const h3 = line.match(/^###\s+(.+)/);
|
||||||
|
const h4 = line.match(/^####\s+(.+)/);
|
||||||
|
const li = line.match(/^-\s+(.+)/);
|
||||||
|
|
||||||
|
if (h3) {
|
||||||
|
if (inList) { html += '</ul>'; inList = false; }
|
||||||
|
html += `<h4>${markdownInline(h3[1].replace(/\[([^\]]+)\]/, '$1'))}</h4>`;
|
||||||
|
} else if (h4) {
|
||||||
|
if (inList) { html += '</ul>'; inList = false; }
|
||||||
|
html += `<h5 class="changelog-section">${markdownInline(h4[1])}</h5>`;
|
||||||
|
} else if (li) {
|
||||||
|
if (!inList) { html += '<ul>'; inList = true; }
|
||||||
|
html += `<li>${markdownInline(li[1])}</li>`;
|
||||||
|
} else if (line.startsWith('---') || line.startsWith('## ')) {
|
||||||
|
if (inList) { html += '</ul>'; inList = false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (inList) html += '</ul>';
|
||||||
|
return html || null;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadAboutView() {
|
async function loadAboutView() {
|
||||||
const currentVerEl = document.querySelector('#aboutCurrentVersion');
|
const currentVerEl = document.querySelector('#aboutCurrentVersion');
|
||||||
const latestVerEl = document.querySelector('#aboutLatestVersion');
|
const latestVerEl = document.querySelector('#aboutLatestVersion');
|
||||||
const updateWrap = document.querySelector('#aboutUpdateWrap');
|
const updateWrap = document.querySelector('#aboutUpdateWrap');
|
||||||
const updateStatus = document.querySelector('#aboutUpdateStatus');
|
const updateStatus = document.querySelector('#aboutUpdateStatus');
|
||||||
const updateBtn = document.querySelector('#aboutUpdateBtn');
|
const updateBtn = document.querySelector('#aboutUpdateBtn');
|
||||||
|
const changelogEl = document.querySelector('#aboutChangelog');
|
||||||
|
|
||||||
if (latestVerEl) latestVerEl.textContent = t('about.checking');
|
if (latestVerEl) latestVerEl.textContent = t('about.checking');
|
||||||
|
|
||||||
try {
|
// Fetch version info and changelog in parallel
|
||||||
const about = await api('/api/about');
|
const [aboutResult, changelogResult] = await Promise.allSettled([
|
||||||
|
api('/api/about'),
|
||||||
|
fetch('https://raw.githubusercontent.com/asabino2/dockerbackup/main/README.md').then((r) => r.text()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Version info
|
||||||
|
if (aboutResult.status === 'fulfilled') {
|
||||||
|
const about = aboutResult.value;
|
||||||
const current = about.currentVersion || '—';
|
const current = about.currentVersion || '—';
|
||||||
const latest = about.latestVersion || null;
|
const latest = about.latestVersion || null;
|
||||||
|
|
||||||
|
|
@ -1499,10 +1574,20 @@ async function loadAboutView() {
|
||||||
if (updateStatus) updateStatus.textContent = t('about.checkError');
|
if (updateStatus) updateStatus.textContent = t('about.checkError');
|
||||||
if (updateBtn) updateBtn.classList.add('hidden');
|
if (updateBtn) updateBtn.classList.add('hidden');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} else {
|
||||||
if (currentVerEl) currentVerEl.textContent = '—';
|
if (currentVerEl) currentVerEl.textContent = '—';
|
||||||
if (latestVerEl) latestVerEl.textContent = '—';
|
if (latestVerEl) latestVerEl.textContent = '—';
|
||||||
showToast(error.message, true);
|
showToast(aboutResult.reason?.message || 'Erro ao buscar versão', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changelog from GitHub README
|
||||||
|
if (changelogEl) {
|
||||||
|
if (changelogResult.status === 'fulfilled') {
|
||||||
|
const html = parseChangelogSection(changelogResult.value);
|
||||||
|
changelogEl.innerHTML = html || '<p class="changelog-loading">Changelog não encontrado.</p>';
|
||||||
|
} else {
|
||||||
|
changelogEl.innerHTML = '<p class="changelog-loading">Não foi possível carregar o changelog.</p>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,23 @@
|
||||||
<select id="settingsLanguage" class="settings-select">
|
<select id="settingsLanguage" class="settings-select">
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-section">
|
||||||
|
<h2>Tema</h2>
|
||||||
|
<p>Personalize a aparência da interface</p>
|
||||||
|
<select id="settingsTheme" class="settings-select">
|
||||||
|
<option value="default">Padrão (Azul)</option>
|
||||||
|
<option value="dark">Escuro</option>
|
||||||
|
<option value="sunrise">Amanhecer</option>
|
||||||
|
<option value="forest">Floresta</option>
|
||||||
|
<option value="ocean">Oceano</option>
|
||||||
|
<option value="purple">Púrpura</option>
|
||||||
|
<option value="rose">Rosa</option>
|
||||||
|
<option value="orange">Laranja</option>
|
||||||
|
<option value="graphite">Grafite</option>
|
||||||
|
<option value="sapphire">Safira</option>
|
||||||
|
<option value="contrast">Alto Contraste</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="settings-section">
|
<div class="settings-section">
|
||||||
<h2 data-i18n="settings.auth">Controle de Acesso</h2>
|
<h2 data-i18n="settings.auth">Controle de Acesso</h2>
|
||||||
<label class="toggle-label">
|
<label class="toggle-label">
|
||||||
|
|
@ -282,28 +299,7 @@
|
||||||
<div class="about-changelog">
|
<div class="about-changelog">
|
||||||
<h3 data-i18n="about.changelog">Últimas alterações</h3>
|
<h3 data-i18n="about.changelog">Últimas alterações</h3>
|
||||||
<div id="aboutChangelog" class="changelog-content">
|
<div id="aboutChangelog" class="changelog-content">
|
||||||
<h4>0.0.4</h4>
|
<p class="changelog-loading">Carregando changelog...</p>
|
||||||
<ul>
|
|
||||||
<li>Correção: contador de arquivos no progresso do backup ultrapassava o total</li>
|
|
||||||
<li>Correção: aba Sobre não exibia a última versão disponível</li>
|
|
||||||
</ul>
|
|
||||||
<h4>0.0.3</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Suporte a múltiplos idiomas (10 idiomas)</li>
|
|
||||||
<li>Aba de configurações com controle de acesso (usuário e senha)</li>
|
|
||||||
<li>Aba "Sobre" com verificação de versão via GitHub e atualização automática</li>
|
|
||||||
</ul>
|
|
||||||
<h4>0.0.2</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Adicionado gerenciamento de Storage Locations</li>
|
|
||||||
<li>Backup incremental com seleção de full backup base</li>
|
|
||||||
<li>Agrupamento visual de backups incrementais sob o full</li>
|
|
||||||
<li>Removidas abas Servers e Naming Rules</li>
|
|
||||||
</ul>
|
|
||||||
<h4>0.0.1</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Versão inicial: backup e restore de volumes Docker</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,168 @@
|
||||||
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
|
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Themes --------------------------------------------- */
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--sidebar-bg: #0a0a14;
|
||||||
|
--sidebar-text: #8892a4;
|
||||||
|
--sidebar-text-active: #e2e8f0;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.1);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.06);
|
||||||
|
--sidebar-accent: #60a5fa;
|
||||||
|
--bg: #0f172a;
|
||||||
|
--surface: #1e293b;
|
||||||
|
--border: #334155;
|
||||||
|
--text: #e2e8f0;
|
||||||
|
--text-muted: #94a3b8;
|
||||||
|
--text-sm: #cbd5e1;
|
||||||
|
--accent: #60a5fa;
|
||||||
|
--accent-hover: #3b82f6;
|
||||||
|
}
|
||||||
|
[data-theme="sunrise"] {
|
||||||
|
--sidebar-bg: #7c2d12;
|
||||||
|
--sidebar-text: #fed7aa;
|
||||||
|
--sidebar-text-active: #fff7ed;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.12);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.07);
|
||||||
|
--sidebar-accent: #fb923c;
|
||||||
|
--bg: #fff7ed;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #fed7aa;
|
||||||
|
--text: #431407;
|
||||||
|
--text-muted: #9a3412;
|
||||||
|
--text-sm: #7c2d12;
|
||||||
|
--accent: #ea580c;
|
||||||
|
--accent-hover: #c2410c;
|
||||||
|
}
|
||||||
|
[data-theme="forest"] {
|
||||||
|
--sidebar-bg: #14532d;
|
||||||
|
--sidebar-text: #bbf7d0;
|
||||||
|
--sidebar-text-active: #f0fdf4;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.1);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.06);
|
||||||
|
--sidebar-accent: #4ade80;
|
||||||
|
--bg: #f0fdf4;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #bbf7d0;
|
||||||
|
--text: #14532d;
|
||||||
|
--text-muted: #166534;
|
||||||
|
--text-sm: #15803d;
|
||||||
|
--accent: #16a34a;
|
||||||
|
--accent-hover: #15803d;
|
||||||
|
}
|
||||||
|
[data-theme="ocean"] {
|
||||||
|
--sidebar-bg: #164e63;
|
||||||
|
--sidebar-text: #a5f3fc;
|
||||||
|
--sidebar-text-active: #ecfeff;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.1);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.06);
|
||||||
|
--sidebar-accent: #22d3ee;
|
||||||
|
--bg: #ecfeff;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #a5f3fc;
|
||||||
|
--text: #164e63;
|
||||||
|
--text-muted: #0e7490;
|
||||||
|
--text-sm: #0891b2;
|
||||||
|
--accent: #0891b2;
|
||||||
|
--accent-hover: #0e7490;
|
||||||
|
}
|
||||||
|
[data-theme="purple"] {
|
||||||
|
--sidebar-bg: #3b0764;
|
||||||
|
--sidebar-text: #e9d5ff;
|
||||||
|
--sidebar-text-active: #faf5ff;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.1);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.06);
|
||||||
|
--sidebar-accent: #c084fc;
|
||||||
|
--bg: #faf5ff;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #e9d5ff;
|
||||||
|
--text: #3b0764;
|
||||||
|
--text-muted: #7e22ce;
|
||||||
|
--text-sm: #6b21a8;
|
||||||
|
--accent: #9333ea;
|
||||||
|
--accent-hover: #7e22ce;
|
||||||
|
}
|
||||||
|
[data-theme="rose"] {
|
||||||
|
--sidebar-bg: #881337;
|
||||||
|
--sidebar-text: #fecdd3;
|
||||||
|
--sidebar-text-active: #fff1f2;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.1);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.06);
|
||||||
|
--sidebar-accent: #fb7185;
|
||||||
|
--bg: #fff1f2;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #fecdd3;
|
||||||
|
--text: #881337;
|
||||||
|
--text-muted: #be123c;
|
||||||
|
--text-sm: #9f1239;
|
||||||
|
--accent: #e11d48;
|
||||||
|
--accent-hover: #be123c;
|
||||||
|
}
|
||||||
|
[data-theme="orange"] {
|
||||||
|
--sidebar-bg: #431407;
|
||||||
|
--sidebar-text: #fed7aa;
|
||||||
|
--sidebar-text-active: #fff7ed;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.1);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.06);
|
||||||
|
--sidebar-accent: #fb923c;
|
||||||
|
--bg: #fff7ed;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #fed7aa;
|
||||||
|
--text: #1c1917;
|
||||||
|
--text-muted: #78350f;
|
||||||
|
--text-sm: #92400e;
|
||||||
|
--accent: #f97316;
|
||||||
|
--accent-hover: #ea580c;
|
||||||
|
}
|
||||||
|
[data-theme="graphite"] {
|
||||||
|
--sidebar-bg: #111827;
|
||||||
|
--sidebar-text: #9ca3af;
|
||||||
|
--sidebar-text-active: #f9fafb;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.08);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.04);
|
||||||
|
--sidebar-accent: #9ca3af;
|
||||||
|
--bg: #f3f4f6;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #d1d5db;
|
||||||
|
--text: #111827;
|
||||||
|
--text-muted: #6b7280;
|
||||||
|
--text-sm: #374151;
|
||||||
|
--accent: #374151;
|
||||||
|
--accent-hover: #1f2937;
|
||||||
|
}
|
||||||
|
[data-theme="sapphire"] {
|
||||||
|
--sidebar-bg: #1e1b4b;
|
||||||
|
--sidebar-text: #c7d2fe;
|
||||||
|
--sidebar-text-active: #eef2ff;
|
||||||
|
--sidebar-active-bg: rgba(255,255,255,0.1);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.06);
|
||||||
|
--sidebar-accent: #818cf8;
|
||||||
|
--bg: #eef2ff;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #c7d2fe;
|
||||||
|
--text: #1e1b4b;
|
||||||
|
--text-muted: #4338ca;
|
||||||
|
--text-sm: #3730a3;
|
||||||
|
--accent: #4f46e5;
|
||||||
|
--accent-hover: #4338ca;
|
||||||
|
}
|
||||||
|
[data-theme="contrast"] {
|
||||||
|
--sidebar-bg: #000000;
|
||||||
|
--sidebar-text: #e5e5e5;
|
||||||
|
--sidebar-text-active: #ffff00;
|
||||||
|
--sidebar-active-bg: rgba(255,255,0,0.15);
|
||||||
|
--sidebar-hover-bg: rgba(255,255,255,0.08);
|
||||||
|
--sidebar-accent: #ffff00;
|
||||||
|
--bg: #ffffff;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--border: #000000;
|
||||||
|
--text: #000000;
|
||||||
|
--text-muted: #333333;
|
||||||
|
--text-sm: #1a1a1a;
|
||||||
|
--accent: #0000cc;
|
||||||
|
--accent-hover: #000099;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
@ -279,9 +441,17 @@ button, input, select {
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn--ghost {
|
.btn--ghost {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-muted);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--danger {
|
||||||
background: #fff0f0;
|
background: #fff0f0;
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
|
border: 1px solid #fecaca;
|
||||||
}
|
}
|
||||||
|
.btn--danger:not(:disabled):hover { background: #fee2e2; opacity: 1; }
|
||||||
|
|
||||||
.btn--sm { padding: 5px 11px; font-size: 12.5px; }
|
.btn--sm { padding: 5px 11px; font-size: 12.5px; }
|
||||||
|
|
||||||
|
|
@ -1020,6 +1190,19 @@ button, input, select {
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
.changelog-content h5.changelog-section {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin: 8px 0 4px;
|
||||||
|
}
|
||||||
|
.changelog-loading {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Modal ----------------------------------------------- */
|
/* --- Modal ----------------------------------------------- */
|
||||||
.modal {
|
.modal {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue