Custom themes

This commit is contained in:
Zan
2025-12-01 21:29:33 +01:00
parent 08203f135b
commit 5fbe47f5ea
48 changed files with 1354 additions and 46 deletions

View File

@@ -245,3 +245,496 @@ h2 {
font-size:18px;
vertical-align:middle;
}
.custom-themes-section {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--border-border, var(--text-teritary));
}
.custom-themes-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
font-weight: 600;
font-size: 14px;
color: var(--text-primary);
}
.add-theme-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 8px;
background: var(--accent-15);
color: var(--accent-accent);
cursor: pointer;
transition: all 0.2s ease;
}
.add-theme-btn:hover {
background: var(--accent-accent);
color: var(--button-secondaryFill);
}
.custom-themes-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
min-height: 50px;
}
.custom-theme-option {
position: relative;
display: flex;
flex-direction: column;
background: var(--button-secondaryFill);
border-radius: 12px;
overflow: hidden;
border: 1px solid var(--border-border, var(--text-teritary));
transition: all 0.2s ease;
}
.custom-theme-option:hover {
border-color: var(--accent-accent);
transform: translateY(-2px);
}
.custom-theme-option.active {
border-color: var(--accent-accent);
box-shadow: 0 0 0 2px var(--accent-15);
}
.custom-theme-option .theme-preview {
height: 70px;
border-radius: 0;
}
.custom-theme-option .theme-info {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
background: var(--button-secondaryFill);
border-top: 1px solid var(--border-border, var(--text-teritary));
gap: 8px;
}
.custom-theme-option .theme-name {
font-size: 12px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
min-width: 0;
}
.custom-theme-option .theme-actions {
display: flex;
gap: 4px;
flex-shrink: 0;
}
.theme-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border: none;
border-radius: 6px;
background: var(--accent-15);
color: var(--accent-accent);
cursor: pointer;
transition: all 0.2s ease;
}
.theme-action-btn:hover {
background: var(--accent-accent);
color: var(--button-secondaryFill);
}
.theme-action-btn.delete-theme {
background: rgba(255, 80, 80, 0.15);
color: #ff5050;
}
.theme-action-btn.delete-theme:hover {
background: #ff5050;
color: white;
}
.theme-action-btn .material-icons-round {
font-size: 16px;
}
.no-custom-themes {
grid-column: 1 / -1;
text-align: center;
color: var(--text-secondary);
font-size: 12px;
padding: 20px 0;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 16px;
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-overlay.active {
display: flex;
}
.modal-content {
background: var(--card-card);
border-radius: 20px;
width: 100%;
max-width: 420px;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.4);
animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid var(--border-border, var(--text-teritary));
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px;
border-bottom: 1px solid var(--border-border, var(--text-teritary));
}
.modal-header h3 {
font-size: 18px;
font-weight: 700;
color: var(--text-primary);
margin: 0;
}
.modal-close {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border: none;
border-radius: 10px;
background: var(--button-secondaryFill);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.modal-close:hover {
background: var(--error-15);
color: var(--error-accent);
transform: rotate(90deg);
}
.modal-close .material-icons-round {
font-size: 20px;
}
.modal-body {
padding: 20px;
}
.modal-footer {
display: flex;
gap: 8px;
padding: 16px;
border-top: 1px solid var(--border-border, var(--text-teritary));
}
.modal-footer button {
flex: 1;
padding: 10px 16px;
border: none;
border-radius: 8px;
font-weight: 500;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
font-family: "Montserrat", serif;
}
.btn-primary {
background: var(--accent-accent);
color: #000;
}
.btn-primary:hover {
opacity: 0.9;
}
.btn-secondary {
background: var(--button-secondaryFill);
color: var(--text-primary);
}
.btn-secondary:hover {
background: var(--accent-15);
}
.theme-form .form-group {
margin-bottom: 16px;
}
.theme-form label {
display: block;
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 8px;
}
.theme-form input[type="text"] {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border-border, var(--text-teritary));
border-radius: 8px;
background: var(--button-secondaryFill);
color: var(--text-primary);
font-size: 14px;
font-family: "Montserrat", serif;
outline: none;
transition: border-color 0.2s ease;
}
.theme-form input[type="text"]:focus {
border-color: var(--accent-accent);
}
.mode-selector {
display: flex;
gap: 8px;
}
.mode-option {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 10px;
border: 1px solid var(--border-border, var(--text-teritary));
border-radius: 8px;
background: var(--button-secondaryFill);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s ease;
font-family: "Montserrat", serif;
font-size: 13px;
}
.mode-option:hover {
border-color: var(--accent-accent);
}
.mode-option.active {
background: var(--accent-15);
border-color: var(--accent-accent);
color: var(--accent-accent);
}
.color-section {
margin-top: 20px;
}
.color-section h4 {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 12px 0;
}
.color-group {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.color-group label {
font-size: 13px;
color: var(--text-secondary);
margin: 0;
}
.color-input-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.color-input-wrapper input[type="color"] {
width: 32px;
height: 32px;
border: none;
border-radius: 6px;
cursor: pointer;
padding: 0;
background: transparent;
}
.color-input-wrapper input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
.color-input-wrapper input[type="color"]::-webkit-color-swatch {
border: none;
border-radius: 6px;
}
.color-hex {
width: 80px;
padding: 6px 8px;
border: 1px solid var(--border-border, var(--text-teritary));
border-radius: 6px;
background: var(--button-secondaryFill);
color: var(--text-primary);
font-size: 12px;
font-family: monospace;
text-transform: uppercase;
}
.theme-preview-section {
margin-top: 20px;
}
.theme-preview-section h4 {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 12px 0;
}
.live-preview {
width: 100%;
height: 80px;
border-radius: 8px;
overflow: hidden;
position: relative;
}
.live-preview .preview-header {
height: 30%;
width: 100%;
}
.live-preview .preview-content {
padding: 8px;
}
.live-preview .preview-card {
height: 20px;
border-radius: 4px;
margin-bottom: 4px;
}
.live-preview .preview-text {
font-size: 10px;
}
.share-code-wrapper {
position: relative;
margin-top: 12px;
}
.share-code-wrapper textarea,
#importCode {
width: 100%;
min-height: 80px;
padding: 12px;
border: 1px solid var(--border-border, var(--text-teritary));
border-radius: 8px;
background: var(--button-secondaryFill);
color: var(--text-primary);
font-family: monospace;
font-size: 12px;
resize: none;
outline: none;
}
.share-code-wrapper textarea:focus,
#importCode:focus {
border-color: var(--accent-accent);
}
.copy-btn {
position: absolute;
top: 8px;
right: 8px;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 6px;
background: var(--accent-15);
color: var(--accent-accent);
cursor: pointer;
transition: all 0.2s ease;
}
.copy-btn:hover {
background: var(--accent-accent);
color: var(--button-secondaryFill);
}
.copy-btn.copied {
background: var(--grades-4);
color: white;
}
.error-message {
color: var(--error-accent);
font-size: 12px;
margin-top: 8px;
min-height: 16px;
}
.import-modal .modal-body p {
color: var(--text-secondary);
font-size: 13px;
margin-bottom: 8px;
}
#importCode {
margin-top: 8px;
}

