mirror of
https://github.com/Crazyco-xyz/48hr.email.git
synced 2026-01-09 19:29:34 +01:00
[Feat]: Add light/dark-mode toggle
INcluding localstorage token and quick-load functionality to prevent flashing on initial canvas paint.
This commit is contained in:
parent
cc4e3ddfbd
commit
89003f0d26
8 changed files with 189 additions and 9 deletions
|
|
@ -1,5 +1,21 @@
|
|||
// Consolidated utilities: date formatting and lock modals
|
||||
// Load theme immediately to prevent flash
|
||||
(function() {
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
if (savedTheme === 'light') {
|
||||
document.documentElement.classList.add('light-mode');
|
||||
// Also add to body if it exists
|
||||
if (document.body) {
|
||||
document.body.classList.add('light-mode');
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Ensure body syncs with documentElement theme on load
|
||||
if (document.documentElement.classList.contains('light-mode') && !document.body.classList.contains('light-mode')) {
|
||||
document.body.classList.add('light-mode');
|
||||
}
|
||||
|
||||
function getExpiryMs(time, unit) {
|
||||
switch (unit) {
|
||||
case 'minutes':
|
||||
|
|
@ -310,12 +326,34 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
});
|
||||
}
|
||||
|
||||
function initThemeToggle() {
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
if (!themeToggle) return;
|
||||
|
||||
// Sync body with documentElement if theme was loaded early
|
||||
if (document.documentElement.classList.contains('light-mode')) {
|
||||
document.body.classList.add('light-mode');
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
document.body.classList.toggle('light-mode');
|
||||
document.documentElement.classList.toggle('light-mode');
|
||||
|
||||
// Save preference
|
||||
const isLight = document.body.classList.contains('light-mode');
|
||||
localStorage.setItem('theme', isLight ? 'light' : 'dark');
|
||||
});
|
||||
}
|
||||
|
||||
// Expose utilities and run them
|
||||
window.utils = { formatEmailDates, formatMailDate, initLockModals, initCopyAddress, initExpiryTimers, initQrModal, initHamburgerMenu };
|
||||
window.utils = { formatEmailDates, formatMailDate, initLockModals, initCopyAddress, initExpiryTimers, initQrModal, initHamburgerMenu, initThemeToggle };
|
||||
formatEmailDates();
|
||||
formatMailDate();
|
||||
initLockModals();
|
||||
initCopyAddress();
|
||||
initQrModal();
|
||||
initHamburgerMenu();
|
||||
initThemeToggle();
|
||||
});
|
||||
|
|
@ -52,6 +52,51 @@
|
|||
--overlay-warning-10: rgba(255, 140, 0, 0.1);
|
||||
}
|
||||
|
||||
|
||||
/* Light mode theme */
|
||||
|
||||
body.light-mode {
|
||||
--color-bg-dark: #f5f5f5;
|
||||
--color-bg-medium: #e4e4e4;
|
||||
--color-text-primary: #2c2c2c;
|
||||
--color-text-light: #1a1a1a;
|
||||
--color-text-dim: #666666;
|
||||
--color-text-dimmer: #888888;
|
||||
--color-text-gray: #555555;
|
||||
--color-text-white: #2c2c2c;
|
||||
--color-text-white-alt: #2c2c2c;
|
||||
--color-text-muted: #666666;
|
||||
--color-text-secondary: #444444;
|
||||
--color-border-dark: #cccccc;
|
||||
--color-button-cancel: #aaaaaa;
|
||||
--overlay-white-15: rgba(0, 0, 0, 0.08);
|
||||
--overlay-white-12: rgba(0, 0, 0, 0.06);
|
||||
--overlay-white-10: rgba(0, 0, 0, 0.05);
|
||||
--overlay-white-08: rgba(0, 0, 0, 0.04);
|
||||
--overlay-white-06: rgba(0, 0, 0, 0.03);
|
||||
--overlay-white-05: rgba(0, 0, 0, 0.025);
|
||||
--overlay-white-04: rgba(0, 0, 0, 0.02);
|
||||
--overlay-white-03: rgba(0, 0, 0, 0.015);
|
||||
--overlay-white-02: rgba(0, 0, 0, 0.01);
|
||||
--overlay-black-80: rgba(0, 0, 0, 0.15);
|
||||
--overlay-black-45: rgba(0, 0, 0, 0.08);
|
||||
--overlay-black-40: rgba(0, 0, 0, 0.07);
|
||||
--overlay-black-35: rgba(0, 0, 0, 0.06);
|
||||
--overlay-black-30: rgba(0, 0, 0, 0.05);
|
||||
--overlay-purple-50: rgba(155, 77, 202, 0.25);
|
||||
--overlay-purple-40: rgba(155, 77, 202, 0.2);
|
||||
--overlay-purple-35: rgba(155, 77, 202, 0.18);
|
||||
--overlay-purple-30: rgba(155, 77, 202, 0.15);
|
||||
--overlay-purple-25: rgba(155, 77, 202, 0.12);
|
||||
--overlay-purple-20: rgba(155, 77, 202, 0.1);
|
||||
--overlay-purple-15: rgba(155, 77, 202, 0.08);
|
||||
--overlay-purple-12: rgba(155, 77, 202, 0.06);
|
||||
--overlay-purple-10: rgba(155, 77, 202, 0.05);
|
||||
--overlay-purple-08: rgba(155, 77, 202, 0.04);
|
||||
--overlay-purple-04: rgba(155, 77, 202, 0.02);
|
||||
--overlay-warning-10: rgba(255, 140, 0, 0.05);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
|
|
@ -65,6 +110,16 @@ body {
|
|||
box-shadow: 0 10px 30px var(--overlay-black-45), inset 0 1px 0 var(--overlay-white-06);
|
||||
}
|
||||
|
||||
button:focus {
|
||||
background: var(--overlay-purple-20);
|
||||
border-color: var(--overlay-purple-30);
|
||||
}
|
||||
|
||||
button:focus,
|
||||
button:hover {
|
||||
color: var(--color-accent-purple-light);
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -194,6 +249,7 @@ text-muted {
|
|||
}
|
||||
|
||||
.action-links a {
|
||||
height: 42px;
|
||||
display: inline-block;
|
||||
padding: 12px 24px;
|
||||
border-radius: 15px;
|
||||
|
|
@ -422,15 +478,17 @@ label {
|
|||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.email-card,
|
||||
.mail-content,
|
||||
.mail-attachments {
|
||||
border-radius: 18px;
|
||||
border: 1px solid var(--overlay-white-08);
|
||||
border: 2px solid var(--overlay-purple-30);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 8px 30px var(--overlay-black-30);
|
||||
}
|
||||
|
||||
.email-card {
|
||||
border: 1px solid var(--overlay-purple-30);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 8px 30px var(--overlay-black-30);
|
||||
padding: 24px;
|
||||
transition: all 0.4s ease;
|
||||
position: relative;
|
||||
|
|
@ -564,6 +622,8 @@ label {
|
|||
.mail-header {
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
border: 2px solid var(--overlay-purple-30);
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 12px 40px var(--overlay-black-40);
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +648,6 @@ label {
|
|||
|
||||
.mail-content {
|
||||
background: var(--overlay-white-02);
|
||||
border: 1px solid var(--overlay-white-06);
|
||||
padding: 30px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
|
@ -621,6 +680,8 @@ label {
|
|||
}
|
||||
|
||||
.mail-attachments {
|
||||
border: 2px solid var(--overlay-purple-30);
|
||||
border-radius: 15px;
|
||||
background: var(--overlay-white-03);
|
||||
padding: 25px;
|
||||
}
|
||||
|
|
@ -931,6 +992,52 @@ body.loading-page .logo {
|
|||
}
|
||||
|
||||
|
||||
/* Theme Toggle Button */
|
||||
|
||||
.theme-toggle {
|
||||
background: var(--overlay-purple-20);
|
||||
border: 1px solid var(--overlay-purple-30);
|
||||
border-radius: 15px;
|
||||
height: 42px;
|
||||
color: var(--color-accent-purple-light);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
background: var(--overlay-purple-30);
|
||||
border-color: var(--overlay-purple-40);
|
||||
box-shadow: 0 4px 15px var(--overlay-purple-25);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.theme-toggle svg {
|
||||
margin: auto;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body:not(.light-mode) .theme-icon-dark {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body.light-mode .theme-icon-light {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/* Responsive Styles */
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
|
@ -940,6 +1047,10 @@ body.loading-page .logo {
|
|||
.hamburger-menu {
|
||||
display: flex;
|
||||
}
|
||||
.action-links.mobile-open .theme-toggle {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
.action-links.mobile-hidden>a,
|
||||
.action-links.mobile-hidden>button:not(.hamburger-menu) {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@
|
|||
<a href="#" id="unlockBtn" aria-label="Unlock inbox">Unlock</a>
|
||||
{% endif %}
|
||||
<a href="/" aria-label="Return to home">Logout</a>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle dark/light mode">
|
||||
<svg class="theme-icon theme-icon-dark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path 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" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
<svg class="theme-icon theme-icon-light" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,12 +17,19 @@
|
|||
{% else %}
|
||||
<a href="/logout" aria-label="Logout">Logout</a>
|
||||
{% endif %}
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle dark/light mode">
|
||||
<svg class="theme-icon theme-icon-dark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path 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" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
<svg class="theme-icon theme-icon-light" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<script src="/javascripts/qrcode.js"></script>
|
||||
<script src="/javascripts/utils.js" defer></script>
|
||||
<script src="/javascripts/inbox-init.js" defer data-address="{{ address }}" data-expiry-time="{{ expiryTime }}" data-expiry-unit="{{ expiryUnit }}"></script>
|
||||
<div class="inbox-container">
|
||||
<div class="inbox-header">
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@
|
|||
</script>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/javascripts/utils.js"></script>
|
||||
<script src="/socket.io/socket.io.js" defer="true"></script>
|
||||
<script src="/javascripts/notifications.js" defer="true"></script>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,14 @@
|
|||
{% block header %}
|
||||
<div class="action-links">
|
||||
<a href="/inbox/{{ example }}">Example Inbox</a>
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle dark/light mode">
|
||||
<svg class="theme-icon theme-icon-dark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path 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" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
<svg class="theme-icon theme-icon-light" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,11 +10,18 @@
|
|||
{% else %}
|
||||
<a href="/logout" aria-label="Logout">Logout</a>
|
||||
{% endif %}
|
||||
<button class="theme-toggle" id="themeToggle" aria-label="Toggle dark/light mode">
|
||||
<svg class="theme-icon theme-icon-dark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path 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" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
<svg class="theme-icon theme-icon-light" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<script src="/javascripts/utils.js" defer></script>
|
||||
<div class="mail-container">
|
||||
<div class="mail-header">
|
||||
<h1 class="mail-subject">{{ mail.subject }}</h1>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "48hr.email",
|
||||
"version": "1.7.3",
|
||||
"version": "1.7.4",
|
||||
"private": false,
|
||||
"description": "48hr.email is your favorite open-source tempmail client.",
|
||||
"keywords": [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue