This commit is contained in:
+15
-2
@@ -3,6 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta http-equiv="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
<title>Reese Wells - Self-Hosting & Infrastructure</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
@@ -17,6 +20,7 @@
|
||||
<div class="logo"><Reese /></div>
|
||||
<ul class="nav-links" id="navLinks">
|
||||
<li><a href="#about">About</a></li>
|
||||
<li><a href="#experience">Experience</a></li>
|
||||
<li><a href="#skills">Skills</a></li>
|
||||
<li><a href="#projects">Projects</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
@@ -558,8 +562,17 @@ XwEAnes79w4eYeMUjIytQWACEvy4QoO7X2MLTKliSqc4Ag8=
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<!-- Achievements Tracker -->
|
||||
<section id="achievements" class="achievements-section" style="display: none;">
|
||||
<div class="section-label">Achievements</div>
|
||||
<div class="section-title">Terminal Achievements</div>
|
||||
<p class="section-desc" style="margin: 0 auto 2rem;">Explore every corner of the terminal to unlock achievements.</p>
|
||||
<div id="achievements-count" style="text-align: center; margin-bottom: 2rem; color: var(--text-muted); font-size: 0.9rem;"></div>
|
||||
<div id="achievements-grid" class="achievements-grid"></div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
<p style="font-size: 0.75rem; color: var(--text-muted);">
|
||||
Built with a 100% self-hosted LLM stack running
|
||||
<a href="https://github.com/ggml-org/llama.cpp" target="_blank" rel="noopener" style="color: var(--accent-hover); text-decoration: none;">llama.cpp</a>,
|
||||
|
||||
+277
-73
@@ -274,92 +274,99 @@ function createServerRack() {
|
||||
content.innerHTML = '';
|
||||
}
|
||||
|
||||
if (cmdText === 'exit') {
|
||||
if (!isRoot) {
|
||||
isLoginScreen = true;
|
||||
document.body.innerHTML = '';
|
||||
document.body.style.background = '#000';
|
||||
document.body.style.color = '#fff';
|
||||
document.body.style.fontFamily = 'monospace';
|
||||
document.body.style.padding = '20px';
|
||||
document.body.style.minHeight = '100vh';
|
||||
document.body.style.margin = '0';
|
||||
document.body.style.lineHeight = '1.6';
|
||||
if (cmdText === 'exit') {
|
||||
if (!isRoot) {
|
||||
const newAchievements = checkAchievements(cmdText, false);
|
||||
if (newAchievements.length > 0) {
|
||||
revealAchievements();
|
||||
newAchievements.forEach(a => showToast(a));
|
||||
}
|
||||
|
||||
const loginContainer = document.createElement('div');
|
||||
loginContainer.style.marginBottom = '10px';
|
||||
loginContainer.textContent = 'homelab tty1';
|
||||
document.body.appendChild(loginContainer);
|
||||
isLoginScreen = true;
|
||||
document.body.innerHTML = '';
|
||||
document.body.style.background = '#000';
|
||||
document.body.style.color = '#fff';
|
||||
document.body.style.fontFamily = 'monospace';
|
||||
document.body.style.padding = '20px';
|
||||
document.body.style.minHeight = '100vh';
|
||||
document.body.style.margin = '0';
|
||||
document.body.style.lineHeight = '1.6';
|
||||
|
||||
const loginInput = document.createElement('input');
|
||||
loginInput.type = 'text';
|
||||
loginInput.style.background = 'transparent';
|
||||
loginInput.style.border = 'none';
|
||||
loginInput.style.outline = 'none';
|
||||
loginInput.style.color = '#fff';
|
||||
loginInput.style.fontFamily = 'monospace';
|
||||
loginInput.style.fontSize = '14px';
|
||||
loginInput.style.width = '300px';
|
||||
loginInput.style.caretColor = '#fff';
|
||||
loginInput.autofocus = true;
|
||||
const loginContainer = document.createElement('div');
|
||||
loginContainer.style.marginBottom = '10px';
|
||||
loginContainer.textContent = 'homelab tty1';
|
||||
document.body.appendChild(loginContainer);
|
||||
|
||||
const loginLine = document.createElement('div');
|
||||
loginLine.style.marginBottom = '20px';
|
||||
loginLine.appendChild(document.createTextNode('homelab login: '));
|
||||
loginLine.appendChild(loginInput);
|
||||
const loginInput = document.createElement('input');
|
||||
loginInput.type = 'text';
|
||||
loginInput.style.background = 'transparent';
|
||||
loginInput.style.border = 'none';
|
||||
loginInput.style.outline = 'none';
|
||||
loginInput.style.color = '#fff';
|
||||
loginInput.style.fontFamily = 'monospace';
|
||||
loginInput.style.fontSize = '14px';
|
||||
loginInput.style.width = '300px';
|
||||
loginInput.style.caretColor = '#fff';
|
||||
loginInput.autofocus = true;
|
||||
|
||||
document.body.appendChild(loginLine);
|
||||
const loginLine = document.createElement('div');
|
||||
loginLine.style.marginBottom = '20px';
|
||||
loginLine.appendChild(document.createTextNode('homelab login: '));
|
||||
loginLine.appendChild(loginInput);
|
||||
|
||||
loginInput.focus();
|
||||
document.body.appendChild(loginLine);
|
||||
|
||||
const handleLogin = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const deniedLine = document.createElement('div');
|
||||
deniedLine.textContent = 'Access denied';
|
||||
deniedLine.style.marginTop = '10px';
|
||||
document.body.appendChild(deniedLine);
|
||||
loginInput.focus();
|
||||
|
||||
const newLoginLine = document.createElement('div');
|
||||
newLoginLine.style.marginTop = '10px';
|
||||
const newInput = document.createElement('input');
|
||||
newInput.type = 'text';
|
||||
newInput.style.background = 'transparent';
|
||||
newInput.style.border = 'none';
|
||||
newInput.style.outline = 'none';
|
||||
newInput.style.color = '#fff';
|
||||
newInput.style.fontFamily = 'monospace';
|
||||
newInput.style.fontSize = '14px';
|
||||
newInput.style.width = '300px';
|
||||
newInput.style.caretColor = '#fff';
|
||||
newInput.autofocus = true;
|
||||
newLoginLine.appendChild(document.createTextNode('homelab login: '));
|
||||
newLoginLine.appendChild(newInput);
|
||||
const handleLogin = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
const deniedLine = document.createElement('div');
|
||||
deniedLine.textContent = 'Access denied';
|
||||
deniedLine.style.marginTop = '10px';
|
||||
document.body.appendChild(deniedLine);
|
||||
|
||||
document.body.appendChild(newLoginLine);
|
||||
const newLoginLine = document.createElement('div');
|
||||
newLoginLine.style.marginTop = '10px';
|
||||
const newInput = document.createElement('input');
|
||||
newInput.type = 'text';
|
||||
newInput.style.background = 'transparent';
|
||||
newInput.style.border = 'none';
|
||||
newInput.style.outline = 'none';
|
||||
newInput.style.color = '#fff';
|
||||
newInput.style.fontFamily = 'monospace';
|
||||
newInput.style.fontSize = '14px';
|
||||
newInput.style.width = '300px';
|
||||
newInput.style.caretColor = '#fff';
|
||||
newInput.autofocus = true;
|
||||
newLoginLine.appendChild(document.createTextNode('homelab login: '));
|
||||
newLoginLine.appendChild(newInput);
|
||||
|
||||
loginInput.remove();
|
||||
newInput.focus();
|
||||
document.body.appendChild(newLoginLine);
|
||||
|
||||
newInput.addEventListener('keydown', handleLogin);
|
||||
}
|
||||
};
|
||||
loginInput.remove();
|
||||
newInput.focus();
|
||||
|
||||
loginInput.addEventListener('keydown', handleLogin);
|
||||
newInput.addEventListener('keydown', handleLogin);
|
||||
}
|
||||
};
|
||||
|
||||
return;
|
||||
} else {
|
||||
isRoot = false;
|
||||
}
|
||||
loginInput.addEventListener('keydown', handleLogin);
|
||||
|
||||
const newLine = document.createElement('div');
|
||||
newLine.innerHTML = '<span class="terminal-prompt">$</span> ';
|
||||
content.appendChild(newLine);
|
||||
const newCursor = document.createElement('span');
|
||||
newCursor.className = 'terminal-cursor';
|
||||
newLine.appendChild(newCursor);
|
||||
content.scrollTop = content.scrollHeight;
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
isRoot = false;
|
||||
}
|
||||
|
||||
const newLine = document.createElement('div');
|
||||
newLine.innerHTML = '<span class="terminal-prompt">$</span> ';
|
||||
content.appendChild(newLine);
|
||||
const newCursor = document.createElement('span');
|
||||
newCursor.className = 'terminal-cursor';
|
||||
newLine.appendChild(newCursor);
|
||||
content.scrollTop = content.scrollHeight;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmdText === 'rm -rf /') {
|
||||
if (!isRoot) {
|
||||
@@ -392,6 +399,13 @@ function createServerRack() {
|
||||
newCursor.className = 'terminal-cursor' + (isRoot ? ' red' : '');
|
||||
newLine.appendChild(newCursor);
|
||||
content.scrollTop = content.scrollHeight;
|
||||
|
||||
const newAchievements = checkAchievements(cmdText, isRoot);
|
||||
if (newAchievements.length > 0) {
|
||||
revealAchievements();
|
||||
newAchievements.forEach(a => showToast(a));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -412,6 +426,13 @@ function createServerRack() {
|
||||
newCursor.className = 'terminal-cursor red';
|
||||
newLine.appendChild(newCursor);
|
||||
content.scrollTop = content.scrollHeight;
|
||||
|
||||
const newAchievements = checkAchievements(cmdText, isRoot);
|
||||
if (newAchievements.length > 0) {
|
||||
revealAchievements();
|
||||
newAchievements.forEach(a => showToast(a));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -464,6 +485,12 @@ function createServerRack() {
|
||||
content.appendChild(outLine);
|
||||
}
|
||||
|
||||
const newAchievements = checkAchievements(cmdText, isRoot);
|
||||
if (newAchievements.length > 0) {
|
||||
revealAchievements();
|
||||
newAchievements.forEach(a => showToast(a));
|
||||
}
|
||||
|
||||
const newLine = document.createElement('div');
|
||||
if (isRoot) {
|
||||
newLine.innerHTML = '<span class="terminal-prompt">#</span> ';
|
||||
@@ -716,3 +743,180 @@ document.getElementById('hero').addEventListener('click', (e) => {
|
||||
activeTerminal._mobileInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Achievement System
|
||||
const ACHIEVEMENTS_STORAGE_KEY = 'reese-terminal-achievements';
|
||||
|
||||
const ACHIEVEMENTS = {
|
||||
time_flies: { name: 'Time Flies', desc: 'Check the system uptime', icon: '⏱️', cmd: 'uptime' },
|
||||
curious: { name: 'Curious', desc: 'List directory contents', icon: '📁', cmd: 'ls', prefix: true },
|
||||
port_scanner: { name: 'Port Scanner', desc: 'Check listening ports', icon: '🔍', cmd: 'ss', prefix: true },
|
||||
password_reset: { name: 'Password Reset Request', desc: 'Submit a SNOW request', icon: '🚪', cmd: 'exit' },
|
||||
power_user: { name: 'Power User', desc: 'Gain root access', icon: '👑', cmd: 'sudo su -' },
|
||||
nice_try: { name: 'Nice Try', desc: 'Attempt rm -rf / as a normal user', icon: '😏', cmd: 'rm -rf /' },
|
||||
restore_backup: { name: 'Restore from Backup', desc: 'Actually destroy the system as root', icon: '💥', cmd: 'rm -rf / root' },
|
||||
where_am_i: { name: 'Where Am I?', desc: 'Print working directory', icon: '📍', cmd: 'pwd' },
|
||||
self_aware: { name: 'Self-Aware', desc: 'Print current user', icon: '🪞', cmd: 'whoami' },
|
||||
identity: { name: 'Identity Crisis', desc: 'Print hostname', icon: '🖥️', cmd: 'hostname' },
|
||||
time_keeper: { name: 'Time Keeper', desc: 'Print current date', icon: '📅', cmd: 'date' },
|
||||
system_explorer: { name: 'System Explorer', desc: 'Print system information', icon: '🔧', cmd: 'uname' },
|
||||
access_granted: { name: 'Access Granted', desc: 'Check user identity', icon: '🆔', cmd: 'id' },
|
||||
os_detective: { name: 'OS Detective', desc: 'Read the OS release file', icon: '🐧', cmd: 'cat /etc/os-release' },
|
||||
ssh_explorer: { name: 'SSH Explorer', desc: 'View SSH public key', icon: '🔑', cmd: 'cat ~/.ssh/id_ed25519.pub' },
|
||||
container_watcher: { name: 'Container Watcher', desc: 'List Docker containers', icon: '🐳', cmd: 'docker ps', prefix: true },
|
||||
docker_confused: { name: 'Docker Confused', desc: 'Use wrong Docker command', icon: '🤔', cmd: 'docker ls', prefix: true },
|
||||
container_pro: { name: 'Container Pro', desc: 'Use correct container command', icon: '✅', cmd: 'docker container ls', prefix: true },
|
||||
podman_fan: { name: 'Podman Fan', desc: 'List Podman containers', icon: '📦', cmd: 'podman ps', prefix: true },
|
||||
podman_confused: { name: 'Podman Confused', desc: 'Use wrong Podman command', icon: '🤔', cmd: 'podman ls', prefix: true },
|
||||
podman_pro: { name: 'Podman Pro', desc: 'Use correct Podman command', icon: '✅', cmd: 'podman container ls', prefix: true },
|
||||
service_hunter: { name: 'Service Hunter', desc: 'List systemd units', icon: '🔎', cmd: 'systemctl list-units', prefix: true },
|
||||
service_filter: { name: 'Service Filter', desc: 'Filter running services', icon: '🎯', cmd: 'systemctl list-units --type=service --state=running --no-pager' },
|
||||
network_ninja: { name: 'Network Ninja', desc: 'Show network interfaces', icon: '🌐', cmd: 'ip addr show', prefix: true },
|
||||
disk_detective: { name: 'Disk Detective', desc: 'Check disk usage', icon: '💾', cmd: 'df -h' },
|
||||
memory_minded: { name: 'Memory Minded', desc: 'Check memory usage', icon: '🧠', cmd: 'free -h' },
|
||||
process_watcher: { name: 'Process Watcher', desc: 'List running processes', icon: '👁️', cmd: 'ps aux', prefix: true },
|
||||
memory_hog: { name: 'Memory Hog', desc: 'Find top memory consumers', icon: '🐗', cmd: 'ps aux --sort=-%mem | head -10' },
|
||||
aesthetic_mode: { name: 'Aesthetic Mode', desc: 'Display system info with style', icon: '✨', cmd: 'neofetch' },
|
||||
help_seeker: { name: 'Help Seeker', desc: 'Look up available commands', icon: '📖', cmd: 'help' },
|
||||
clean_slate: { name: 'Clean Slate', desc: 'Clear the terminal', icon: '🧹', cmd: 'clear' },
|
||||
};
|
||||
|
||||
function loadAchievements() {
|
||||
try {
|
||||
const saved = localStorage.getItem(ACHIEVEMENTS_STORAGE_KEY);
|
||||
return saved ? JSON.parse(saved) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function saveAchievements(achieved) {
|
||||
localStorage.setItem(ACHIEVEMENTS_STORAGE_KEY, JSON.stringify(achieved));
|
||||
}
|
||||
|
||||
function checkAchievements(cmdText, isRoot) {
|
||||
const achieved = loadAchievements();
|
||||
const newAchievements = [];
|
||||
|
||||
for (const [key, achievement] of Object.entries(ACHIEVEMENTS)) {
|
||||
if (achieved.includes(key)) continue;
|
||||
|
||||
if (key === 'restore_backup' && cmdText === 'rm -rf /' && isRoot) {
|
||||
achieved.push(key);
|
||||
newAchievements.push(achievement);
|
||||
} else if (key === 'power_user' && (cmdText === 'sudo su -' || cmdText === 'sudo -i')) {
|
||||
achieved.push(key);
|
||||
newAchievements.push(achievement);
|
||||
} else if (key === 'password_reset' && cmdText === 'exit' && !isRoot) {
|
||||
achieved.push(key);
|
||||
newAchievements.push(achievement);
|
||||
} else if (key === 'nice_try' && cmdText === 'rm -rf /' && !isRoot) {
|
||||
achieved.push(key);
|
||||
newAchievements.push(achievement);
|
||||
} else if (key === 'system_explorer' && (cmdText === 'uname' || cmdText === 'uname -a')) {
|
||||
achieved.push(key);
|
||||
newAchievements.push(achievement);
|
||||
} else if (key === 'docker_confused' && cmdText === 'docker ls') {
|
||||
achieved.push(key);
|
||||
newAchievements.push(achievement);
|
||||
} else if (key === 'podman_confused' && cmdText === 'podman ls') {
|
||||
achieved.push(key);
|
||||
newAchievements.push(achievement);
|
||||
} else if (achievement.prefix ? cmdText.startsWith(achievement.cmd) : achievement.cmd === cmdText) {
|
||||
achieved.push(key);
|
||||
newAchievements.push(achievement);
|
||||
}
|
||||
}
|
||||
|
||||
if (newAchievements.length > 0) {
|
||||
saveAchievements(achieved);
|
||||
}
|
||||
|
||||
return newAchievements;
|
||||
}
|
||||
|
||||
function showToast(achievement) {
|
||||
const container = document.getElementById('toast-container') || createToastContainer();
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast';
|
||||
toast.innerHTML = `
|
||||
<div class="toast-icon">${achievement.icon}</div>
|
||||
<div class="toast-content">
|
||||
<div class="toast-label">Achievement Unlocked</div>
|
||||
<div class="toast-title">${achievement.name}</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.classList.add('removing');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 4000);
|
||||
}
|
||||
|
||||
function createToastContainer() {
|
||||
const container = document.createElement('div');
|
||||
container.id = 'toast-container';
|
||||
container.className = 'toast-container';
|
||||
document.body.appendChild(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
function revealAchievements() {
|
||||
const section = document.getElementById('achievements');
|
||||
if (section.style.display === 'none' || !section.style.display) {
|
||||
section.style.display = 'block';
|
||||
section.classList.add('fade-in');
|
||||
setTimeout(() => section.classList.add('visible'), 50);
|
||||
addAchievementsNav();
|
||||
}
|
||||
renderAchievements();
|
||||
}
|
||||
|
||||
function renderAchievements() {
|
||||
const achieved = loadAchievements();
|
||||
const total = Object.keys(ACHIEVEMENTS).length;
|
||||
const count = achieved.length;
|
||||
|
||||
const countEl = document.getElementById('achievements-count');
|
||||
countEl.textContent = `${count} / ${total} achievements unlocked`;
|
||||
|
||||
const grid = document.getElementById('achievements-grid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
for (const [key, achievement] of Object.entries(ACHIEVEMENTS)) {
|
||||
const isUnlocked = achieved.includes(key);
|
||||
const card = document.createElement('div');
|
||||
card.className = `achievement-card ${isUnlocked ? 'unlocked' : 'locked'}`;
|
||||
card.innerHTML = `
|
||||
<div class="achievement-icon">${achievement.icon}</div>
|
||||
<div class="achievement-info">
|
||||
<h3>${isUnlocked ? achievement.name : '???'}</h3>
|
||||
<p>${isUnlocked ? achievement.desc : 'Keep exploring the terminal...'}</p>
|
||||
</div>
|
||||
`;
|
||||
grid.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
function addAchievementsNav() {
|
||||
const navLinks = document.getElementById('navLinks');
|
||||
const existing = document.getElementById('nav-achievements');
|
||||
if (!existing && loadAchievements().length > 0) {
|
||||
const li = document.createElement('li');
|
||||
li.id = 'nav-achievements';
|
||||
li.innerHTML = '<a href="#achievements">Achievements</a>';
|
||||
navLinks.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
(function initAchievements() {
|
||||
const achieved = loadAchievements();
|
||||
if (achieved.length > 0) {
|
||||
revealAchievements();
|
||||
addAchievementsNav();
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
|
||||
+157
@@ -1454,6 +1454,163 @@ footer p {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Achievements Section */
|
||||
.achievements-section {
|
||||
background: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.achievements-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.achievement-card {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 1.25rem;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.achievement-card.locked {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.achievement-card.unlocked {
|
||||
border-color: rgba(234, 179, 8, 0.3);
|
||||
box-shadow: 0 0 20px rgba(234, 179, 8, 0.08);
|
||||
}
|
||||
|
||||
.achievement-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-glow);
|
||||
border: 1px solid rgba(234, 179, 8, 0.2);
|
||||
border-radius: 10px;
|
||||
font-size: 1.2rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.achievement-card.locked .achievement-icon {
|
||||
filter: grayscale(1);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.achievement-info h3 {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.achievement-info p {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.achievement-card.locked .achievement-info h3 {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.achievement-card.locked .achievement-info p {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Toast Notification */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
top: 80px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--accent);
|
||||
border-radius: 12px;
|
||||
padding: 1rem 1.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4), 0 0 20px var(--accent-glow);
|
||||
pointer-events: auto;
|
||||
animation: toastIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
max-width: 340px;
|
||||
}
|
||||
|
||||
.toast.removing {
|
||||
animation: toastOut 0.3s ease forwards;
|
||||
}
|
||||
|
||||
.toast-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--accent-glow);
|
||||
border: 1px solid rgba(234, 179, 8, 0.3);
|
||||
border-radius: 8px;
|
||||
font-size: 1.1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toast-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toast-label {
|
||||
font-size: 0.65rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.15rem;
|
||||
}
|
||||
|
||||
.toast-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@keyframes toastIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(100px) scale(0.9);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes toastOut {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateX(100px) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.nav-links {
|
||||
|
||||
Reference in New Issue
Block a user