View File

@@ -51,6 +51,17 @@
<span class="theme-name" data-i18n="settings.themes.dark_green">Sötét Zöld</span>
</button>
</div>
<div class="custom-themes-section">
<div class="custom-themes-header">
<span data-i18n="settings.custom_themes.title">Egyéni témák</span>
<button class="add-theme-btn" id="addCustomTheme">
<span class="material-icons-round">add</span>
</button>
</div>
<div class="theme-grid custom-themes-grid" id="customThemesGrid">
</div>
</div>
</div>
<div class="setting-section">
<div class="setting-header">
@@ -107,6 +118,123 @@
<div class="version-info" id="version">v1.3.0</div>
</footer>
</div>
<div class="modal-overlay" id="themeEditorModal">
<div class="modal-content theme-editor-modal">
<div class="modal-header">
<h3 id="themeEditorTitle" data-i18n="settings.custom_themes.create">Új téma létrehozása</h3>
<button class="modal-close" id="closeThemeEditor">
<span class="material-icons-round">close</span>
</button>
</div>
<div class="modal-body">
<div class="theme-form">
<div class="form-group">
<label for="themeName" data-i18n="settings.custom_themes.name">Téma neve</label>
<input type="text" id="themeName" placeholder="Pl. Kék éjszaka">
</div>
<div class="form-group">
<label data-i18n="settings.custom_themes.mode">Mód</label>
<div class="mode-selector">
<button class="mode-option active" data-mode="dark">
<span class="material-icons-round">dark_mode</span>
<span data-i18n="settings.custom_themes.dark_mode">Sötét</span>
</button>
<button class="mode-option" data-mode="light">
<span class="material-icons-round">light_mode</span>
<span data-i18n="settings.custom_themes.light_mode">Világos</span>
</button>
</div>
</div>
<div class="color-section">
<h4 data-i18n="settings.custom_themes.colors">Színek</h4>
<div class="color-group">
<label data-i18n="settings.custom_themes.accent_color">Kiemelő szín</label>
<div class="color-input-wrapper">
<input type="color" id="accentColor" value="#A7DC22">
<input type="text" class="color-hex" id="accentColorHex" value="#A7DC22">
</div>
</div>
<div class="color-group">
<label data-i18n="settings.custom_themes.background_color">Háttér szín</label>
<div class="color-input-wrapper">
<input type="color" id="backgroundColor" value="#0D1202">
<input type="text" class="color-hex" id="backgroundColorHex" value="#0D1202">
</div>
</div>
<div class="color-group">
<label data-i18n="settings.custom_themes.card_color">Kártya szín</label>
<div class="color-input-wrapper">
<input type="color" id="cardColor" value="#141905">
<input type="text" class="color-hex" id="cardColorHex" value="#141905">
</div>
</div>
<div class="color-group">
<label data-i18n="settings.custom_themes.text_color">Szöveg szín</label>
<div class="color-input-wrapper">
<input type="color" id="textColor" value="#EAF7CC">
<input type="text" class="color-hex" id="textColorHex" value="#EAF7CC">
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="cancelThemeEditor" data-i18n="common.cancel">Mégse</button>
<button class="btn-primary" id="saveTheme" data-i18n="common.save">Mentés</button>
</div>
</div>
</div>
<div class="modal-overlay" id="shareThemeModal">
<div class="modal-content share-modal">
<div class="modal-header">
<h3 data-i18n="settings.custom_themes.share">Téma megosztása</h3>
<button class="modal-close" id="closeShareModal">
<span class="material-icons-round">close</span>
</button>
</div>
<div class="modal-body">
<p data-i18n="settings.custom_themes.share_description">Másold ki a kódot és oszd meg másokkal:</p>
<div class="share-code-wrapper">
<textarea id="shareCode" readonly></textarea>
<button class="copy-btn" id="copyShareCode">
<span class="material-icons-round">content_copy</span>
</button>
</div>
</div>
<div class="modal-footer">
<button class="btn-primary" id="closeShareModalBtn" data-i18n="common.close">Bezárás</button>
</div>
</div>
</div>
<div class="modal-overlay" id="importThemeModal">
<div class="modal-content import-modal">
<div class="modal-header">
<h3 data-i18n="settings.custom_themes.import">Téma importálása</h3>
<button class="modal-close" id="closeImportModal">
<span class="material-icons-round">close</span>
</button>
</div>
<div class="modal-body">
<p data-i18n="settings.custom_themes.import_description">Illeszd be a téma kódot:</p>
<textarea id="importCode" placeholder="Illessze be a téma kódot ide..."></textarea>
<p class="error-message" id="importError"></p>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="cancelImport" data-i18n="common.cancel">Mégse</button>
<button class="btn-primary" id="confirmImport" data-i18n="settings.custom_themes.import">Importálás</button>
</div>
</div>
</div>
<script src="../tools/storageManager.js"></script>
<script src="../global/language.js"></script>
<script src="index.js"></script>

View File

@@ -3,6 +3,311 @@ document.addEventListener("DOMContentLoaded", async () => {
await new Promise((resolve) => setTimeout(resolve, 10));
}
let customThemes = [];
let editingThemeId = null;
async function loadCustomThemes() {
try {
const saved = await storageManager.get("customThemes", []);
customThemes = Array.isArray(saved) ? saved : [];
renderCustomThemes();
} catch (error) {
console.error("Error loading custom themes:", error);
customThemes = [];
}
}
async function saveCustomThemes() {
try {
await storageManager.set("customThemes", customThemes);
} catch (error) {
console.error("Error saving custom themes:", error);
}
}
function renderCustomThemes() {
const grid = document.getElementById("customThemesGrid");
if (!grid) return;
if (customThemes.length === 0) {
grid.innerHTML = `<div class="no-custom-themes" data-i18n="settings.custom_themes.no_themes">Még nincsenek egyéni témák</div>`;
if (window.LanguageManager) {
window.LanguageManager.updatePageTranslations();
}
return;
}
grid.innerHTML = customThemes.map(theme => `
<button class="theme-option custom-theme-option" data-theme="custom-${theme.id}">
<div class="theme-preview" style="background: ${theme.colors.background};">
<div class="preview-header" style="background: ${theme.colors.card};"></div>
<div class="preview-content">
<div class="preview-card" style="background: ${theme.colors.accent}20; border: 1px solid ${theme.colors.accent};"></div>
</div>
</div>
<div class="theme-info">
<span class="theme-name">${theme.name}</span>
<div class="theme-actions">
<span class="theme-action-btn edit-theme" data-id="${theme.id}" title="Szerkesztés">
<span class="material-icons-round">edit</span>
</span>
<span class="theme-action-btn share-theme" data-id="${theme.id}" title="Megosztás">
<span class="material-icons-round">share</span>
</span>
<span class="theme-action-btn delete-theme" data-id="${theme.id}" title="Törlés">
<span class="material-icons-round">delete</span>
</span>
</div>
</div>
</button>
`).join("");
grid.querySelectorAll(".custom-theme-option").forEach(btn => {
btn.addEventListener("click", (e) => {
if (e.target.closest(".theme-action-btn")) return;
const themeId = btn.dataset.theme;
applyTheme(themeId);
});
});
grid.querySelectorAll(".edit-theme").forEach(btn => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const id = btn.dataset.id;
editTheme(id);
});
});
grid.querySelectorAll(".share-theme").forEach(btn => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const id = btn.dataset.id;
shareTheme(id);
});
});
grid.querySelectorAll(".delete-theme").forEach(btn => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const id = btn.dataset.id;
deleteTheme(id);
});
});
updateThemeButtons(getCurrentTheme());
}
function openThemeEditor(theme = null) {
const modal = document.getElementById("themeEditorModal");
const titleEl = document.getElementById("themeEditorTitle");
if (theme) {
editingThemeId = theme.id;
titleEl.setAttribute("data-i18n", "settings.custom_themes.edit");
titleEl.textContent = window.LanguageManager ? window.LanguageManager.t("settings.custom_themes.edit") : "Téma szerkesztése";
document.getElementById("themeName").value = theme.name;
document.getElementById("accentColor").value = theme.colors.accent;
document.getElementById("accentColorHex").value = theme.colors.accent;
document.getElementById("backgroundColor").value = theme.colors.background;
document.getElementById("backgroundColorHex").value = theme.colors.background;
document.getElementById("cardColor").value = theme.colors.card;
document.getElementById("cardColorHex").value = theme.colors.card;
document.getElementById("textColor").value = theme.colors.text;
document.getElementById("textColorHex").value = theme.colors.text;
document.querySelectorAll(".mode-option").forEach(btn => {
btn.classList.toggle("active", btn.dataset.mode === theme.mode);
});
} else {
editingThemeId = null;
titleEl.setAttribute("data-i18n", "settings.custom_themes.create");
titleEl.textContent = window.LanguageManager ? window.LanguageManager.t("settings.custom_themes.create") : "Új téma létrehozása";
document.getElementById("themeName").value = "";
document.getElementById("accentColor").value = "#A7DC22";
document.getElementById("accentColorHex").value = "#A7DC22";
document.getElementById("backgroundColor").value = "#0D1202";
document.getElementById("backgroundColorHex").value = "#0D1202";
document.getElementById("cardColor").value = "#141905";
document.getElementById("cardColorHex").value = "#141905";
document.getElementById("textColor").value = "#EAF7CC";
document.getElementById("textColorHex").value = "#EAF7CC";
document.querySelectorAll(".mode-option").forEach(btn => {
btn.classList.toggle("active", btn.dataset.mode === "dark");
});
}
updateLivePreview();
modal.classList.add("active");
}
function closeThemeEditor() {
const modal = document.getElementById("themeEditorModal");
modal.classList.remove("active");
editingThemeId = null;
}
function updateLivePreview() {
const preview = document.getElementById("livePreview");
if (!preview) return;
const backgroundColor = document.getElementById("backgroundColor").value;
const cardColor = document.getElementById("cardColor").value;
const accentColor = document.getElementById("accentColor").value;
const textColor = document.getElementById("textColor").value;
preview.style.background = backgroundColor;
preview.querySelector(".preview-header").style.background = cardColor;
preview.querySelector(".preview-card").style.background = `${accentColor}20`;
preview.querySelector(".preview-card").style.border = `1px solid ${accentColor}`;
preview.querySelector(".preview-text").style.color = textColor;
}
function saveThemeFromEditor() {
const name = document.getElementById("themeName").value.trim();
if (!name) {
document.getElementById("themeName").focus();
return;
}
const mode = document.querySelector(".mode-option.active")?.dataset.mode || "dark";
const colors = {
accent: document.getElementById("accentColor").value,
background: document.getElementById("backgroundColor").value,
card: document.getElementById("cardColor").value,
text: document.getElementById("textColor").value
};
if (editingThemeId) {
const index = customThemes.findIndex(t => t.id === editingThemeId);
if (index !== -1) {
customThemes[index] = {
...customThemes[index],
name,
mode,
colors
};
}
} else {
const newTheme = {
id: Date.now().toString(36) + Math.random().toString(36).substr(2, 5),
name,
mode,
colors
};
customThemes.push(newTheme);
}
saveCustomThemes();
renderCustomThemes();
closeThemeEditor();
}
function editTheme(id) {
const theme = customThemes.find(t => t.id === id);
if (theme) {
openThemeEditor(theme);
}
}
function shareTheme(id) {
const theme = customThemes.find(t => t.id === id);
if (!theme) return;
const shareData = {
v: 1,
n: theme.name,
m: theme.mode,
c: theme.colors
};
const code = btoa(JSON.stringify(shareData));
document.getElementById("shareCode").value = code;
document.getElementById("shareThemeModal").classList.add("active");
}
function deleteTheme(id) {
const confirmMsg = window.LanguageManager ?
window.LanguageManager.t("settings.custom_themes.delete_confirm") :
"Biztosan törölni szeretnéd ezt a témát?";
if (confirm(confirmMsg)) {
customThemes = customThemes.filter(t => t.id !== id);
saveCustomThemes();
renderCustomThemes();
const currentTheme = getCurrentTheme();
if (currentTheme === `custom-${id}`) {
applyTheme("light-green");
}
}
}
function importTheme() {
const code = document.getElementById("importCode").value.trim();
const errorEl = document.getElementById("importError");
errorEl.textContent = "";
if (!code) {
errorEl.textContent = window.LanguageManager ?
window.LanguageManager.t("settings.custom_themes.import_error_empty") :
"Kérlek illeszd be a téma kódot!";
return;
}
try {
const data = JSON.parse(atob(code));
if (!data.n || !data.m || !data.c) {
throw new Error("Invalid theme data");
}
const newTheme = {
id: Date.now().toString(36) + Math.random().toString(36).substr(2, 5),
name: data.n,
mode: data.m,
colors: data.c
};
customThemes.push(newTheme);
saveCustomThemes();
renderCustomThemes();
closeImportModal();
} catch (error) {
errorEl.textContent = window.LanguageManager ?
window.LanguageManager.t("settings.custom_themes.import_error_invalid") :
"Érvénytelen téma kód!";
}
}
function closeImportModal() {
document.getElementById("importThemeModal").classList.remove("active");
document.getElementById("importCode").value = "";
document.getElementById("importError").textContent = "";
}
function closeShareModal() {
document.getElementById("shareThemeModal").classList.remove("active");
}
function copyShareCode() {
const textarea = document.getElementById("shareCode");
textarea.select();
document.execCommand("copy");
const btn = document.getElementById("copyShareCode");
btn.classList.add("copied");
btn.querySelector(".material-icons-round").textContent = "check";
setTimeout(() => {
btn.classList.remove("copied");
btn.querySelector(".material-icons-round").textContent = "content_copy";
}, 2000);
}
function getCurrentTheme() {
return (
localStorage.getItem("themePreference") ||
@@ -15,7 +320,6 @@ document.addEventListener("DOMContentLoaded", async () => {
const theme = button.dataset.theme;
button.classList.toggle("active", theme === currentTheme);
});
}
function getCurrentLanguage() {
@@ -53,6 +357,16 @@ document.addEventListener("DOMContentLoaded", async () => {
document.documentElement.setAttribute("data-theme", theme);
if (theme.startsWith("custom-")) {
const themeId = theme.replace("custom-", "");
const customTheme = customThemes.find(t => t.id === themeId);
if (customTheme) {
applyCustomThemeColors(customTheme);
}
} else {
clearCustomThemeColors();
}
updateThemeButtons(theme);
const tabs = await chrome.tabs.query({});
@@ -61,12 +375,104 @@ document.addEventListener("DOMContentLoaded", async () => {
.sendMessage(tab.id, {
action: "changeTheme",
theme: theme,
customThemes: customThemes,
})
.catch(() => {});
});
}
const themeButtons = document.querySelectorAll(".theme-option");
function applyCustomThemeColors(theme) {
const root = document.documentElement;
const isDark = theme.mode === "dark";
root.style.setProperty("--background", theme.colors.background);
root.style.setProperty("--card-card", theme.colors.card);
root.style.setProperty("--accent-accent", theme.colors.accent);
root.style.setProperty("--text-primary", theme.colors.text);
root.style.setProperty("--text-secondary", theme.colors.text + "cc");
root.style.setProperty("--text-teritary", theme.colors.text + "80");
root.style.setProperty("--accent-15", theme.colors.accent + "26");
root.style.setProperty("--button-secondaryFill", isDark ? lightenColor(theme.colors.card, 10) : darkenColor(theme.colors.card, 5));
root.style.setProperty("--accent-secondary", isDark ? lightenColor(theme.colors.accent, 20) : darkenColor(theme.colors.accent, 20));
root.style.setProperty("--shadow-blur", isDark ? "0" : "2px");
root.style.setProperty("--accent-shadow", isDark ? "#0000" : theme.colors.accent + "26");
root.style.setProperty("--icon-filter", hexToFilter(theme.colors.accent));
}
function clearCustomThemeColors() {
const root = document.documentElement;
const properties = [
"--background",
"--card-card",
"--accent-accent",
"--text-primary",
"--text-secondary",
"--text-teritary",
"--accent-15",
"--button-secondaryFill",
"--accent-secondary",
"--shadow-blur",
"--accent-shadow",
"--icon-filter"
];
properties.forEach(prop => root.style.removeProperty(prop));
}
function hexToFilter(hex) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
const rNorm = r / 255;
const gNorm = g / 255;
const bNorm = b / 255;
const max = Math.max(rNorm, gNorm, bNorm);
const min = Math.min(rNorm, gNorm, bNorm);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case rNorm: h = ((gNorm - bNorm) / d + (gNorm < bNorm ? 6 : 0)) / 6; break;
case gNorm: h = ((bNorm - rNorm) / d + 2) / 6; break;
case bNorm: h = ((rNorm - gNorm) / d + 4) / 6; break;
}
}
const hue = Math.round(h * 360);
const saturation = Math.round(s * 100);
const lightness = Math.round(l * 100);
const brightnessVal = lightness > 50 ? 1 + (lightness - 50) / 100 : 0.5 + lightness / 100;
const saturateVal = saturation > 0 ? 1 + saturation / 100 : 0;
return `brightness(0) saturate(100%) invert(${lightness}%) sepia(${saturation}%) saturate(${Math.min(500, saturation * 5)}%) hue-rotate(${hue}deg) brightness(${brightnessVal}) contrast(${90 + saturation / 10}%)`;
}
function lightenColor(color, percent) {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * percent);
const R = Math.min(255, (num >> 16) + amt);
const G = Math.min(255, ((num >> 8) & 0x00ff) + amt);
const B = Math.min(255, (num & 0x0000ff) + amt);
return "#" + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1);
}
function darkenColor(color, percent) {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * percent);
const R = Math.max(0, (num >> 16) - amt);
const G = Math.max(0, ((num >> 8) & 0x00ff) - amt);
const B = Math.max(0, (num & 0x0000ff) - amt);
return "#" + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1);
}
const themeButtons = document.querySelectorAll(".theme-option:not(.custom-theme-option)");
themeButtons.forEach((button) => {
button.addEventListener("click", () => {
const theme = button.dataset.theme;
@@ -92,6 +498,74 @@ document.addEventListener("DOMContentLoaded", async () => {
});
});
document.getElementById("addCustomTheme")?.addEventListener("click", () => openThemeEditor());
document.getElementById("closeThemeEditor")?.addEventListener("click", closeThemeEditor);
document.getElementById("cancelThemeEditor")?.addEventListener("click", closeThemeEditor);
document.getElementById("saveTheme")?.addEventListener("click", saveThemeFromEditor);
document.querySelectorAll(".mode-option").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll(".mode-option").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
const isDark = btn.dataset.mode === "dark";
if (isDark) {
document.getElementById("backgroundColor").value = "#0D1202";
document.getElementById("backgroundColorHex").value = "#0D1202";
document.getElementById("cardColor").value = "#141905";
document.getElementById("cardColorHex").value = "#141905";
document.getElementById("textColor").value = "#EAF7CC";
document.getElementById("textColorHex").value = "#EAF7CC";
} else {
document.getElementById("backgroundColor").value = "#FAFFF0";
document.getElementById("backgroundColorHex").value = "#FAFFF0";
document.getElementById("cardColor").value = "#F3FBDE";
document.getElementById("cardColorHex").value = "#F3FBDE";
document.getElementById("textColor").value = "#394C0A";
document.getElementById("textColorHex").value = "#394C0A";
}
updateLivePreview();
});
});
["accent", "background", "card", "text"].forEach(colorType => {
const colorInput = document.getElementById(`${colorType}Color`);
const hexInput = document.getElementById(`${colorType}ColorHex`);
colorInput?.addEventListener("input", () => {
hexInput.value = colorInput.value.toUpperCase();
updateLivePreview();
});
hexInput?.addEventListener("input", () => {
const hex = hexInput.value;
if (/^#[0-9A-Fa-f]{6}$/.test(hex)) {
colorInput.value = hex;
updateLivePreview();
}
});
});
document.getElementById("closeShareModal")?.addEventListener("click", closeShareModal);
document.getElementById("closeShareModalBtn")?.addEventListener("click", closeShareModal);
document.getElementById("copyShareCode")?.addEventListener("click", copyShareCode);
let pressTimer;
const addBtn = document.getElementById("addCustomTheme");
addBtn?.addEventListener("mousedown", () => {
pressTimer = setTimeout(() => {
document.getElementById("importThemeModal").classList.add("active");
}, 500);
});
addBtn?.addEventListener("mouseup", () => clearTimeout(pressTimer));
addBtn?.addEventListener("mouseleave", () => clearTimeout(pressTimer));
document.getElementById("closeImportModal")?.addEventListener("click", closeImportModal);
document.getElementById("cancelImport")?.addEventListener("click", closeImportModal);
document.getElementById("confirmImport")?.addEventListener("click", importTheme);
await loadCustomThemes();
let initialTheme = getCurrentTheme();
await applyTheme(initialTheme);