feat(frontend): add a terminal theme and three-way theme switcher
Adds the cyber theme to the console and login page, replaces the dark/light toggle with a theme switcher, and moves the login page script to /login.js so it is served under the existing CSP.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
(() => {
|
||||
const root = document.documentElement;
|
||||
const buttons = document.querySelectorAll('.tg button');
|
||||
const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
|
||||
function setTheme(theme) {
|
||||
root.setAttribute('data-theme', theme);
|
||||
try {
|
||||
localStorage.setItem('thecloud-theme', theme);
|
||||
} catch (ignored) {
|
||||
// Theme persistence is optional when browser storage is unavailable.
|
||||
}
|
||||
buttons.forEach((button) => button.classList.toggle('on', button.dataset.t === theme));
|
||||
}
|
||||
|
||||
let initialTheme = matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
||||
try {
|
||||
const savedTheme = localStorage.getItem('thecloud-theme');
|
||||
if (savedTheme === 'dark' || savedTheme === 'light' || savedTheme === 'cyber') {
|
||||
initialTheme = savedTheme;
|
||||
}
|
||||
} catch (ignored) {
|
||||
// The OS preference remains the fallback when browser storage is unavailable.
|
||||
}
|
||||
setTheme(initialTheme);
|
||||
|
||||
buttons.forEach((button) => button.addEventListener('click', () => setTheme(button.dataset.t)));
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if ((event.key === 't' || event.key === 'T') && !event.metaKey && !event.ctrlKey && !event.altKey && !/INPUT|TEXTAREA/.test(event.target.tagName)) {
|
||||
const themes = ['dark', 'light', 'cyber'];
|
||||
setTheme(themes[(themes.indexOf(root.getAttribute('data-theme')) + 1) % themes.length]);
|
||||
}
|
||||
});
|
||||
|
||||
const lines = ['hello, engineer.', 'aws s3 mb s3://my-bucket', 'your own AWS, on your own hardware.', 'sfn start-execution --local', 'welcome to TheCloud.'];
|
||||
const typewriter = document.getElementById('tw');
|
||||
let line = 0;
|
||||
let count = 0;
|
||||
let deleting = false;
|
||||
if (reducedMotion) {
|
||||
typewriter.textContent = lines[2];
|
||||
} else {
|
||||
function type() {
|
||||
const word = lines[line];
|
||||
if (deleting) {
|
||||
count--;
|
||||
typewriter.textContent = word.slice(0, count);
|
||||
if (count === 0) {
|
||||
deleting = false;
|
||||
line = (line + 1) % lines.length;
|
||||
setTimeout(type, 400);
|
||||
return;
|
||||
}
|
||||
setTimeout(type, 35);
|
||||
return;
|
||||
}
|
||||
count++;
|
||||
typewriter.textContent = word.slice(0, count);
|
||||
if (count === word.length) {
|
||||
deleting = true;
|
||||
setTimeout(type, 2200);
|
||||
return;
|
||||
}
|
||||
setTimeout(type, 75 + Math.random() * 50);
|
||||
}
|
||||
type();
|
||||
}
|
||||
|
||||
const hero = document.getElementById('top');
|
||||
const glow = document.getElementById('grid-glow');
|
||||
if (!reducedMotion) {
|
||||
hero.addEventListener('mousemove', (event) => {
|
||||
const rect = hero.getBoundingClientRect();
|
||||
glow.style.left = `${event.clientX - rect.left}px`;
|
||||
glow.style.top = `${event.clientY - rect.top}px`;
|
||||
glow.style.opacity = '1';
|
||||
});
|
||||
hero.addEventListener('mouseleave', () => {
|
||||
glow.style.opacity = '0';
|
||||
});
|
||||
}
|
||||
|
||||
const progress = document.getElementById('bar');
|
||||
addEventListener('scroll', () => {
|
||||
const height = document.documentElement.scrollHeight - innerHeight;
|
||||
progress.style.width = `${height > 0 ? scrollY / height * 100 : 0}%`;
|
||||
}, {passive: true});
|
||||
|
||||
const form = document.querySelector('[data-sso-form]');
|
||||
if (form) {
|
||||
form.addEventListener('submit', () => {
|
||||
const button = form.querySelector('.btn');
|
||||
button.classList.add('is-loading');
|
||||
button.disabled = true;
|
||||
document.getElementById('label').textContent = 'Redirecting...';
|
||||
});
|
||||
}
|
||||
|
||||
const copy = document.getElementById('cp');
|
||||
if (copy) {
|
||||
copy.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(document.getElementById('code').innerText).then(() => {
|
||||
copy.textContent = 'copied';
|
||||
copy.classList.add('ok');
|
||||
setTimeout(() => {
|
||||
copy.textContent = 'copy';
|
||||
copy.classList.remove('ok');
|
||||
}, 1800);
|
||||
});
|
||||
});
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user